Megosztás:


Érték-összehasonlítók

Jótanács

A dokumentumban szereplő kód futtatható mintaként megtalálható a GitHubon.

Háttér

A változáskövetés azt jelenti, hogy az EF Core automatikusan meghatározza, hogy az alkalmazás milyen módosításokat hajtott végre egy betöltött entitáspéldányon, így ezek a módosítások visszamenthetők az adatbázisba, amikor SaveChanges meghívják őket. Az EF Core ezt általában úgy hajtja végre, hogy pillanatképet készít a példányról az adatbázisból való betöltésekor, és összehasonlítja a pillanatképet az alkalmazásnak átadott példánysal.

Az EF Core beépített logikával rendelkezik az adatbázisokban használt szabványos típusok pillanatképezéséhez és összehasonlításához, így a felhasználóknak általában nem kell foglalkozniuk ezzel a témakörrel. Ha azonban egy tulajdonság egy értékkonverteren keresztül van leképezve, az EF Core-nak összehasonlítást kell végeznie tetszőleges felhasználói típusok esetében, ami összetett lehet. Alapértelmezés szerint az EF Core a típusok (pl. a Equals módszer) alapján meghatározott alapértelmezett egyenlőségi összehasonlítást használja; a pillanatkép létrehozásához az értéktípusok másolása történik, míg referenciatípusok esetében nem történik másolás, és ugyanazt a példányt használja a rendszer, mint a pillanatképet.

Olyan esetekben, amikor a beépített összehasonlítási viselkedés nem megfelelő, a felhasználók megadhatnak egy érték-összehasonlítót, amely a kivonatkódok pillanatképezésére, összehasonlítására és kiszámítására szolgáló logikát tartalmaz. Az alábbi beállítás például az értékkonvertálást állítja be az adatbázis JSON-sztringjévé konvertálandó tulajdonsághoz List<int> , és meghatározza a megfelelő érték-összehasonlítót is:

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()));

További részletekért lásd alább a mutable osztályokat .

Vegye figyelembe, hogy az érték-összehasonlítók azt is meghatározzák, hogy két kulcsérték megegyezik-e a kapcsolatok feloldásakor; ezt az alábbiakban ismertetjük.

Sekély és mély összehasonlítás

Az olyan kis, nem módosítható értéktípusok esetében, mint például intaz EF Core alapértelmezett logikája, jól működik: az érték a pillanatkép készítésekor as-is lesz másolva, és összehasonlítható a típus beépített egyenlőségi összehasonlításával. A saját érték-összehasonlító implementálása során fontos megfontolni, hogy a mély vagy a sekély összehasonlítási (és pillanatképkészítési) logika megfelelő-e.

Vegye figyelembe a bájttömböket, amelyek tetszőlegesen nagyok lehetnek. Ezeket össze lehet hasonlítani:

  • Úgy, hogy referencia szerint csak akkor észlelhető különbség, ha új bájttömb kerül használatra
  • Mély összehasonlítással a tömb bájtjainak mutációja észlelhető

Alapértelmezés szerint az EF Core az első ilyen módszert használja a nem kulcsalapú bájttömbökhöz. Vagyis csak a hivatkozások lesznek összehasonlítva, és a rendszer csak akkor észlel módosítást, ha egy meglévő bájttömb újra cserélődik. Ez egy pragmatikus döntés, amely elkerüli a teljes tömbök másolását és azok egyenkénti bájtonkénti összehasonlítását a SaveChanges végrehajtás során. Ez azt jelenti, hogy az egyik kép egy másikra való cseréjének gyakori forgatókönyve hatékony módon történik.

Másrészt a hivatkozási egyenlőség nem működik, ha a bájttömbök bináris kulcsokat jelölnek, mivel nagyon valószínűtlen, hogy egy FK-tulajdonság ugyanarra a példányra legyen beállítva, mint egy PK-tulajdonság, amelyhez össze kell hasonlítani. Ezért az EF Core mély összehasonlításokat használ a kulcsként működő bájttömbökhöz; ez valószínűleg nem lesz nagy teljesítményű, mivel a bináris kulcsok általában rövidek.

Vegye figyelembe, hogy a választott összehasonlítási és pillanatkép-készítési logikának egymással kell egyeznie: a mély összehasonlításhoz mély pillanatképkészítés szükséges a helyes működéshez.

Egyszerű nem módosítható osztályok

Fontolja meg azt a tulajdonságot, amely értékkonvertert használ egy egyszerű, nem módosítható osztály leképezéséhez.

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));

