Udostępnij za pośrednictwem


Rozpoznawanie tożsamości w programie EF Core

Obiekt DbContext może śledzić tylko jedno wystąpienie jednostki z dowolną każdą wartością klucza podstawowego. Oznacza to, że wiele wystąpień jednostki o tej samej wartości klucza musi zostać rozpoznanych w jednym wystąpieniu. Jest to nazywane "rozpoznawaniem tożsamości". Rozpoznawanie tożsamości zapewnia, że program Entity Framework Core (EF Core) śledzi spójny graf bez niejednoznaczności dotyczących relacji lub wartości właściwości jednostek.

Napiwek

W tym dokumencie przyjęto założenie, że stany jednostki i podstawy śledzenia zmian platformy EF Core są zrozumiałe. Aby uzyskać więcej informacji na temat tych tematów, zobacz Change Tracking in EF Core (Śledzenie zmian w programie EF Core ).

Napiwek

Możesz uruchomić i debugować cały kod podany w tym dokumencie, pobierając przykładowy kod z serwisu GitHub.

Wprowadzenie

Poniższy kod wykonuje zapytania dotyczące jednostki, a następnie próbuje dołączyć inne wystąpienie z tą samą wartością klucza podstawowego:

using var context = new BlogsContext();

var blogA = context.Blogs.Single(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}");
}

Uruchomienie tego kodu powoduje następujący wyjątek:

System.InvalidOperationException: Nie można śledzić wystąpienia typu jednostki "Blog", ponieważ jest już śledzone inne wystąpienie o wartości klucza {Id: 1}. Podczas dołączania istniejących jednostek upewnij się, że dołączono tylko jedno wystąpienie jednostki z daną wartością klucza.

Program EF Core wymaga jednego wystąpienia, ponieważ:

  • Wartości właściwości mogą być różne między wieloma wystąpieniami. Podczas aktualizowania bazy danych program EF Core musi wiedzieć, które wartości właściwości mają być używane.
  • Relacje z innymi jednostkami mogą być różne między wieloma wystąpieniami. Na przykład "blogA" może być powiązany z inną kolekcją wpisów niż "blogB".

Powyższy wyjątek występuje często w następujących sytuacjach:

  • Podczas próby zaktualizowania jednostki
  • Podczas próby śledzenia serializowanego grafu jednostek
  • Jeśli nie można ustawić wartości klucza, która nie jest generowana automatycznie
  • Podczas ponownego wykonywania wystąpienia DbContext dla wielu jednostek pracy

Każda z tych sytuacji została omówiona w poniższych sekcjach.

Aktualizowanie jednostki

Istnieje kilka różnych podejść do aktualizowania jednostki przy użyciu nowych wartości, jak opisano w temacie Śledzenie zmian w programie EF Core i Jawne śledzenie jednostek. Te podejścia przedstawiono poniżej w kontekście rozpoznawania tożsamości. Ważnym punktem do zauważenia jest to, że każde z podejść używa zapytania lub wywołania do jednego z Update lub Attach, ale nigdy nie obu tych metod.

Aktualizacja wywołań

Często jednostka do aktualizacji nie pochodzi z zapytania w obiekcie DbContext, którego będziemy używać dla funkcji SaveChanges. Na przykład w aplikacji internetowej wystąpienie jednostki może zostać utworzone na podstawie informacji w żądaniu POST. Najprostszym sposobem obsługi tej metody jest użycie metody DbContext.Update lub DbSet<TEntity>.Update. Na przykład:

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

    context.Update(blog);

    context.SaveChanges();
}

W takim przypadku:

  • Tworzone jest tylko jedno wystąpienie jednostki.
  • Wystąpienie jednostki nie jest odpytywane z bazy danych w ramach tworzenia aktualizacji.
  • Wszystkie wartości właściwości zostaną zaktualizowane w bazie danych, niezależnie od tego, czy zostały one rzeczywiście zmienione, czy nie.
  • Jedna runda bazy danych jest wykonana.

Następnie zapytanie zastosuj zmiany

