Entitas yang terputus

Instans DbContext akan secara otomatis melacak entitas yang dikembalikan dari database. Perubahan yang dilakukan pada entitas ini kemudian akan terdeteksi ketika SaveChanges dipanggil dan database akan diperbarui sesuai kebutuhan. Lihat Simpan Dasar dan Data Terkait untuk detailnya.

Namun, terkadang entitas dikueri menggunakan satu instans konteks dan kemudian disimpan menggunakan instans yang berbeda. Ini sering terjadi dalam skenario "terputus" seperti aplikasi web tempat entitas dikueri, dikirim ke klien, dimodifikasi, dikirim kembali ke server dalam permintaan, lalu disimpan. Dalam hal ini, instans konteks kedua perlu mengetahui apakah entitas baru (harus dimasukkan) atau yang sudah ada (harus diperbarui).

Petunjuk / Saran

Anda dapat melihat contoh artikel ini di GitHub.

Petunjuk / Saran

EF Core hanya dapat melacak satu instans entitas apa pun dengan nilai kunci primer tertentu. Cara terbaik untuk menghindari masalah ini adalah dengan menggunakan konteks jangka pendek untuk setiap unit kerja, sehingga konteks dimulai dalam keadaan kosong, memiliki entitas yang terkait dengannya, menyimpan entitas tersebut, dan kemudian konteksnya dihapus dan dibuang.

Mengidentifikasi entitas baru

Klien mengidentifikasi entitas baru

Kasus paling sederhana untuk ditangani adalah ketika klien menginformasikan server apakah entitas baru atau yang sudah ada. Misalnya, seringkali permintaan untuk menyisipkan entitas baru berbeda dari permintaan untuk memperbarui entitas yang ada.

Sisa bagian ini mencakup kasus di mana perlu untuk menentukan dengan cara lain apakah akan menyisipkan atau memperbarui.

Dengan kunci yang dihasilkan secara otomatis

Nilai kunci yang dihasilkan secara otomatis sering dapat digunakan untuk menentukan apakah entitas perlu disisipkan atau diperbarui. Jika kunci belum diatur (yaitu, kunci masih memiliki nilai default CLR null, nol, dll.), maka entitas harus baru dan perlu dimasukkan. Di sisi lain, jika nilai kunci telah ditetapkan, maka nilai tersebut harus sudah disimpan sebelumnya dan sekarang perlu diperbarui. Dengan kata lain, jika kunci memiliki nilai, maka entitas dikueri, dikirim ke klien, dan sekarang telah kembali untuk diperbarui.

Mudah memeriksa kunci yang belum disetel ketika jenis entitas diketahui.

public static bool IsItNew(Blog blog)
    => blog.BlogId == 0;

Namun, EF juga memiliki cara bawaan untuk melakukan ini untuk jenis entitas dan jenis kunci apa pun:

public static bool IsItNew(DbContext context, object entity)
    => !context.Entry(entity).IsKeySet;

Petunjuk / Saran

Kunci diatur segera setelah entitas dilacak oleh konteks, bahkan jika entitas berada dalam status Ditambahkan. Ini membantu saat menelusuri grafik entitas dan memutuskan apa yang harus dilakukan dengan masing-masing entitas, seperti saat menggunakan TrackGraph API. Nilai kunci hanya boleh digunakan dengan cara yang ditunjukkan di sini sebelum panggilan apa pun dilakukan untuk melacak entitas.

Dengan kunci-kunci lain

Beberapa mekanisme lain diperlukan untuk mengidentifikasi entitas baru ketika nilai kunci tidak dihasilkan secara otomatis. Ada dua pendekatan umum untuk ini:

  • Kueri untuk entitas
  • Meneruskan bendera dari klien

Untuk mengkueri entitas, cukup gunakan metode Temukan:

public static async Task<bool> IsItNew(BloggingContext context, Blog blog)
    => (await context.Blogs.FindAsync(blog.BlogId)) == null;

