Delen via


Identiteitsresolutie in EF Core

A DbContext kan slechts één entiteitsexemplaar bijhouden met een bepaalde primaire-sleutelwaarde. Dit betekent dat meerdere exemplaren van een entiteit met dezelfde sleutelwaarde moeten worden omgezet in één exemplaar. Dit wordt 'identiteitsresolutie' genoemd. Identiteitsoplossing zorgt ervoor dat Entity Framework Core (EF Core) een consistente grafiek bijhoudt zonder dubbelzinnigheid over de relaties of eigenschapswaarden van de entiteiten.

Aanbeveling

In dit document wordt ervan uitgegaan dat entiteitsstatussen en de basisprincipes van EF Core-wijzigingen bijhouden worden begrepen. Zie Wijzigingen bijhouden in EF Core voor meer informatie over deze onderwerpen.

Aanbeveling

U kunt alle code in dit document uitvoeren en fouten opsporen door de voorbeeldcode van GitHub te downloaden.

Introductie

De volgende code vraagt een entiteit op en probeert vervolgens een andere instance te koppelen met dezelfde primaire-sleutelwaarde.

using var context = new BlogsContext();

var blogA = await context.Blogs.SingleAsync(e => e.Id == 1);
var blogB = new Blog { Id = 1, Name = ".NET Blog (All new!)" };

try
{
    context.Update(blogB); // This will throw
}
catch (Exception e)
{
    Console.WriteLine($"{e.GetType().FullName}: {e.Message}");
}

Het uitvoeren van deze code resulteert in de volgende uitzondering:

System.InvalidOperationException: het exemplaar van het entiteitstype 'Blog' kan niet worden bijgehouden omdat er al een ander exemplaar met de sleutelwaarde {Id: 1} wordt bijgehouden. Wanneer u bestaande entiteiten koppelt, moet u ervoor zorgen dat slechts één entiteitsexemplaar met een bepaalde sleutelwaarde is gekoppeld.

EF Core vereist één exemplaar omdat:

  • Eigenschapswaarden kunnen verschillen tussen meerdere exemplaren. Bij het bijwerken van de database moet EF Core weten welke eigenschapswaarden moeten worden gebruikt.
  • Relaties met andere entiteiten kunnen verschillen tussen meerdere exemplaren. 'blogA' kan bijvoorbeeld zijn gerelateerd aan een andere verzameling berichten dan 'blogB'.

De bovenstaande uitzondering wordt vaak aangetroffen in deze situaties:

  • Bij een poging om een entiteit bij te werken
  • Bij een poging om een geserialiseerde grafiek van entiteiten bij te houden
  • Wanneer u geen sleutelwaarde instelt die niet automatisch wordt gegenereerd
  • Bij het hergebruik van een DbContext-exemplaar voor meerdere werkeenheden

Elk van deze situaties wordt besproken in de volgende secties.

Een entiteit bijwerken

Er zijn verschillende benaderingen voor het bijwerken van een entiteit met nieuwe waarden, zoals beschreven in Wijzigingen bijhouden in EF Core en Expliciet bijhouden van entiteiten. Deze benaderingen worden hieronder beschreven in de context van identiteitsomzetting. Een belangrijk punt om op te merken is dat elk van de benaderingen gebruikmaakt van een query of een aanroep naar een van Update of Attach, maar nooit beide.

Update aanroepen

De entiteit die moet worden bijgewerkt, komt vaak niet uit een query in dbContext die we gaan gebruiken voor SaveChanges. In een webtoepassing kan bijvoorbeeld een entiteitsexemplaar worden gemaakt op basis van de informatie in een POST-aanvraag. De eenvoudigste manier om dit te verwerken is door te gebruiken DbContext.Update of DbSet<TEntity>.Update. Voorbeeld:

public static async Task UpdateFromHttpPost1(Blog blog)
{
    using var context = new BlogsContext();

    context.Update(blog);

    await context.SaveChangesAsync();
}

In dit geval:

  • Er wordt slechts één exemplaar van de entiteit gemaakt.
  • Het entiteitsexemplaar wordt niet uit de database opgevraagd als onderdeel van het bijwerken.
  • Alle eigenschapswaarden worden bijgewerkt in de database, ongeacht of ze daadwerkelijk zijn gewijzigd of niet.
  • Er wordt één database-interactie uitgevoerd.

