Resolusi Identitas dalam EF Core

DbContext hanya dapat melacak satu instance entitas dengan nilai kunci primer tertentu. Ini berarti beberapa instans entitas dengan nilai kunci yang sama harus dikonsolidasikan menjadi satu instans. Ini disebut "resolusi identitas". Resolusi identitas memastikan Entity Framework Core (EF Core) melacak grafik yang konsisten tanpa ambiguitas tentang hubungan atau nilai properti entitas.

Petunjuk / Saran

Dokumen ini mengasumsikan bahwa status entitas dan dasar-dasar pelacakan perubahan EF Core dipahami. Lihat Pelacakan Perubahan di EF Core untuk informasi selengkapnya tentang topik ini.

Petunjuk / Saran

Anda dapat menjalankan dan men-debug ke semua kode dalam dokumen ini dengan mengunduh kode sampel dari GitHub.

Pendahuluan

Kueri kode berikut untuk entitas lalu mencoba melampirkan instans yang berbeda dengan nilai kunci primer yang sama:

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

Menjalankan kode ini menghasilkan pengecualian berikut:

System.InvalidOperationException: Instans jenis entitas 'Blog' tidak dapat dilacak karena instans lain dengan nilai kunci '{Id: 1}' sudah dilacak. Saat melampirkan entitas yang sudah ada, pastikan hanya satu instans entitas dengan nilai kunci tertentu yang terpasang.

EF Core memerlukan satu instans karena:

  • Nilai properti mungkin berbeda antara beberapa instans. Saat memperbarui database, EF Core perlu mengetahui nilai properti mana yang akan digunakan.
  • Hubungan dengan entitas lain mungkin berbeda antara beberapa instans. Misalnya, "blogA" mungkin terkait dengan koleksi posting yang berbeda dari "blogB".

Pengecualian di atas biasanya ditemui dalam situasi ini:

  • Saat mencoba memperbarui entitas
  • Saat mencoba melacak graf entitas yang diserialkan
  • Saat gagal mengatur nilai kunci yang tidak dibuat secara otomatis
  • Saat menggunakan kembali instance DbContext dalam sejumlah unit kerja

Masing-masing situasi ini dibahas di bagian berikut.

Memperbarui entitas

Ada beberapa pendekatan berbeda untuk memperbarui entitas dengan nilai baru, seperti yang tercakup dalam Pelacakan Perubahan di EF Core dan Entitas Pelacakan Eksplisit. Pendekatan ini diuraikan di bawah ini dalam konteks resolusi identitas. Penting diperhatikan bahwa setiap pendekatan menggunakan kueri atau panggilan ke salah satu dari Update atau Attach, tetapi tidak pernah keduanya.

Pembaruan Panggilan

Seringkali entitas yang akan diperbarui tidak berasal dari kueri di DbContext yang akan kita gunakan untuk SaveChanges. Misalnya, dalam aplikasi web, instans entitas dapat dibuat dari informasi dalam permintaan POST. Cara paling sederhana untuk menangani ini adalah dengan menggunakan DbContext.Update atau DbSet<TEntity>.Update. Contohnya:

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

    context.Update(blog);

    await context.SaveChangesAsync();
}

Dalam hal ini:

  • Hanya satu instance entitas yang dibuat.
  • Instans entitas tidak dikueri dari database sebagai bagian dari membuat pembaruan.
  • Semua nilai properti akan diperbarui dalam database, terlepas dari apakah mereka telah benar-benar berubah atau tidak.
  • Satu kali akses database dilakukan.

Kueri lalu terapkan perubahan

Biasanya tidak diketahui nilai properti mana yang sebenarnya telah diubah ketika entitas dibuat dari informasi dalam permintaan POST atau sejenisnya. Seringkali baik-baik saja untuk hanya memperbarui semua nilai dalam database, seperti yang kita lakukan dalam contoh sebelumnya. Namun, jika aplikasi menangani banyak entitas dan hanya sejumlah kecil yang memiliki perubahan aktual, maka mungkin berguna untuk membatasi pembaruan yang dikirim. Ini dapat dicapai dengan menjalankan kueri untuk melacak entitas seperti yang saat ini ada di database, lalu menerapkan perubahan pada entitas yang dilacak ini. Contohnya:

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

Dalam hal ini:

  • Hanya satu entitas instans yang dilacak; yang dikembalikan oleh kueri Find dari database.
  • Update, Attach, dll. tidak digunakan.
  • Hanya nilai properti yang benar-benar telah diubah yang diperbarui dalam database.
  • Dua kali kunjungan ke database dilakukan.