Zazwyczaj nie wiadomo, które wartości właściwości zostały rzeczywiście zmienione podczas tworzenia jednostki na podstawie informacji w żądaniu POST lub podobnej. Często wystarczy zaktualizować wszystkie wartości w bazie danych, tak jak w poprzednim przykładzie. Jeśli jednak aplikacja obsługuje wiele jednostek i tylko niewielka liczba tych jednostek ma rzeczywiste zmiany, może być przydatne ograniczenie wysłanych aktualizacji. Można to osiągnąć, wykonując zapytanie, aby śledzić jednostki, ponieważ obecnie istnieją w bazie danych, a następnie stosując zmiany do tych śledzonych jednostek. Na przykład:

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

    var trackedBlog = context.Blogs.Find(blog.Id);

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

    context.SaveChanges();
}

W takim przypadku:

  • Śledzone jest tylko jedno wystąpienie jednostki; ten, który jest zwracany z bazy danych przez Find zapytanie.
  • Update, Attachitp. nieużywane.
  • W bazie danych są aktualizowane tylko wartości właściwości, które zostały rzeczywiście zmienione.
  • Są wykonywane dwie rundy bazy danych.

Program EF Core ma niektórych pomocników do przenoszenia wartości właściwości w następujący sposób. Na przykład PropertyValues.SetValues skopiuje wszystkie wartości z danego obiektu i ustawi je na śledzonym obiekcie:

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

    var trackedBlog = context.Blogs.Find(blog.Id);

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

    context.SaveChanges();
}

SetValues akceptuje różne typy obiektów, w tym obiekty transferu danych (DTO) z nazwami właściwości pasujących do właściwości typu jednostki. Na przykład:

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

    var trackedBlog = context.Blogs.Find(dto.Id);

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

    context.SaveChanges();
}

Lub słownik z wpisami nazwy/wartości dla wartości właściwości:

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

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

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

    context.SaveChanges();
}

Aby uzyskać więcej informacji na temat pracy z wartościami właściwości, zobacz Uzyskiwanie dostępu do śledzonych jednostek .

Użyj oryginalnych wartości

Do tej pory każde podejście wykonało zapytanie przed wprowadzeniem aktualizacji lub zaktualizowało wszystkie wartości właściwości niezależnie od tego, czy uległy zmianie. Aby zaktualizować tylko wartości, które uległy zmianie bez wykonywania zapytań w ramach aktualizacji, wymaga określonych informacji o tym, które wartości właściwości uległy zmianie. Typowym sposobem uzyskania tych informacji jest wysłanie z powrotem bieżących i oryginalnych wartości w wpisie HTTP lub podobnych. Na przykład:

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

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

    context.SaveChanges();
}

W tym kodzie jednostka z zmodyfikowanymi wartościami jest najpierw dołączona. Powoduje to śledzenie jednostki w Unchanged stanie przez program EF Core, czyli bez wartości właściwości oznaczonych jako zmodyfikowane. Słownik oryginalnych wartości jest następnie stosowany do tej śledzonej jednostki. Spowoduje to oznaczenie jako zmodyfikowanych właściwości z różnymi bieżącymi i oryginalnymi wartościami. Właściwości, które mają te same bieżące i oryginalne wartości, nie zostaną oznaczone jako zmodyfikowane.

W takim przypadku:

  • Śledzone jest tylko jedno wystąpienie jednostki przy użyciu funkcji Dołączanie.
  • Wystąpienie jednostki nie jest odpytywane z bazy danych w ramach tworzenia aktualizacji.
  • Zastosowanie oryginalnych wartości gwarantuje, że w bazie danych są aktualizowane tylko wartości właściwości, które rzeczywiście uległy zmianie.
  • Jedna runda bazy danych jest wykonana.

Podobnie jak w przypadku przykładów w poprzedniej sekcji oryginalne wartości nie muszą być przekazywane jako słownik; wystąpienie jednostki lub DTO również będzie działać.

Napiwek

Chociaż takie podejście ma atrakcyjne cechy, wymaga wysyłania oryginalnych wartości jednostki do i z klienta internetowego. Starannie zastanów się, czy ta dodatkowa złożoność jest warta korzyści; w przypadku wielu zastosowań jednym z prostszych podejść jest bardziej pragmatyczne.

