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

Tip

Anda dapat melihat contoh artikel ini di GitHub.

Tip

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 berumur pendek untuk setiap unit kerja sedih sehingga konteks mulai kosong, memiliki entitas yang melekat padanya, menyimpan entitas tersebut, dan kemudian konteksnya dibuang 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 untuk memeriksa kunci yang tidak diatur 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;

Tip

Kunci diatur segera setelah entitas dilacak oleh konteks, bahkan jika entitas berada dalam status Ditambahkan. Ini membantu saat melintas grafik entitas dan memutuskan apa yang harus dilakukan dengan masing-masing, 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 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 bool IsItNew(BloggingContext context, Blog blog)
    => context.Blogs.Find(blog.BlogId) == null;

Di luar cakupan dokumen ini untuk menunjukkan kode lengkap untuk meneruskan bendera 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 void Insert(DbContext context, object entity)
{
    context.Add(entity);
    context.SaveChanges();
}

public static void Update(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

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

public static void InsertOrUpdate(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

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 void InsertOrUpdate(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs.Find(blog.BlogId);
    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
    }

    context.SaveChanges();
}

Langkah-langkah 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.

Tip

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 void InsertGraph(DbContext context, object rootEntity)
{
    context.Add(rootEntity);
    context.SaveChanges();
}

Panggilan ke Tambahkan akan menandai blog dan semua postingan yang akan disisipkan.

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

public static void UpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

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 void InsertOrUpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

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 void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(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);
            }
        }
    }

    context.SaveChanges();
}

Menangani 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 sementara" sehingga entitas ditandai sebagai dihapus daripada benar-benar dihapus. Menghapus kemudian menjadi sama dengan pembaruan. Penghapusan sementara dapat diimplementasikan menggunakan filter kueri.

Untuk penghapusan benar, pola umumnya adalah menggunakan ekstensi pola kueri untuk melakukan apa yang pada dasarnya merupakan perbedaan grafik. Contohnya:

public static void InsertUpdateOrDeleteGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(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);
            }
        }
    }

    context.SaveChanges();
}

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

    context.SaveChanges();
}

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.