EF Core memiliki beberapa pembantu untuk mentransfer nilai properti seperti ini. Misalnya, PropertyValues.SetValues akan menyalin semua nilai dari objek yang diberikan dan mengaturnya pada objek yang dilacak:

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 menerima berbagai jenis objek, termasuk objek transfer data (DTO) dengan nama properti yang cocok dengan properti jenis entitas. Contohnya:

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

Atau kamus dengan entri nama/nilai untuk nilai properti:

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

Lihat Mengakses entitas terlacak untuk informasi selengkapnya tentang bekerja dengan nilai properti seperti ini.

Menggunakan nilai asli

Sejauh ini setiap pendekatan telah menjalankan kueri sebelum membuat pembaruan, atau memperbarui semua nilai properti terlepas dari apakah mereka telah berubah atau tidak. Untuk memperbarui hanya nilai yang berubah tanpa melakukan kueri sebagai bagian dari pembaruan, diperlukan informasi spesifik mengenai nilai properti mana yang telah berubah. Cara umum untuk mendapatkan informasi ini adalah dengan mengirim kembali nilai saat ini dan asli di Pos HTTP atau yang serupa. Contohnya:

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

Dalam kode ini entitas dengan nilai yang dimodifikasi pertama kali dilampirkan. Ini menyebabkan EF Core melacak entitas dalam Unchanged status; yaitu, tanpa nilai properti yang ditandai sebagai dimodifikasi. Kamus nilai asli kemudian diterapkan ke entitas terlacak ini. Ini akan menandai properti yang dimodifikasi dengan nilai saat ini dan asli yang berbeda. Properti yang memiliki nilai saat ini dan asli yang sama tidak akan ditandai sebagai dimodifikasi.

Dalam hal ini:

  • Hanya satu instance entitas yang dilacak, menggunakan Attach.
  • Instans entitas tidak dikueri dari database sebagai bagian dari membuat pembaruan.
  • Menerapkan nilai asli memastikan bahwa hanya nilai properti yang benar-benar berubah yang diperbarui dalam database.
  • Satu perjalanan pulang-pergi database dilakukan.

Seperti contoh di bagian sebelumnya, nilai asli tidak harus diteruskan sebagai struktur data kamus; instans entitas atau DTO juga dapat digunakan.

Petunjuk / Saran

Meskipun pendekatan ini memiliki karakteristik yang menarik, pendekatan ini memerlukan pengiriman nilai asli entitas ke dan dari klien web. Pertimbangkan dengan cermat apakah kompleksitas ekstra ini sepadan dengan manfaatnya; untuk banyak aplikasi salah satu pendekatan yang lebih sederhana adalah lebih pragmatis.

Melampirkan graf terserialisasi

EF Core bekerja dengan grafik entitas yang terhubung melalui kunci asing dan properti navigasi, seperti yang dijelaskan dalam Mengubah Kunci Asing dan Navigasi. Jika grafik ini dibuat di luar EF Core menggunakan, misalnya, dari file JSON, grafik tersebut dapat memiliki beberapa instans dari entitas yang sama. Duplikat ini perlu diselesaikan menjadi satu instans sebelum grafik dapat dilacak.

Grafik tanpa duplikat

Sebelum melangkah lebih jauh, penting untuk mengenali bahwa:

  • Serialiser sering memiliki opsi untuk menangani perulangan dan instans duplikat dalam graf.
  • Pilihan objek yang digunakan sebagai akar grafik sering kali dapat membantu mengurangi atau menghapus duplikat.

Jika memungkinkan, gunakan opsi serialisasi dan pilih akar yang tidak menghasilkan duplikat. Misalnya, kode berikut menggunakan Json.NET untuk membuat serial daftar blog masing-masing dengan posting terkait:

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

JSON yang dihasilkan dari kode ini adalah:

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

Perhatikan bahwa tidak ada blog atau posting duplikat di JSON. Ini berarti bahwa panggilan sederhana ke Update akan berfungsi untuk memperbarui entitas ini dalam database:

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

Menangani duplikat

Kode dalam contoh sebelumnya mensirialkan setiap blog dengan posting terkait. Jika ini diubah untuk menserialisasikan setiap posting dengan blog terkait, maka duplikat diperkenalkan ke dalam JSON berseri. Contohnya:

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

JSON yang diserialisasikan sekarang terlihat seperti ini:

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

Perhatikan bahwa grafik sekarang menyertakan beberapa instans Blog dengan nilai kunci yang sama, serta beberapa instans Post dengan nilai kunci yang sama. Mencoba melacak grafik ini seperti yang kami lakukan dalam contoh sebelumnya akan melemparkan:

System.InvalidOperationException: Instans jenis entitas 'Post' tidak dapat dilacak karena instans lain dengan nilai kunci '{Id: 2}' sudah dilacak. Saat melampirkan entitas yang sudah ada, pastikan hanya satu instans entitas dengan nilai kunci tertentu yang terpasang.

Kita dapat memperbaikinya dengan dua cara:

  • Menggunakan opsi serialisasi JSON yang mempertahankan referensi
  • Melakukan resolusi identitas saat grafik sedang dilacak

Mempertahankan referensi

Json.NET menyediakan PreserveReferencesHandling opsi untuk menangani ini. Contohnya:

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

JSON yang dihasilkan sekarang terlihat seperti ini:

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

Perhatikan bahwa JSON ini telah mengganti duplikat dengan referensi seperti "$ref": "5" yang mengacu pada instans yang sudah ada dalam graf. Grafik ini dapat dilacak lagi menggunakan panggilan sederhana ke Update, seperti yang ditunjukkan di atas.

System.Text.Json Dukungan dalam pustaka kelas dasar .NET (BCL) memiliki opsi serupa yang menghasilkan hasil yang sama. Contohnya:

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

Mengatasi duplikat

Jika tidak mungkin untuk menghilangkan duplikat dalam proses serialisasi, maka ChangeTracker.TrackGraph menyediakan cara untuk menangani ini. TrackGraph bekerja seperti Add, Attach dan Update, tetapi menghasilkan pemanggilan balik untuk setiap instans entitas sebelum melacaknya. Panggilan balik ini dapat digunakan untuk melacak entitas atau mengabaikannya. Contohnya:

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

Untuk setiap entitas dalam grafik, kode ini akan:

  • Menemukan jenis entitas dan nilai kunci entitas
  • Cari entitas dengan kunci ini di pelacak perubahan
    • Jika entitas ditemukan, maka tidak ada tindakan lebih lanjut yang diambil karena entitas adalah duplikat
    • Jika entitas tidak ditemukan, maka entitas dilacak dengan mengatur status ke Modified

Output dari menjalankan kode ini adalah:

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

Penting

Kode ini mengasumsikan bahwa semua duplikat identik. Ini membuatnya aman untuk secara sewenang-wenang memilih salah satu duplikat untuk dilacak sambil membuang yang lain. Jika duplikat dapat berbeda, maka kode harus memutuskan cara menentukan mana yang akan digunakan, dan cara menggabungkan nilai properti dan navigasi bersama-sama.

Catatan

Untuk kesederhanaan, kode ini mengasumsikan setiap entitas memiliki properti kunci utama yang disebut Id. Ini dapat dikodifikasi menjadi kelas dasar abstrak atau antarmuka. Sebagai alternatif, properti kunci utama dapat diperoleh dari IEntityType metadata sehingga kode ini akan berfungsi dengan setiap jenis entitas.

Gagal mengatur nilai kunci

Jenis entitas sering dikonfigurasi untuk menggunakan nilai kunci yang dihasilkan secara otomatis. Ini adalah default untuk properti bilangan bulat dan GUID dari kunci non-komposit. Namun, jika jenis entitas tidak dikonfigurasi untuk menggunakan nilai kunci yang dihasilkan secara otomatis, maka nilai kunci eksplisit harus diatur sebelum melacak entitas. Misalnya, menggunakan jenis entitas berikut:

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

    public string Name { get; set; }
}

Pertimbangkan kode yang mencoba melacak dua instans entitas baru tanpa mengatur nilai kunci:

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

Kode ini akan melemparkan:

System.InvalidOperationException: Instans jenis entitas 'Pet' tidak dapat dilacak karena instans lain dengan nilai kunci '{Id: 0}' sudah dilacak. Saat melampirkan entitas yang sudah ada, pastikan hanya satu instans entitas dengan nilai kunci tertentu yang terpasang.

Perbaikan untuk ini adalah mengatur nilai kunci secara eksplisit atau mengonfigurasi properti kunci untuk menggunakan nilai kunci yang dihasilkan. Lihat Nilai yang Dihasilkan untuk informasi selengkapnya.

Menggunakan instans DbContext tunggal secara berlebihan

DbContext dirancang untuk mewakili unit kerja berumur pendek, seperti yang dijelaskan dalam Inisialisasi dan Konfigurasi DbContext, dan diuraikan dalam Pelacakan Perubahan di EF Core. Tidak mengikuti panduan ini dapat dengan mudah menyebabkan situasi di mana terjadi upaya untuk melacak beberapa instance dari entitas yang sama. Contoh umumnya adalah:

  • Menggunakan instans DbContext yang sama untuk menyiapkan status pengujian lalu menjalankan pengujian. Ini sering menyebabkan DbContext masih melacak satu instans entitas dari penyiapan pengujian, sementara kemudian berusaha mengaitkan instans baru dalam pengujian yang sesungguhnya. Sebagai gantinya, gunakan instans DbContext yang berbeda untuk menyiapkan status pengujian dan kode pengujian yang tepat.
  • Menggunakan instans DbContext bersama di repositori atau kode serupa. Sebagai gantinya, pastikan repositori Anda menggunakan satu instans DbContext untuk setiap unit kerja.