Query past vervolgens wijzigingen toe

Meestal is het niet bekend welke eigenschapswaarden daadwerkelijk zijn gewijzigd wanneer een entiteit wordt gemaakt op basis van informatie in een POST-aanvraag of vergelijkbaar. Vaak is het prima om alleen alle waarden in de database bij te werken, zoals we in het vorige voorbeeld hebben gedaan. Als de toepassing echter veel entiteiten verwerkt en slechts een klein aantal entiteiten werkelijke wijzigingen heeft, kan het handig zijn om de verzonden updates te beperken. Dit kan worden bereikt door een query uit te voeren om de entiteiten bij te houden zoals deze zich momenteel in de database bevinden en vervolgens wijzigingen toe te passen op deze bijgehouden entiteiten. Voorbeeld:

public static async Task UpdateFromHttpPost2(Blog blog)
{
    using var context = new BlogsContext();

    var trackedBlog = await context.Blogs.FindAsync(blog.Id);

    trackedBlog.Name = blog.Name;
    trackedBlog.Summary = blog.Summary;

    await context.SaveChangesAsync();
}

In dit geval:

  • Er wordt slechts één instantie van de entiteit bijgehouden; degene die door de Find query uit de database wordt geretourneerd.
  • Update, Attachenz. worden niet gebruikt.
  • Alleen eigenschapswaarden die daadwerkelijk zijn gewijzigd, worden bijgewerkt in de database.
  • Er worden twee databaseverzoeken gemaakt.

EF Core heeft enkele helpers voor het overdragen van eigenschapswaarden als deze. Kopieert bijvoorbeeld PropertyValues.SetValues alle waarden van het opgegeven object en stelt deze in op het bijgehouden object:

public static async Task UpdateFromHttpPost3(Blog blog)
{
    using var context = new BlogsContext();

    var trackedBlog = await context.Blogs.FindAsync(blog.Id);

    context.Entry(trackedBlog).CurrentValues.SetValues(blog);

    await context.SaveChangesAsync();
}