Dołączanie serializowanego grafu

Program EF Core współpracuje z grafami jednostek połączonych za pośrednictwem kluczy obcych i właściwości nawigacji, zgodnie z opisem w temacie Zmienianie kluczy obcych i nawigacji. Jeśli te grafy są tworzone poza programem EF Core, na przykład z pliku JSON, mogą mieć wiele wystąpień tej samej jednostki. Te duplikaty należy rozpoznać w pojedynczych wystąpieniach, zanim będzie można śledzić graf.

Grafy bez duplikatów

Przed kontynuowaniem należy pamiętać, że:

  • Serializatory często mają opcje obsługi pętli i zduplikowanych wystąpień na grafie.
  • Wybór obiektu używanego jako główny grafu często może pomóc zmniejszyć lub usunąć duplikaty.

Jeśli to możliwe, użyj opcji serializacji i wybierz korzenie, które nie powodują duplikatów. Na przykład poniższy kod używa Json.NET do serializacji listy blogów z powiązanymi wpisami:

using var context = new BlogsContext();

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

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

Console.WriteLine(serialized);

Kod JSON wygenerowany na podstawie tego kodu to:

[
  {
    "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
      }
    ]
  }
]

Zwróć uwagę, że w formacie JSON nie ma zduplikowanych blogów ani wpisów. Oznacza to, że proste wywołania Update będą działać w celu zaktualizowania tych jednostek w bazie danych:

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

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

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

    context.SaveChanges();
}

Obsługa duplikatów

Kod w poprzednim przykładzie serializował każdy blog ze skojarzonymi wpisami. Jeśli zostanie to zmienione tak, aby serializować każdy wpis ze skojarzonym z nim blogiem, duplikaty są wprowadzane do serializowanego kodu JSON. Na przykład:

using var context = new BlogsContext();

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

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

Console.WriteLine(serialized);

Serializowany kod JSON wygląda teraz następująco:

[
  {
    "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
        }
      ]
    }
  }
]

Zwróć uwagę, że wykres zawiera teraz wiele wystąpień blogu z tą samą wartością klucza, a także wiele wystąpień Post z tą samą wartością klucza. Próba śledzenia tego grafu, tak jak w poprzednim przykładzie, spowoduje zgłoszenie:

System.InvalidOperationException: Nie można śledzić wystąpienia typu jednostki "Post", ponieważ jest już śledzone inne wystąpienie o wartości klucza {Id: 2}. Podczas dołączania istniejących jednostek upewnij się, że dołączono tylko jedno wystąpienie jednostki z daną wartością klucza.

Możemy rozwiązać ten problem na dwa sposoby:

  • Użyj opcji serializacji JSON, które zachowują odwołania
  • Wykonywanie rozpoznawania tożsamości podczas śledzenia grafu

Zachowywanie odwołań

Json.NET zapewnia PreserveReferencesHandling opcję obsługi tej opcji. Na przykład:

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

Wynikowy kod JSON wygląda teraz następująco:

{
  "$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"
    }
  ]
}

Zwróć uwagę, że ten kod JSON zamienił duplikaty na odwołania podobne "$ref": "5" do już istniejącego wystąpienia na grafie. Ten graf można ponownie śledzić przy użyciu prostych wywołań metody Update, jak pokazano powyżej.

Obsługa System.Text.Json bibliotek klas bazowych .NET (BCL) ma podobną opcję, która generuje ten sam wynik. Na przykład:

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

Rozwiązywanie duplikatów

Jeśli nie można wyeliminować duplikatów w procesie serializacji, można ChangeTracker.TrackGraph to zrobić. Funkcja TrackGraph działa jak Add, Attach i Update z tą różnicą, że generuje wywołanie zwrotne dla każdego wystąpienia jednostki przed jego śledzeniem. Tego wywołania zwrotnego można użyć do śledzenia jednostki lub ignorowania jej. Na przykład:

public static void 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}");
                }
            });
    }

    context.SaveChanges();
}