Resolusi dan pencarian identitas

Resolusi identitas terjadi secara otomatis ketika entitas dilacak dari kueri. Ini berarti bahwa jika instans entitas dengan nilai kunci tertentu sudah dilacak, maka instans terlacak yang ada ini digunakan alih-alih membuat instans baru. Ini memiliki konsekuensi penting: jika data telah berubah dalam database, maka ini tidak akan tercermin dalam hasil kueri. Ini adalah alasan yang baik untuk menggunakan instans DbContext baru untuk setiap unit kerja, seperti yang dijelaskan dalam Inisialisasi dan Konfigurasi DbContext, dan diuraikan dalam Pelacakan Perubahan di EF Core.

Penting

Penting untuk dipahami bahwa EF Core selalu menjalankan kueri LINQ pada DbSet terhadap database dan hanya mengembalikan hasil berdasarkan apa yang ada di database. Namun, untuk kueri pelacakan, jika entitas yang dikembalikan sudah dilacak, maka instans yang dilacak digunakan alih-alih membuat instans dari data dalam database.

Reload() atau GetDatabaseValues() dapat digunakan ketika entitas yang dilacak perlu di-refresh dengan data terbaru dari database. Lihat Mengakses Entitas Terlacak untuk informasi selengkapnya.

Berbeda dengan kueri pelacakan, kueri tanpa pelacakan tidak melakukan resolusi identitas. Ini berarti bahwa kueri tanpa pelacakan dapat mengembalikan duplikat seperti dalam kasus serialisasi JSON yang dijelaskan sebelumnya. Ini biasanya bukan masalah jika hasil kueri akan diserialisasikan dan dikirim ke klien.

Petunjuk / Saran

Jangan secara rutin melakukan kueri tanpa pelacakan lalu lampirkan entitas yang dikembalikan ke konteks yang sama. Ini akan lebih lambat dan lebih sulit untuk mendapatkan hasil yang benar daripada menggunakan kueri pelacakan.

Kueri tanpa pelacakan tidak melakukan penyelesaian identitas karena hal tersebut mempengaruhi kinerja proses streaming sejumlah besar objek dari kueri. Ini karena resolusi identitas memerlukan pelacakan setiap instans yang dikembalikan sehingga dapat digunakan alih-alih nanti membuat duplikat.

Kueri tanpa pelacakan dapat dipaksa untuk melakukan resolusi identitas dengan menggunakan AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>). Kueri kemudian akan melacak instans yang dikembalikan (tanpa melacaknya dengan cara normal) dan memastikan tidak ada duplikat yang dibuat dalam hasil kueri.

Mengesampingkan kesetaraan objek

EF Core menggunakan kesetaraan referensi saat membandingkan instans entitas. Ini adalah kasus bahkan jika jenis entitas mengambil Object.Equals(Object) alih atau mengubah kesetaraan objek. Namun, ada satu tempat di mana penggantian kesetaraan dapat memengaruhi perilaku EF Core: ketika navigasi koleksi menggunakan kesetaraan yang diganti alih-alih kesetaraan referensi, dan karenanya melaporkan beberapa instance sebagai sama.

Karena itu, disarankan agar mengambil alih kesetaraan entitas harus dihindari. Jika digunakan, pastikan untuk membuat navigasi koleksi yang memaksa kesetaraan referensi. Misalnya, buat pembanding kesetaraan yang menggunakan kesetaraan referensi.

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

(Dimulai dengan .NET 5, ini termasuk dalam BCL sebagai ReferenceEqualityComparer.)

Perbandingan ini kemudian dapat digunakan saat membuat navigasi koleksi. Contohnya:

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

Membandingkan properti kunci

Selain perbandingan kesetaraan, nilai kunci juga perlu diurutkan. Ini penting untuk menghindari kebuntuan saat memperbarui beberapa entitas dalam satu panggilan ke SaveChanges. Semua jenis yang digunakan untuk properti utama, alternatif, atau kunci asing, serta yang digunakan untuk indeks unik, harus mengimplementasikan IComparable<T> dan IEquatable<T>. Jenis yang biasanya digunakan sebagai kunci (int, Guid, string, dll.) sudah mendukung antarmuka ini. Jenis kunci kustom dapat menambahkan antarmuka ini.