SetValues accepteert verschillende objecttypen, waaronder gegevensoverdrachtobjecten (DTU's) met eigenschapsnamen die overeenkomen met de eigenschappen van het entiteitstype. Voorbeeld:

public static async Task UpdateFromHttpPost4(BlogDto dto)
{
    using var context = new BlogsContext();

    var trackedBlog = await context.Blogs.FindAsync(dto.Id);

    context.Entry(trackedBlog).CurrentValues.SetValues(dto);

    await context.SaveChangesAsync();
}

Of een woordenlijst met naam/waarde-vermeldingen voor de eigenschapswaarden:

public static async Task UpdateFromHttpPost5(Dictionary<string, object> propertyValues)
{
    using var context = new BlogsContext();

    var trackedBlog = await context.Blogs.FindAsync(propertyValues["Id"]);

    context.Entry(trackedBlog).CurrentValues.SetValues(propertyValues);

    await context.SaveChangesAsync();
}

Zie Toegang tot bijgehouden entiteiten voor meer informatie over het werken met eigenschapswaarden zoals deze.

Oorspronkelijke waarden gebruiken

Tot nu toe heeft elke benadering een query uitgevoerd voordat de update wordt uitgevoerd of alle eigenschapswaarden bijgewerkt, ongeacht of ze al dan niet zijn gewijzigd. Als u alleen waarden wilt bijwerken die zijn gewijzigd zonder query's uit te voeren als onderdeel van de update, is specifieke informatie vereist over welke eigenschapswaarden zijn gewijzigd. Een veelvoorkomende manier om deze informatie op te halen, is door zowel de huidige als de oorspronkelijke waarden in de HTTP-post of vergelijkbaar terug te sturen. Voorbeeld:

public static async Task UpdateFromHttpPost6(Blog blog, Dictionary<string, object> originalValues)
{
    using var context = new BlogsContext();

    context.Attach(blog);
    context.Entry(blog).OriginalValues.SetValues(originalValues);

    await context.SaveChangesAsync();
}

In deze code wordt de entiteit met gewijzigde waarden eerst gekoppeld. Dit zorgt ervoor dat EF Core de entiteit in de Unchanged status bijhoudt. Dat wil gezegd, zonder dat er eigenschapswaarden zijn gemarkeerd als gewijzigd. De woordenlijst met oorspronkelijke waarden wordt vervolgens toegepast op deze bijgehouden entiteit. Hiermee worden gewijzigde eigenschappen gemarkeerd met verschillende huidige en oorspronkelijke waarden. Eigenschappen met dezelfde huidige en oorspronkelijke waarden worden niet gemarkeerd als gewijzigd.

In dit geval:

  • Er wordt slechts één exemplaar van de entiteit bijgehouden, met behulp van Attach.
  • Het entiteitsexemplaar wordt niet uit de database opgevraagd als onderdeel van het bijwerken.
  • Het toepassen van de oorspronkelijke waarden zorgt ervoor dat alleen eigenschapswaarden die daadwerkelijk zijn gewijzigd, worden bijgewerkt in de database.
  • Er wordt één database-interactie uitgevoerd.

Net als bij de voorbeelden in de vorige sectie hoeven de oorspronkelijke waarden niet als woordenlijst te worden doorgegeven; een entiteitsexemplaar of DTO werkt ook.

Aanbeveling

Hoewel deze benadering aantrekkelijke kenmerken heeft, moet de oorspronkelijke waarden van de entiteit naar en van de webclient worden verzonden. Overweeg zorgvuldig of deze extra complexiteit de voordelen waard is; voor veel toepassingen is een van de eenvoudigere benaderingen pragmatischer.

Een geserialiseerde grafiek koppelen

EF Core werkt met grafieken van entiteiten die zijn verbonden via refererende sleutels en navigatie-eigenschappen, zoals beschreven in Wijzigen van refererende sleutels en navigatie. Als deze grafieken buiten EF Core worden gemaakt met bijvoorbeeld een JSON-bestand, kunnen ze meerdere exemplaren van dezelfde entiteit hebben. Deze duplicaten moeten worden omgezet in afzonderlijke exemplaren voordat de grafiek kan worden bijgehouden.

Grafieken zonder duplicaten

Voordat u verdergaat, is het belangrijk om te herkennen dat:

  • Serializers hebben vaak opties voor het afhandelen van lussen en dubbele exemplaren in de grafiek.
  • De keuze van het object dat wordt gebruikt als de wortel van de grafiek kan vaak helpen om dubbele waarden te verminderen of te elimineren.

Gebruik indien mogelijk serialisatieopties en kies wortels die geen duplicaten opleveren. De volgende code maakt bijvoorbeeld gebruik van Json.NET om een lijst met blogs te serialiseren die elk zijn gekoppeld aan de bijbehorende berichten:

using var context = new BlogsContext();

var blogs = await context.Blogs.Include(e => e.Posts).ToListAsync();

var serialized = JsonConvert.SerializeObject(
    blogs,
    new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, Formatting = Formatting.Indented });

Console.WriteLine(serialized);

De JSON die op basis van deze code is gegenereerd, is:

[
  {
    "Id": 1,
    "Name": ".NET Blog",
    "Summary": "Posts about .NET",
    "Posts": [
      {
        "Id": 1,
        "Title": "Announcing the Release of EF Core 5.0",
        "Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
        "BlogId": 1
      },
      {
        "Id": 2,
        "Title": "Announcing F# 5",
        "Content": "F# 5 is the latest version of F#, the functional programming language...",
        "BlogId": 1
      }
    ]
  },
  {
    "Id": 2,
    "Name": "Visual Studio Blog",
    "Summary": "Posts about Visual Studio",
    "Posts": [
      {
        "Id": 3,
        "Title": "Disassembly improvements for optimized managed debugging",
        "Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
        "BlogId": 2
      },
      {
        "Id": 4,
        "Title": "Database Profiling with Visual Studio",
        "Content": "Examine when database queries were executed and measure how long the take using...",
        "BlogId": 2
      }
    ]
  }
]

U ziet dat er geen dubbele blogs of berichten in de JSON staan. Dit betekent dat eenvoudige aanroepen Update werken om deze entiteiten in de database bij te werken:

public static async Task UpdateBlogsFromJson(string json)
{
    using var context = new BlogsContext();

    var blogs = JsonConvert.DeserializeObject<List<Blog>>(json);

    foreach (var blog in blogs)
    {
        context.Update(blog);
    }

    await context.SaveChangesAsync();
}