Tidak tercakup dalam dokumen ini untuk menunjukkan seluruh kode untuk mengirimkan flag dari klien. Dalam aplikasi web, biasanya berarti membuat permintaan yang berbeda untuk tindakan yang berbeda, atau meneruskan beberapa status dalam permintaan lalu mengekstraknya di pengontrol.

Menyimpan entitas tunggal

Jika diketahui apakah penyisipan atau pembaruan diperlukan atau tidak, maka Tambahkan atau Perbarui dapat digunakan dengan tepat:

public static async Task Insert(DbContext context, object entity)
{
    context.Add(entity);
    await context.SaveChangesAsync();
}

public static async Task Update(DbContext context, object entity)
{
    context.Update(entity);
    await context.SaveChangesAsync();
}

Namun, jika entitas menggunakan nilai kunci yang dihasilkan secara otomatis, maka metode Pembaruan dapat digunakan untuk kedua kasus:

public static async Task InsertOrUpdate(DbContext context, object entity)
{
    context.Update(entity);
    await context.SaveChangesAsync();
}

Metode Pembaruan biasanya menandai entitas untuk pembaruan, bukan sisipkan. Namun, jika entitas memiliki kunci yang dihasilkan secara otomatis, dan tidak ada nilai kunci yang telah ditetapkan, maka entitas akan secara otomatis ditandai untuk dimasukkan.

Jika entitas tidak menggunakan kunci yang dihasilkan secara otomatis, maka aplikasi harus memutuskan apakah entitas harus dimasukkan atau diperbarui: Misalnya:

public static async Task InsertOrUpdate(BloggingContext context, Blog blog)
{
    var existingBlog = await context.Blogs.FindAsync(blog.BlogId);
    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
    }

    await context.SaveChangesAsync();
}

Langkah-langkahnya di sini adalah:

  • Jika Find mengembalikan null, database belum berisi blog dengan ID ini, jadi kami memanggil Tambahkan tandai untuk penyisipan.
  • Jika Find mengembalikan entitas, maka entitas tersebut ada di database dan konteksnya sekarang melacak entitas yang ada
    • Kami kemudian menggunakan SetValues untuk mengatur nilai untuk semua properti pada entitas ini ke yang berasal dari klien.
    • Panggilan SetValues akan menandai entitas yang akan diperbarui sesuai kebutuhan.

Petunjuk / Saran

SetValues hanya akan menandai sebagai properti yang dimodifikasi yang memiliki nilai berbeda dengan yang ada di entitas yang dilacak. Ini berarti bahwa ketika pembaruan dikirim, hanya kolom yang benar-benar berubah yang akan diperbarui. (Dan jika tidak ada yang berubah, maka tidak ada pembaruan yang akan dikirim sama sekali.)

Bekerja dengan grafik

Resolusi identitas

Seperti disebutkan di atas, EF Core hanya dapat melacak satu instans entitas apa pun dengan nilai kunci primer tertentu. Saat bekerja dengan grafik, grafik idealnya harus dibuat sehingga invarian ini dipertahankan, dan konteksnya harus digunakan hanya untuk satu unit kerja. Jika grafik memang berisi duplikat, maka perlu memproses grafik sebelum mengirimkannya ke EF untuk mengonsolidasikan beberapa instans menjadi satu. Ini mungkin tidak sepele di mana instans memiliki nilai dan hubungan yang bertentangan, sehingga mengonsolidasikan duplikat harus dilakukan sesegera mungkin dalam alur aplikasi Anda untuk menghindari penyelesaian konflik.

Semua entitas baru/semua yang sudah ada

Contoh bekerja dengan grafik adalah menyisipkan atau memperbarui blog bersama dengan kumpulan posting terkait. Jika semua entitas dalam grafik harus disisipkan, atau semua harus diperbarui, maka prosesnya sama seperti yang dijelaskan di atas untuk entitas tunggal. Misalnya, grafik blog dan posting yang dibuat seperti ini:

var blog = new Blog
{
    Url = "http://sample.com", Posts = new List<Post> { new Post { Title = "Post 1" }, new Post { Title = "Post 2" }, }
};