Dla każdej jednostki na grafie ten kod będzie następujący:

  • Znajdowanie typu jednostki i wartości klucza jednostki
  • Wyszukiwanie jednostki za pomocą tego klucza w monitorze zmian
    • Jeśli jednostka zostanie znaleziona, nie zostanie podjęta żadna dalsza akcja, ponieważ jednostka jest duplikatem
    • Jeśli jednostka nie zostanie znaleziona, zostanie ona śledzona przez ustawienie stanu na Modified

Dane wyjściowe z uruchamiania tego kodu to:

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

Ważne

Ten kod zakłada, że wszystkie duplikaty są identyczne. Dzięki temu można bezpiecznie dowolnie wybrać jeden z duplikatów do śledzenia podczas odrzucania innych. Jeśli duplikaty mogą się różnić, kod będzie musiał zdecydować, jak określić, którego z nich użyć, oraz jak połączyć wartości właściwości i nawigacji.

Uwaga

Dla uproszczenia ten kod zakłada, że każda jednostka ma właściwość klucza podstawowego o nazwie Id. Może to być skodyfikowane w abstrakcyjnej klasie bazowej lub interfejsie. Alternatywnie można uzyskać właściwość lub właściwości klucza podstawowego z IEntityType metadanych, tak aby ten kod działał z dowolnym typem jednostki.

Nie można ustawić wartości kluczy

Typy jednostek są często konfigurowane do używania automatycznie generowanych wartości kluczy. Jest to wartość domyślna dla właściwości liczb całkowitych i identyfikatorów GUID kluczy innych niż złożone. Jeśli jednak typ jednostki nie jest skonfigurowany do używania automatycznie generowanych wartości kluczy, należy ustawić jawną wartość klucza przed śledzeniem jednostki. Na przykład przy użyciu następującego typu jednostki:

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

    public string Name { get; set; }
}

Rozważ kod, który próbuje śledzić dwa nowe wystąpienia jednostki bez ustawiania wartości klucza:

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

Ten kod będzie zgłaszany:

System.InvalidOperationException: Nie można śledzić wystąpienia typu jednostki "Pet", ponieważ jest już śledzone inne wystąpienie o wartości klucza {Id: 0}. Podczas dołączania istniejących jednostek upewnij się, że dołączono tylko jedno wystąpienie jednostki z daną wartością klucza.

Poprawka tego problemu polega na jawnym ustawieniu wartości kluczy lub skonfigurowaniu właściwości klucza do używania wygenerowanych wartości klucza. Aby uzyskać więcej informacji, zobacz Wygenerowane wartości .

Zastępowanie pojedynczego wystąpienia DbContext

DbContext jest przeznaczony do reprezentowania krótkotrwałej jednostki pracy, zgodnie z opisem w temacie DbContext Initialization and Configuration (Inicjowanie i konfiguracja obiektu DbContext) oraz opracowany w temacie Change Tracking in EF Core (Śledzenie zmian w programie EF Core). Nie postępując zgodnie z tymi wskazówkami, można łatwo napotkać sytuacje, w których podjęto próbę śledzenia wielu wystąpień tej samej jednostki. Typowe przykłady:

  • Użyj tego samego wystąpienia DbContext, aby skonfigurować stan testu, a następnie wykonać test. Często powoduje to, że narzędzie DbContext nadal śledzi jedno wystąpienie jednostki z konfiguracji testowej, a następnie próbuje dołączyć nowe wystąpienie w odpowiednim teście. Zamiast tego należy użyć innego wystąpienia DbContext do skonfigurowania stanu testu i prawidłowego kodu testowego.
  • Używanie udostępnionego wystąpienia DbContext w repozytorium lub podobnym kodzie. Zamiast tego upewnij się, że repozytorium używa pojedynczego wystąpienia dbContext dla każdej jednostki pracy.

Rozpoznawanie tożsamości i zapytania