Az ilyen típusú tulajdonságoknak nincs szükségük különleges összehasonlításokra vagy pillanatképekre, mert:

  • Az egyenlőség felül van bírálva, hogy a különböző példányok megfelelően hasonlítsák össze
  • A típus nem módosítható, így nincs esély a pillanatkép értékének mutációjára

Tehát ebben az esetben az EF Core alapértelmezett viselkedése rendben van.

Egyszerű nem módosítható szerkezetek

Az egyszerű szerkezetek leképezése szintén egyszerű, és nem igényel speciális összehasonlítókat vagy pillanatképeket.

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));

Az EF Core beépített támogatást biztosít a struct tulajdonságok lefordított, tagonkénti összehasonlításához. Ez azt jelenti, hogy a szerkezeteknek nem kell felülírniuk az egyenlőséget az EF Core miatt, de ezt más okokból is megtehetik. Emellett nincs szükség speciális pillanatkép-készításra, mivel a szerkezetek nem módosíthatók, és mindenképpen tagként másolódnak. (Ez a mutable structs esetében is igaz, de a mutable structs általában kerülendő.)

Mutable osztályok

Ha lehetséges, érdemes nem módosítható típusokat (osztályokat vagy szerkezeteket) használni értékkonverterekkel. Ez általában hatékonyabb, és tisztább szemantikával rendelkezik, mint egy mutable típusú. Ennek ellenére gyakori, hogy olyan típusú tulajdonságokat használnak, amelyeket az alkalmazás nem tud módosítani. Például egy számlistát tartalmazó tulajdonság leképezése:

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

Az List<T> osztály:

  • Referenciaegyenlőséggel rendelkezik; az azonos értékeket tartalmazó két lista eltérőként lesz kezelve.
  • Nem módosítható; a listában szereplő értékek hozzáadhatók és eltávolíthatók.

A lista tulajdonságának tipikus értékátalakítása a lista JSON-ra és JSON-ból történő átalakítása lehet.

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()));

A ValueComparer<T> konstruktor három kifejezést fogad el:

  • Az egyenlőség ellenőrzésére szolgáló kifejezés
  • Kivonatkód generálására szolgáló kifejezés
  • Érték pillanatképét ábrázoló kifejezés

Ebben az esetben az összehasonlítást úgy végezzük el, hogy ellenőrizzük, hogy a számok sorozata megegyezik-e.

Ugyanígy a kivonatkód is ebből a sorozatból épül fel. (Vegye figyelembe, hogy ez egy kivonatkód a mutable értékek felett, ezért problémákat okozhat. Legyen módosíthatatlan, ha lehet.)

A pillanatkép a lista ToList klónozásával jön létre. Erre is csak akkor van szükség, ha a listák mutálódnak. Legyen módosíthatatlan, ha lehet.

Megjegyzés:

Az értékkonverterek és az összehasonlítók egyszerű delegálások helyett kifejezések használatával jönnek létre. Ennek az az oka, hogy az EF Core ezeket a kifejezéseket egy sokkal összetettebb kifejezésfába szúrja be, amelyet aztán egy entitásalakító delegálttá fordít. Elméletileg ez hasonló a fordítóprogram egybebetételéhez. Előfordulhat például, hogy egy egyszerű átalakítás csak egy öntött formában lefordított, nem pedig egy másik metódus meghívása az átalakítás elvégzésére.

Kulcs-összehasonlítók

A háttérszakasz bemutatja, hogy a kulcs-összehasonlítások miért igényelhetnek speciális szemantikát. Mindenképpen hozzon létre egy olyan összehasonlítót, amely megfelel a kulcsok jellemzőinek, amikor elsődleges, fő vagy idegen kulcstulajdonságra állítja.

Olyan ritka esetekben használható SetKeyValueComparer , amikor ugyanazon a tulajdonságon eltérő szemantikára van szükség.

Megjegyzés:

SetStructuralValueComparer elavult. A SetKeyValueComparer használható helyette.

Az alapértelmezett összehasonlító felülírása

Előfordulhat, hogy az EF Core által használt alapértelmezett összehasonlítás nem megfelelő. Például a bájttömbök mutációja alapértelmezés szerint nem észlelhető az EF Core-ban. Ezt felül lehet bírálni egy másik összehasonlító beállításával a tulajdonságon:

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()));

Az EF Core most összehasonlítja a bájtsorozatokat, és így észleli a bájttömb mutációit.