Duplicaten verwerken

De code in het vorige voorbeeld heeft elke blog geserialiseerd met de bijbehorende berichten. Als dit wordt gewijzigd om elk bericht te serialiseren met de bijbehorende blog, worden duplicaten geïntroduceerd in de geserialiseerde JSON. Voorbeeld:

using var context = new BlogsContext();

var posts = await context.Posts.Include(e => e.Blog).ToListAsync();

var serialized = JsonConvert.SerializeObject(
    posts,
    new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, Formatting = Formatting.Indented });

Console.WriteLine(serialized);

De geserialiseerde JSON ziet er nu als volgt uit:

[
  {
    "Id": 1,
    "Title": "Announcing the Release of EF Core 5.0",
    "Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
    "BlogId": 1,
    "Blog": {
      "Id": 1,
      "Name": ".NET Blog",
      "Summary": "Posts about .NET",
      "Posts": [
        {
          "Id": 2,
          "Title": "Announcing F# 5",
          "Content": "F# 5 is the latest version of F#, the functional programming language...",
          "BlogId": 1
        }
      ]
    }
  },
  {
    "Id": 2,
    "Title": "Announcing F# 5",
    "Content": "F# 5 is the latest version of F#, the functional programming language...",
    "BlogId": 1,
    "Blog": {
      "Id": 1,
      "Name": ".NET Blog",
      "Summary": "Posts about .NET",
      "Posts": [
        {
          "Id": 1,
          "Title": "Announcing the Release of EF Core 5.0",
          "Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
          "BlogId": 1
        }
      ]
    }
  },
  {
    "Id": 3,
    "Title": "Disassembly improvements for optimized managed debugging",
    "Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
    "BlogId": 2,
    "Blog": {
      "Id": 2,
      "Name": "Visual Studio Blog",
      "Summary": "Posts about Visual Studio",
      "Posts": [
        {
          "Id": 4,
          "Title": "Database Profiling with Visual Studio",
          "Content": "Examine when database queries were executed and measure how long the take using...",
          "BlogId": 2
        }
      ]
    }
  },
  {
    "Id": 4,
    "Title": "Database Profiling with Visual Studio",
    "Content": "Examine when database queries were executed and measure how long the take using...",
    "BlogId": 2,
    "Blog": {
      "Id": 2,
      "Name": "Visual Studio Blog",
      "Summary": "Posts about Visual Studio",
      "Posts": [
        {
          "Id": 3,
          "Title": "Disassembly improvements for optimized managed debugging",
          "Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
          "BlogId": 2
        }
      ]
    }
  }
]

U ziet dat de grafiek nu meerdere blogexemplaren met dezelfde sleutelwaarde bevat, evenals meerdere Post-exemplaren met dezelfde sleutelwaarde. Als u deze grafiek probeert bij te houden zoals we in het vorige voorbeeld hebben gedaan, wordt het volgende veroorzaakt:

System.InvalidOperationException: Het exemplaar van het entiteitstype 'Post' kan niet worden bijgehouden omdat er al een ander exemplaar met de sleutelwaarde {Id: 2} wordt bijgehouden. Wanneer u bestaande entiteiten koppelt, moet u ervoor zorgen dat slechts één entiteitsexemplaar met een bepaalde sleutelwaarde is gekoppeld.

We kunnen dit op twee manieren oplossen:

  • JSON-serialisatieopties gebruiken die verwijzingen behouden
  • Identiteitsomzetting uitvoeren terwijl de grafiek wordt bijgehouden

Verwijzingen behouden

Json.NET biedt de PreserveReferencesHandling mogelijkheid om dit te verwerken. Voorbeeld:

var serialized = JsonConvert.SerializeObject(
    posts,
    new JsonSerializerSettings
    {
        PreserveReferencesHandling = PreserveReferencesHandling.All, Formatting = Formatting.Indented
    });

De resulterende JSON ziet er nu als volgt uit:

{
  "$id": "1",
  "$values": [
    {
      "$id": "2",
      "Id": 1,
      "Title": "Announcing the Release of EF Core 5.0",
      "Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
      "BlogId": 1,
      "Blog": {
        "$id": "3",
        "Id": 1,
        "Name": ".NET Blog",
        "Summary": "Posts about .NET",
        "Posts": [
          {
            "$ref": "2"
          },
          {
            "$id": "4",
            "Id": 2,
            "Title": "Announcing F# 5",
            "Content": "F# 5 is the latest version of F#, the functional programming language...",
            "BlogId": 1,
            "Blog": {
              "$ref": "3"
            }
          }
        ]
      }
    },
    {
      "$ref": "4"
    },
    {
      "$id": "5",
      "Id": 3,
      "Title": "Disassembly improvements for optimized managed debugging",
      "Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
      "BlogId": 2,
      "Blog": {
        "$id": "6",
        "Id": 2,
        "Name": "Visual Studio Blog",
        "Summary": "Posts about Visual Studio",
        "Posts": [
          {
            "$ref": "5"
          },
          {
            "$id": "7",
            "Id": 4,
            "Title": "Database Profiling with Visual Studio",
            "Content": "Examine when database queries were executed and measure how long the take using...",
            "BlogId": 2,
            "Blog": {
              "$ref": "6"
            }
          }
        ]
      }
    },
    {
      "$ref": "7"
    }
  ]
}

U ziet dat deze JSON duplicaten heeft vervangen door verwijzingen zoals "$ref": "5" die verwijzen naar het al bestaande exemplaar in de grafiek. Deze grafiek kan opnieuw worden bijgehouden met behulp van de eenvoudige aanroepen naar Update, zoals hierboven wordt weergegeven.

De System.Text.Json ondersteuning in de .NET-basisklassebibliotheken (BCL) heeft een vergelijkbare optie die hetzelfde resultaat oplevert. Voorbeeld:

var serialized = JsonSerializer.Serialize(
    posts, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, WriteIndented = true });

Duplicaten oplossen

Als het niet mogelijk is om duplicaten in het serialisatieproces te elimineren, biedt ChangeTracker.TrackGraph een manier om hiermee om te gaan. TrackGraph werkt als Add, Attach en Update behalve dat er een callback voor elk entiteitsexemplaar wordt gegenereerd voordat deze wordt bijgehouden. Deze callback kan worden gebruikt om de entiteit bij te houden of te negeren. Voorbeeld:

public static async Task UpdatePostsFromJsonWithIdentityResolution(string json)
{
    using var context = new BlogsContext();

    var posts = JsonConvert.DeserializeObject<List<Post>>(json);

    foreach (var post in posts)
    {
        context.ChangeTracker.TrackGraph(
            post, node =>
            {
                var keyValue = node.Entry.Property("Id").CurrentValue;
                var entityType = node.Entry.Metadata;

                var existingEntity = node.Entry.Context.ChangeTracker.Entries()
                    .FirstOrDefault(
                        e => Equals(e.Metadata, entityType)
                             && Equals(e.Property("Id").CurrentValue, keyValue));

                if (existingEntity == null)
                {
                    Console.WriteLine($"Tracking {entityType.DisplayName()} entity with key value {keyValue}");

                    node.Entry.State = EntityState.Modified;
                }
                else
                {
                    Console.WriteLine($"Discarding duplicate {entityType.DisplayName()} entity with key value {keyValue}");
                }
            });
    }

    await context.SaveChangesAsync();
}

Voor elke entiteit in de grafiek gaat deze code als volgt te werk:

  • Het entiteitstype en de sleutelwaarde van de entiteit zoeken
  • De entiteit met deze sleutel opzoeken in de wijzigingstracker
    • Als de entiteit wordt gevonden, wordt er geen verdere actie ondernomen omdat de entiteit een duplicaat is
    • Als de entiteit niet wordt gevonden, wordt deze bijgehouden door de status in te stellen op Modified

Het resultaat van het uitvoeren van deze code is:

Tracking EntityType: Post entity with key value 1
Tracking EntityType: Blog entity with key value 1
Tracking EntityType: Post entity with key value 2
Discarding duplicate EntityType: Post entity with key value 2
Tracking EntityType: Post entity with key value 3
Tracking EntityType: Blog entity with key value 2
Tracking EntityType: Post entity with key value 4
Discarding duplicate EntityType: Post entity with key value 4

Belangrijk