Rozpoznawanie tożsamości odbywa się automatycznie, gdy jednostki są śledzone z zapytania. Oznacza to, że jeśli wystąpienie jednostki z daną wartością klucza jest już śledzone, to istniejące śledzone wystąpienie jest używane zamiast tworzenia nowego wystąpienia. Ma to ważną konsekwencję: jeśli dane zmieniły się w bazie danych, nie zostaną one odzwierciedlone w wynikach zapytania. Jest to dobry powód, aby użyć nowego wystąpienia DbContext dla każdej jednostki pracy, zgodnie z opisem w temacie DbContext Initialization and Configuration (Inicjowanie i konfiguracja obiektu DbContext) oraz opisano je w temacie Change Tracking in EF Core (Śledzenie zmian w programie EF Core).

Ważne

Ważne jest, aby zrozumieć, że program EF Core zawsze wykonuje zapytanie LINQ w bazie danych DbSet i zwraca tylko wyniki na podstawie tego, co znajduje się w bazie danych. Jednak w przypadku zapytania śledzenia, jeśli zwrócone jednostki są już śledzone, śledzone wystąpienia są używane zamiast tworzyć wystąpienia z danych w bazie danych.

Reload() lub GetDatabaseValues() może być używany, gdy śledzone jednostki muszą być odświeżane przy użyciu najnowszych danych z bazy danych. Aby uzyskać więcej informacji, zobacz Uzyskiwanie dostępu do śledzonych jednostek .

W przeciwieństwie do śledzenia zapytań zapytania bez śledzenia nie wykonują rozpoznawania tożsamości. Oznacza to, że zapytania bez śledzenia mogą zwracać duplikaty tak samo jak w opisanym wcześniej przypadku serializacji JSON. Zwykle nie jest to problem, jeśli wyniki zapytania będą serializowane i wysyłane do klienta.

Napiwek

Nie wykonuj rutynowo zapytania bez śledzenia, a następnie dołączaj zwrócone jednostki do tego samego kontekstu. Będzie to zarówno wolniejsze, jak i trudniejsze niż użycie zapytania śledzenia.

Zapytania bez śledzenia nie wykonują rozpoznawania tożsamości, ponieważ ma to wpływ na wydajność przesyłania strumieniowego dużej liczby jednostek z zapytania. Dzieje się tak, ponieważ rozpoznawanie tożsamości wymaga śledzenia zwracanych wystąpień, aby można było go użyć zamiast później tworzyć duplikaty.

Nie można wymusić śledzenia zapytań w celu przeprowadzenia rozpoznawania tożsamości przy użyciu polecenia AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>). Następnie zapytanie będzie śledzić zwracane wystąpienia (bez śledzenia ich w normalny sposób) i upewnić się, że żadne duplikaty nie są tworzone w wynikach zapytania.

Zastępowanie równości obiektów

Program EF Core używa równości referencyjnej podczas porównywania wystąpień jednostek. Jest to przypadek, nawet jeśli typy jednostek zastępują Object.Equals(Object) lub w inny sposób zmieniają równość obiektów. Istnieje jednak jedno miejsce, w którym zastępowanie równości może mieć wpływ na zachowanie platformy EF Core: gdy nawigacje kolekcji używają zastępowanej równości zamiast równości odwołań, a tym samym zgłaszać wiele wystąpień jako takie same.

Z tego powodu zaleca się unikanie zastępowania równości jednostek. Jeśli jest używana, pamiętaj, aby utworzyć nawigacje kolekcji, które wymuszają równość odwołań. Na przykład utwórz porównanie równości, które używa równości równości:

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

(Począwszy od platformy .NET 5, jest to uwzględnione w liście BCL jako ReferenceEqualityComparer.)

Tego porównania można następnie użyć podczas tworzenia nawigacji kolekcji. Na przykład:

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

Porównywanie właściwości klucza

Oprócz porównań równości należy również porządkować wartości kluczy. Jest to ważne, aby uniknąć zakleszczenia podczas aktualizowania wielu jednostek w jednym wywołaniu funkcji SaveChanges. Wszystkie typy używane dla właściwości klucza podstawowego, alternatywnego lub obcego, a także używane do indeksów unikatowych, muszą implementować IComparable<T> i IEquatable<T>. Typy zwykle używane jako klucze (int, Guid, string itp.) obsługują już te interfejsy. Niestandardowe typy kluczy mogą dodawać te interfejsy.