dapat disisipkan seperti ini:

public static async Task InsertGraph(DbContext context, object rootEntity)
{
    context.Add(rootEntity);
    await context.SaveChangesAsync();
}

Pemanggilan ke fungsi Tambahkan akan menandai blog dan semua postingan untuk disisipkan.

Demikian juga, jika semua entitas dalam grafik perlu diperbarui, maka Pembaruan dapat digunakan:

public static async Task UpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    await context.SaveChangesAsync();
}

Blog dan semua postingannya akan ditandai untuk diperbarui.

Campuran entitas baru dan yang sudah ada

Dengan kunci yang dihasilkan secara otomatis, Pembaruan dapat kembali digunakan untuk sisipan dan pembaruan, bahkan jika grafik berisi campuran entitas yang memerlukan penyisipan dan yang memerlukan pembaruan:

public static async Task InsertOrUpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    await context.SaveChangesAsync();
}

Pembaruan akan menandai entitas apa pun dalam grafik, blog, atau posting, untuk penyisipan jika tidak memiliki kumpulan nilai kunci, sementara semua entitas lain ditandai untuk diperbarui.

Seperti sebelumnya, saat tidak menggunakan kunci yang dihasilkan secara otomatis, kueri dan beberapa pemrosesan dapat digunakan:

public static async Task InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
    var existingBlog = await context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefaultAsync(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }
    }

    await context.SaveChangesAsync();
}

Penanganan penghapusan

Penghapusan dapat sulit untuk ditangani karena seringkali tidak adanya entitas berarti bahwa itu harus dihapus. Salah satu cara untuk menangani hal ini adalah dengan menggunakan "penghapusan lunak" di mana entitas ditandai sebagai dihapus daripada benar-benar dihapus. Menghapus kemudian dianggap sama dengan memperbarui. Penghapusan sementara dapat diimplementasikan menggunakan filter kueri.

Untuk penghapusan secara permanen, pola umumnya adalah menggunakan ekstensi pola kueri untuk melakukan yang pada dasarnya merupakan perbedaan dalam struktur grafis. Contohnya:

public static async Task InsertUpdateOrDeleteGraph(BloggingContext context, Blog blog)
{
    var existingBlog = await context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefaultAsync(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }

        foreach (var post in existingBlog.Posts)
        {
            if (!blog.Posts.Any(p => p.PostId == post.PostId))
            {
                context.Remove(post);
            }
        }
    }

    await context.SaveChangesAsync();
}

TrackGraph

Secara internal, Tambahkan, Lampirkan, dan Perbarui gunakan graph-traversal dengan penentuan yang dibuat untuk setiap entitas apakah harus ditandai sebagai Ditambahkan (untuk menyisipkan), Diubah (untuk memperbarui), Tidak Berubah (tidak melakukan apa pun), atau Dihapus (untuk menghapus). Mekanisme ini diekspos melalui API TrackGraph. Misalnya, mari kita asumsikan bahwa ketika klien mengirim kembali grafik entitas, klien menetapkan beberapa bendera pada setiap entitas yang menunjukkan bagaimana seharusnya ditangani. TrackGraph kemudian dapat digunakan untuk memproses bendera ini:

public static async Task SaveAnnotatedGraph(DbContext context, object rootEntity)
{
    context.ChangeTracker.TrackGraph(
        rootEntity,
        n =>
        {
            var entity = (EntityBase)n.Entry.Entity;
            n.Entry.State = entity.IsNew
                ? EntityState.Added
                : entity.IsChanged
                    ? EntityState.Modified
                    : entity.IsDeleted
                        ? EntityState.Deleted
                        : EntityState.Unchanged;
        });

    await context.SaveChangesAsync();
}

Bendera hanya ditampilkan sebagai bagian dari entitas untuk kesederhanaan contoh. Biasanya bendera akan menjadi bagian dari DTO atau beberapa status lain yang disertakan dalam permintaan.