Bij deze code wordt ervan uitgegaan dat alle duplicaten identiek zijn. Dit maakt het veilig om willekeurig een van de duplicaten te kiezen die moeten worden bijgehouden terwijl de anderen worden verwijderd. Als de duplicaten kunnen verschillen, moet de code bepalen hoe deze moet worden gebruikt en hoe eigenschaps- en navigatiewaarden samen moeten worden gecombineerd.

Opmerking

Voor het gemak gaat deze code ervan uit dat elke entiteit een primaire sleuteleigenschap heeft met de naam Id. Dit kan worden gecodificeerd in een abstracte basisklasse of interface. De primaire sleuteleigenschap of eigenschappen kunnen ook worden verkregen uit de IEntityType metagegevens, zodat deze code zou werken met elk type entiteit.

Het niet instellen van sleutelwaarden

Entiteitstypen worden vaak geconfigureerd voor het gebruik van automatisch gegenereerde sleutelwaarden. Dit is de standaardinstelling voor gehele getallen en GUID-eigenschappen van niet-samengestelde sleutels. Als het entiteitstype echter niet is geconfigureerd voor het gebruik van automatisch gegenereerde sleutelwaarden, moet er een expliciete sleutelwaarde worden ingesteld voordat de entiteit wordt bijgehouden. Gebruik bijvoorbeeld het volgende entiteitstype:

public class Pet
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }

    public string Name { get; set; }
}

Houd rekening met code die probeert twee nieuwe entiteitsexemplaren bij te houden zonder sleutelwaarden in te stellen:

using var context = new BlogsContext();

context.Add(new Pet { Name = "Smokey" });

try
{
    context.Add(new Pet { Name = "Clippy" }); // This will throw
}
catch (Exception e)
{
    Console.WriteLine($"{e.GetType().FullName}: {e.Message}");
}

Met deze code wordt het volgende gegooid:

System.InvalidOperationException: Het exemplaar van het entiteitstype Pet kan niet worden bijgehouden omdat er al een ander exemplaar met de sleutelwaarde {Id: 0} wordt bijgehouden. Wanneer u bestaande entiteiten koppelt, moet u ervoor zorgen dat slechts één entiteitsexemplaar met een bepaalde sleutelwaarde is gekoppeld.

De oplossing hiervoor is het expliciet instellen van sleutelwaarden of het configureren van de sleuteleigenschap voor het gebruik van gegenereerde sleutelwaarden. Zie Gegenereerde waarden voor meer informatie.

Het overmatig gebruik van een enkele DbContext-instantie

DbContext is ontworpen om een kortstondige eenheid van werk te vertegenwoordigen, zoals beschreven in DbContext Initialization and Configuration, en verder uitgewerkt in Wijzigingen bijhouden in EF Core. Door deze richtlijnen niet te volgen, kunt u eenvoudig situaties tegenkomen waarin een poging wordt gedaan om meerdere exemplaren van dezelfde entiteit bij te houden. Enkele typische voorbeelden:

  • Gebruik hetzelfde DbContext-exemplaar om de teststatus in te stellen en voer vervolgens de test uit. Dit leidt er vaak toe dat in DbContext nog steeds één entiteitsexemplaar wordt bijgehouden uit de testinstallatie, terwijl vervolgens wordt geprobeerd een nieuw exemplaar bij te voegen in de test. Gebruik in plaats daarvan een ander DbContext-exemplaar voor het instellen van de teststatus en de juiste testcode.
  • Een gedeeld DbContext-exemplaar gebruiken in een opslagplaats of vergelijkbare code. Zorg er in plaats daarvan voor dat uw opslagplaats één DbContext-exemplaar gebruikt voor elke werkeenheid.

Identiteitsresolutie en zoekopdrachten

Identiteitsomzetting vindt automatisch plaats wanneer entiteiten worden bijgehouden vanuit een query. Dit betekent dat als een entiteitsexemplaar met een bepaalde sleutelwaarde al wordt bijgehouden, dit bestaande bijgehouden exemplaar wordt gebruikt in plaats van een nieuw exemplaar te maken. Dit heeft een belangrijk gevolg: als de gegevens in de database zijn gewijzigd, wordt dit niet weergegeven in de resultaten van de query. Dit is een goede reden om een nieuw DbContext-exemplaar te gebruiken voor elke werkeenheid, zoals beschreven in DbContext Initialization and Configuration, en verder uitgewerkt in Wijzigingen bijhouden in EF Core.

