Değer Karşılaştırıcıları

Bahşiş

Bu belgedeki kod GitHub'da çalıştırılabilir bir örnek olarak bulunabilir.

Background

Değişiklik izleme , EF Core'un uygulama tarafından yüklenen bir varlık örneğinde hangi değişikliklerin gerçekleştirildiğini otomatik olarak belirlediği ve bu değişikliklerin çağrıldığında SaveChanges veritabanına geri kaydedilebileceği anlamına gelir. EF Core bunu genellikle veritabanından yüklendiğinde örneğin anlık görüntüsünü alarak ve bu anlık görüntüyü uygulamaya dağıtılan örnekle karşılaştırarak gerçekleştirir.

EF Core, veritabanlarında kullanılan çoğu standart türün anlık görüntüsünü almak ve karşılaştırmak için yerleşik mantıkla birlikte gelir, bu nedenle kullanıcıların genellikle bu konu hakkında endişelenmeleri gerekmez. Ancak, bir özellik bir değer dönüştürücüsü ile eşlendiğinde EF Core'un karmaşık olabilecek rastgele kullanıcı türleri üzerinde karşılaştırma gerçekleştirmesi gerekir. Varsayılan olarak, EF Core türler tarafından tanımlanan varsayılan eşitlik karşılaştırmasını Equals kullanır (örneğin yöntemi); anlık görüntü oluşturmak için değer türleri kopyalanır ve başvuru türleri için hiçbir kopyalama gerçekleşmez ve anlık görüntü olarak aynı örnek kullanılır.

Yerleşik karşılaştırma davranışının uygun olmadığı durumlarda, kullanıcılar bir karma kodun anlık görüntüsünü alma, karşılaştırma ve hesaplama mantığı içeren bir değer karşılaştırıcısı sağlayabilir. Örneğin, aşağıdakiler, veritabanındaki bir JSON dizesine dönüştürülecek özellik için List<int> değer dönüştürmeyi ayarlar ve uygun değer karşılaştırıcısını da tanımlar:

modelBuilder
    .Entity<EntityType>()
    .Property(e => e.MyListProperty)
    .HasConversion(
        v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
        v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
        new ValueComparer<List<int>>(
            (c1, c2) => c1.SequenceEqual(c2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
            c => c.ToList()));

Diğer ayrıntılar için aşağıdaki değiştirilebilir sınıflara bakın.

İlişkileri çözerken iki anahtar değerinin aynı olup olmadığını belirlerken değer karşılaştırıcılarının da kullanıldığını unutmayın; aşağıda açıklanmıştır.

Sığ ve derin karşılaştırma

gibi küçük, sabit değer türleri için intEF Core'un varsayılan mantığı düzgün çalışır: değer anlık görüntülendiğinde olduğu gibi kopyalanır ve türün yerleşik eşitlik karşılaştırmasıyla karşılaştırılır. Kendi değer karşılaştırıcınızı uygularken derin veya sığ karşılaştırma (ve anlık görüntü oluşturma) mantığının uygun olup olmadığını göz önünde bulundurmanız önemlidir.

Rastgele olarak büyük olabilecek bayt dizilerini göz önünde bulundurun. Bunlar karşılaştırılabilir:

  • Başvuruya göre, fark yalnızca yeni bir bayt dizisi kullanıldığında algılanır
  • Derin karşılaştırmayla, dizideki baytların mutasyonu algılanır

VARSAYıLAN olarak EF Core, anahtar olmayan bayt dizileri için bu yaklaşımlardan ilkini kullanır. Başka bir ifadeyle, yalnızca başvurular karşılaştırılır ve yalnızca mevcut bir bayt dizisi yenisiyle değiştirildiğinde bir değişiklik algılanır. Bu, tüm dizilerin kopyalanmasını ve yürütülürken bayt bayt karşılaştırmasını önleyen pragmatik bir karardır SaveChanges. Bu, örneğin, bir görüntüyü başka bir görüntüyle değiştirmenin yaygın senaryosunun performans açısından ele alındığını gösterir.

Öte yandan, bir FK özelliğinin karşılaştırılması gereken PK özelliğiyle aynı örneğe ayarlanması çok düşük bir olasılık olduğundan, ikili anahtarları temsil etmek için bayt dizileri kullanıldığında başvuru eşitliği çalışmaz. Bu nedenle EF Core, anahtar görevi üstlenerek bayt dizileri için derin karşılaştırmalar kullanır; İkili anahtarlar genellikle kısa olduğundan bu büyük bir performansa sahip olma olasılığı düşüktür.

Seçilen karşılaştırma ve anlık görüntü oluşturma mantığının birbirine karşılık geldiğini unutmayın: derin karşılaştırma için düzgün çalışması için derin anlık görüntü gerekir.

Basit sabit sınıflar

Basit, sabit bir sınıfı eşlemek için değer dönüştürücü kullanan bir özellik düşünün.

public sealed class ImmutableClass
{
    public ImmutableClass(int value)
    {
        Value = value;
    }

    public int Value { get; }

    private bool Equals(ImmutableClass other)
        => Value == other.Value;

    public override bool Equals(object obj)
        => ReferenceEquals(this, obj) || obj is ImmutableClass other && Equals(other);

    public override int GetHashCode()
        => Value.GetHashCode();
}
modelBuilder
    .Entity<MyEntityType>()
    .Property(e => e.MyProperty)
    .HasConversion(
        v => v.Value,
        v => new ImmutableClass(v));

Bu türün özellikleri için özel karşılaştırmalar veya anlık görüntüler gerekmez, çünkü:

  • Eşitlik geçersiz kılınır, böylece farklı örnekler doğru karşılaştırılır
  • Tür sabittir, bu nedenle anlık görüntü değerini kapatma şansı yoktur

Bu durumda EF Core'un varsayılan davranışı olduğu gibi iyidir.

Basit sabit yapılar

Basit yapılar için eşleme de basittir ve özel karşılaştırıcılar veya anlık görüntü gerektirmez.

public readonly struct ImmutableStruct
{
    public ImmutableStruct(int value)
    {
        Value = value;
    }

    public int Value { get; }
}
modelBuilder
    .Entity<EntityType>()
    .Property(e => e.MyProperty)
    .HasConversion(
        v => v.Value,
        v => new ImmutableStruct(v));

EF Core, yapı özelliklerinin derlenmiş, üye düzeyinde karşılaştırmalarını oluşturmak için yerleşik desteğe sahiptir. Bu, yapıların EF Core için eşitliğin geçersiz kılınmasına gerek olmadığı anlamına gelir, ancak bunu başka nedenlerle yapmayı tercih edebilirsiniz. Ayrıca yapılar sabit olduğundan ve yine de her zaman üye düzeyinde kopyalandığından özel anlık görüntü oluşturma gerekli değildir. (Bu, değiştirilebilir yapılar için de geçerlidir, ancak genel olarak değiştirilebilir yapılardan kaçınılmalıdır.)

Değiştirilebilir sınıflar

Mümkün olduğunda değer dönüştürücüleriyle sabit türler (sınıflar veya yapılar) kullanmanız önerilir. Bu genellikle daha verimlidir ve değiştirilebilir bir tür kullanmaktan daha temiz semantiklere sahiptir. Ancak, bununla birlikte, uygulamanın değiştiremeyeceği türlerin özelliklerini kullanmak yaygın bir durumdur. Örneğin, sayı listesi içeren bir özelliği eşleme:

public List<int> MyListProperty { get; set; }

Sınıfı List<T> :

  • Başvuru eşitliğine sahiptir; aynı değerleri içeren iki liste farklı olarak değerlendirilir.
  • Değişebilir; değerleri eklenebilir ve kaldırılabilir.

Liste özelliğindeki tipik bir değer dönüştürmesi, listeyi JSON'a ve JSON'dan dönüştürebilir:

modelBuilder
    .Entity<EntityType>()
    .Property(e => e.MyListProperty)
    .HasConversion(
        v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
        v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
        new ValueComparer<List<int>>(
            (c1, c2) => c1.SequenceEqual(c2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
            c => c.ToList()));

ValueComparer<T> Oluşturucu üç ifade kabul eder:

  • Eşitliği denetlemek için bir ifade
  • Karma kod oluşturmaya yönelik ifade
  • Bir değerin anlık görüntüsünü almak için ifade

Bu durumda karşılaştırma, sayı dizilerinin aynı olup olmadığını denetleyerek yapılır.

Benzer şekilde, karma kod da aynı diziden oluşturulur. (Bunun, değiştirilebilir değerler üzerindeki bir karma kod olduğunu ve bu nedenle sorunlara neden olabileceğini unutmayın. İsterseniz sabit olun.)

Anlık görüntü, ile ToListliste kopyalanarak oluşturulur. Bu, yalnızca listelerin sessize alınması durumunda gereklidir. İsterseniz sabit olun.

Dekont

Değer dönüştürücüler ve karşılaştırıcılar basit temsilciler yerine ifadeler kullanılarak oluşturulur. Bunun nedeni EF Core'un bu ifadeleri çok daha karmaşık bir ifade ağacına eklemesi ve ardından bir varlık şekillendirici temsilcisine derlenmesidir. Kavramsal olarak bu, derleyici iç çizgisine benzer. Örneğin, basit bir dönüştürme, dönüştürmeyi yapmak için başka bir yönteme yapılan bir çağrı yerine yalnızca yayında derlenmiş olabilir.

Önemli karşılaştırıcılar

Arka plan bölümü, anahtar karşılaştırmalarının neden özel semantik gerektirebileceğini kapsar. Birincil, sorumlu veya yabancı anahtar özelliğinde ayarlarken anahtarlar için uygun bir karşılaştırıcı oluşturduğunuzdan emin olun.

Aynı özellik üzerinde farklı semantiğin gerekli olduğu nadir durumlarda kullanın SetKeyValueComparer .

Dekont

SetStructuralValueComparer engellendi. Bunun yerine SetKeyValueComparer kullanın.

Varsayılan karşılaştırıcıyı geçersiz kılma

Bazen EF Core tarafından kullanılan varsayılan karşılaştırma uygun olmayabilir. Örneğin, bayt dizilerinin mutasyonu EF Core'da varsayılan olarak algılanmaz. Özelliğinde farklı bir karşılaştırıcı ayarlanarak bu geçersiz kılınabilir:

modelBuilder
    .Entity<EntityType>()
    .Property(e => e.MyBytes)
    .Metadata
    .SetValueComparer(
        new ValueComparer<byte[]>(
            (c1, c2) => c1.SequenceEqual(c2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
            c => c.ToArray()));

EF Core artık bayt dizilerini karşılaştıracak ve bu nedenle bayt dizisi mutasyonlarını algılayacak.