Belangrijk

Het is belangrijk om te begrijpen dat EF Core altijd een LINQ-query uitvoert op een DbSet op basis van de database en alleen resultaten retourneert op basis van wat zich in de database bevindt. Als de geretourneerde entiteiten echter al worden bijgehouden voor een traceringsquery, worden de bijgehouden exemplaren gebruikt in plaats van exemplaren te maken van de gegevens in de database.

Reload() of GetDatabaseValues() kan worden gebruikt wanneer bijgehouden entiteiten moeten worden vernieuwd met de meest recente gegevens uit de database. Zie Accessing Tracked Entities voor meer informatie.

In tegenstelling tot het bijhouden van query's, voeren geen-tracering query's geen identiteit resolutie uit. Dit betekent dat query's zonder tracering duplicaten kunnen teruggeven, net zoals in het eerder beschreven JSON-serialisatiescenario. Dit is meestal geen probleem als de queryresultaten worden geserialiseerd en naar de client worden verzonden.

Aanbeveling

Voer niet regelmatig een query zonder tracering uit en koppel de geretourneerde entiteiten vervolgens aan dezelfde context. Dit zal zowel langzamer als moeilijker zijn om goed te doen dan het gebruik van een zoekquery.

Query's zonder tracering voeren geen identiteitsomzetting uit, omdat dit van invloed is op de prestaties van het streamen van een groot aantal entiteiten uit een query. Dit komt doordat identiteitsresolutie elke geretourneerde instantie moet bijhouden, zodat deze kan worden hergebruikt in plaats van later een duplicaat te maken.

Queries zonder tracering kunnen worden gedwongen om identiteitsresolutie uit te voeren met behulp van AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>). De query houdt vervolgens geretourneerde exemplaren bij (zonder ze op de normale manier bij te houden) en zorgt ervoor dat er geen duplicaten worden gemaakt in de queryresultaten.

Objectgelijkheid overschrijven

EF Core maakt gebruik van referentie gelijkheid bij het vergelijken van entiteitsexemplaren. Dit is het geval, zelfs wanneer entiteitstypen Object.Equals(Object) vervangen of op een andere manier de gelijkheid van objecten wijzigen. Er is echter één plaats waar het negeren van gelijkheid invloed kan hebben op EF Core-gedrag: wanneer verzamelingsnavigatie de overschreven gelijkheid gebruikt in plaats van verwijzingsgelijkheid, en dus meerdere exemplaren als hetzelfde rapporteren.

Daarom wordt aanbevolen dat het overschrijven van gelijkheid van entiteiten moet worden vermeden. Als het wordt gebruikt, moet u verzamelingsnavigaties maken die gelijkheid van referenties afdwingen. Maak bijvoorbeeld een gelijkheidsvergelijker die gebruikmaakt van verwijzingsgelijkheid.

public sealed class ReferenceEqualityComparer : IEqualityComparer<object>
{
    private ReferenceEqualityComparer()
    {
    }

    public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer();

    bool IEqualityComparer<object>.Equals(object x, object y) => x == y;

    int IEqualityComparer<object>.GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
}

(Vanaf .NET 5 is dit opgenomen in de BCL als ReferenceEqualityComparer.)

Deze vergelijking kan vervolgens worden gebruikt bij het maken van verzamelingsnavigatie. Voorbeeld:

public ICollection<Order> Orders { get; set; }
    = new HashSet<Order>(ReferenceEqualityComparer.Instance);

Sleuteleigenschappen vergelijken

Naast gelijkheidsvergelijkingen moeten ook sleutelwaarden worden gerangschikt. Dit is belangrijk voor het voorkomen van impasses bij het bijwerken van meerdere entiteiten in één aanroep naar SaveChanges. Alle typen die worden gebruikt voor primaire, alternatieve of refererende sleuteleigenschappen, evenals typen die worden gebruikt voor unieke indexen, moeten implementeren IComparable<T> en IEquatable<T>. Typen die normaal gesproken worden gebruikt als sleutels (int, Guid, tekenreeks, enzovoort) ondersteunen deze interfaces al. Aangepaste sleuteltypen kunnen deze interfaces toevoegen.