Bagikan melalui


Ubah Deteksi dan Pemberitahuan

Setiap instans DbContext melacak perubahan yang dilakukan pada entitas. Entitas yang dilacak ini pada gilirannya mendorong perubahan ke database saat SaveChanges dipanggil. Ini tercakup dalam Pelacakan Perubahan di EF Core, dan dokumen ini mengasumsikan bahwa status entitas dan dasar-dasar pelacakan perubahan Entity Framework Core (EF Core) dipahami.

Melacak perubahan properti dan hubungan mengharuskan DbContext dapat mendeteksi perubahan ini. Dokumen ini mencakup bagaimana deteksi ini terjadi, serta cara menggunakan pemberitahuan properti atau proksi pelacakan perubahan untuk memaksa deteksi perubahan segera.

Tip

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

Pelacakan perubahan rekam jepret

Secara default, EF Core membuat rekam jepret dari setiap nilai properti entitas saat pertama kali dilacak oleh instans DbContext. Nilai yang disimpan dalam rekam jepret ini kemudian dibandingkan dengan nilai entitas saat ini untuk menentukan nilai properti mana yang telah berubah.

Deteksi perubahan ini terjadi ketika SaveChanges dipanggil untuk memastikan semua nilai yang diubah terdeteksi sebelum mengirim pembaruan ke database. Namun, deteksi perubahan juga terjadi di lain waktu untuk memastikan aplikasi bekerja dengan informasi pelacakan terbaru. Deteksi perubahan dapat dipaksa kapan saja dengan memanggil ChangeTracker.DetectChanges().

Saat deteksi perubahan diperlukan

Deteksi perubahan diperlukan ketika properti atau navigasi telah diubah tanpa menggunakan EF Core untuk membuat perubahan ini. Misalnya, pertimbangkan untuk memuat blog dan postingan lalu membuat perubahan pada entitas ini:

using var context = new BlogsContext();
var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");

// Change a property value
blog.Name = ".NET Blog (Updated!)";

// Add a new entity to a navigation
blog.Posts.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
    });

Console.WriteLine(context.ChangeTracker.DebugView.LongView);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Melihat tampilan debug pelacak perubahan sebelum panggilan ChangeTracker.DetectChanges() menunjukkan bahwa perubahan yang dibuat belum terdeteksi dan karenanya tidak tercermin dalam status entitas dan data properti yang dimodifikasi:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog (Updated!)' Originally '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}, <not found>]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Secara khusus, status entri blog masih Unchanged, dan posting baru tidak muncul sebagai entitas terlacak. (Astute akan melihat properti melaporkan nilai baru mereka, meskipun perubahan ini belum terdeteksi oleh EF Core. Ini karena tampilan debug membaca nilai saat ini langsung dari instans entitas.)

Berbeda dengan tampilan debug setelah memanggil DetectChanges:

Blog {Id: 1} Modified
  Id: 1 PK
  Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
  Id: -2147482643 PK Temporary
  BlogId: 1 FK
  Content: '.NET 5.0 was released recently and has come with many...'
  Title: 'What's next for System.Text.Json?'
  Blog: {Id: 1}
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Sekarang blog ditandai dengan benar sebagai Modified dan posting baru telah terdeteksi dan dilacak sebagai Added.

Pada awal bagian ini, kami menyatakan bahwa mendeteksi perubahan diperlukan saat tidak menggunakan EF Core untuk melakukan perubahan. Inilah yang terjadi dalam kode di atas. Artinya, perubahan pada properti dan navigasi dilakukan langsung pada instans entitas, dan bukan dengan menggunakan metode EF Core apa pun.

Kontraskan ini dengan kode berikut yang memodifikasi entitas dengan cara yang sama, tetapi kali ini menggunakan metode EF Core:

using var context = new BlogsContext();
var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");

// Change a property value
context.Entry(blog).Property(e => e.Name).CurrentValue = ".NET Blog (Updated!)";

// Add a new entity to the DbContext
context.Add(
    new Post
    {
        Blog = blog,
        Title = "What’s next for System.Text.Json?",
        Content = ".NET 5.0 was released recently and has come with many..."
    });

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Dalam hal ini tampilan debug pelacak perubahan menunjukkan bahwa semua status entitas dan modifikasi properti diketahui, meskipun deteksi perubahan belum terjadi. Ini karena PropertyEntry.CurrentValue adalah metode EF Core, yang berarti bahwa EF Core segera tahu tentang perubahan yang dilakukan oleh metode ini. Demikian juga, panggilan DbContext.Add memungkinkan EF Core untuk segera mengetahui tentang entitas baru dan melacaknya dengan tepat.

Tip

Jangan mencoba menghindari deteksi perubahan dengan selalu menggunakan metode EF Core untuk membuat perubahan entitas. Melakukannya sering kali lebih rumit dan berkinerja kurang baik daripada membuat perubahan pada entitas dengan cara normal. Niat dari dokumen ini adalah untuk menginformasikan kapan mendeteksi perubahan diperlukan dan kapan tidak. Niatnya adalah untuk tidak mendorong penghindarian deteksi perubahan.

Metode yang secara otomatis mendeteksi perubahan

DetectChanges() dipanggil secara otomatis oleh metode di mana melakukannya kemungkinan akan berdampak pada hasilnya. Metode ini adalah:

Ada juga beberapa tempat di mana deteksi perubahan hanya terjadi pada satu instans entitas, bukan pada seluruh grafik entitas yang dilacak. Tempat-tempat ini adalah:

  • Saat menggunakan DbContext.Entry, untuk memastikan bahwa status entitas dan properti yang dimodifikasi sudah diperbarui.
  • Saat menggunakan EntityEntry metode seperti Property, , CollectionReference atau Member untuk memastikan modifikasi properti, nilai saat ini, dll. sudah diperbarui.
  • Ketika entitas dependen/anak akan dihapus karena hubungan yang diperlukan telah diputus. Ini mendeteksi kapan entitas tidak boleh dihapus karena telah di-induk ulang.

Deteksi perubahan lokal untuk satu entitas dapat dipicu secara eksplisit dengan memanggil EntityEntry.DetectChanges().

Catatan

Perubahan deteksi lokal dapat melewatkan beberapa perubahan yang akan ditemukan deteksi penuh. Ini terjadi ketika tindakan berjendela yang dihasilkan dari perubahan yang tidak terdeteksi pada entitas lain berdampak pada entitas yang dimaksud. Dalam situasi seperti itu, aplikasi mungkin perlu memaksa pemindaian penuh semua entitas dengan secara eksplisit memanggil ChangeTracker.DetectChanges().

Menonaktifkan deteksi perubahan otomatis

Performa mendeteksi perubahan bukanlah hambatan untuk sebagian besar aplikasi. Namun, mendeteksi perubahan dapat menjadi masalah performa untuk beberapa aplikasi yang melacak ribuan entitas. (Angka pasti akan bergantung pada banyak hal, seperti jumlah properti dalam entitas.) Untuk alasan ini, deteksi perubahan otomatis dapat dinonaktifkan menggunakan ChangeTracker.AutoDetectChangesEnabled. Misalnya, pertimbangkan untuk memproses entitas gabungan dalam hubungan banyak ke banyak dengan payload:

public override int SaveChanges()
{
    foreach (var entityEntry in ChangeTracker.Entries<PostTag>()) // Detects changes automatically
    {
        if (entityEntry.State == EntityState.Added)
        {
            entityEntry.Entity.TaggedBy = "ajcvickers";
            entityEntry.Entity.TaggedOn = DateTime.Now;
        }
    }

    try
    {
        ChangeTracker.AutoDetectChangesEnabled = false;
        return base.SaveChanges(); // Avoid automatically detecting changes again here
    }
    finally
    {
        ChangeTracker.AutoDetectChangesEnabled = true;
    }
}

Seperti yang kita ketahui dari bagian sebelumnya, baik ChangeTracker.Entries<TEntity>() dan DbContext.SaveChanges secara otomatis mendeteksi perubahan. Namun, setelah memanggil Entri, kode kemudian tidak membuat perubahan status entitas atau properti apa pun. (Mengatur nilai properti normal pada Entitas yang ditambahkan tidak menyebabkan perubahan status apa pun.) Oleh karena itu, kode menonaktifkan deteksi perubahan otomatis yang tidak perlu saat memanggil ke metode SaveChanges dasar. Kode ini juga menggunakan blok coba/akhirnya untuk memastikan bahwa pengaturan default dipulihkan meskipun SaveChanges gagal.

Tip

Jangan berasumsi bahwa kode Anda harus menonaktifkan deteksi perubahan otomatis agar berkinerja baik. Ini hanya diperlukan saat membuat profil aplikasi yang melacak banyak entitas menunjukkan bahwa performa deteksi perubahan adalah masalah.

Mendeteksi perubahan dan konversi nilai

Untuk menggunakan pelacakan perubahan rekam jepret dengan jenis entitas, EF Core harus dapat:

  • Membuat rekam jepret dari setiap nilai properti saat entitas dilacak
  • Bandingkan nilai ini dengan nilai properti saat ini
  • Membuat kode hash untuk nilai

Ini ditangani secara otomatis oleh EF Core untuk jenis yang dapat langsung dipetakan ke database. Namun, ketika pengonversi nilai digunakan untuk memetakan properti, maka pengonversi tersebut harus menentukan cara melakukan tindakan ini. Ini dicapai dengan perbandingan nilai, dan dijelaskan secara rinci dalam dokumentasi Value Comparers .

Entitas pemberitahuan

Pelacakan perubahan rekam jepret direkomendasikan untuk sebagian besar aplikasi. Namun, aplikasi yang melacak banyak entitas dan/atau membuat banyak perubahan pada entitas tersebut dapat memperoleh manfaat dari menerapkan entitas yang secara otomatis memberi tahu EF Core ketika nilai properti dan navigasi mereka berubah. Ini dikenal sebagai "entitas pemberitahuan".

Menerapkan entitas pemberitahuan

Entitas pemberitahuan memanfaatkan INotifyPropertyChanging antarmuka dan INotifyPropertyChanged , yang merupakan bagian dari pustaka kelas dasar .NET (BCL). Antarmuka ini menentukan peristiwa yang harus ditembakkan sebelum dan sesudah mengubah nilai properti. Contohnya:

public class Blog : INotifyPropertyChanging, INotifyPropertyChanged
{
    public event PropertyChangingEventHandler PropertyChanging;
    public event PropertyChangedEventHandler PropertyChanged;

    private int _id;

    public int Id
    {
        get => _id;
        set
        {
            PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Id)));
            _id = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
        }
    }

    private string _name;

    public string Name
    {
        get => _name;
        set
        {
            PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Name)));
            _name = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
        }
    }

    public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}

Selain itu, navigasi koleksi apa pun harus menerapkan INotifyCollectionChanged; dalam contoh di atas ini puas dengan menggunakan ObservableCollection<T> postingan. EF Core juga dikirim dengan ObservableHashSet<T> implementasi yang memiliki pencarian yang lebih efisien dengan mengorbankan pemesanan yang stabil.

Sebagian besar kode pemberitahuan ini biasanya dipindahkan ke kelas dasar yang tidak dipetakan. Contohnya:

public class Blog : NotifyingEntity
{
    private int _id;

    public int Id
    {
        get => _id;
        set => SetWithNotify(value, out _id);
    }

    private string _name;

    public string Name
    {
        get => _name;
        set => SetWithNotify(value, out _name);
    }

    public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}

public abstract class NotifyingEntity : INotifyPropertyChanging, INotifyPropertyChanged
{
    protected void SetWithNotify<T>(T value, out T field, [CallerMemberName] string propertyName = "")
    {
        NotifyChanging(propertyName);
        field = value;
        NotifyChanged(propertyName);
    }

    public event PropertyChangingEventHandler PropertyChanging;
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyChanged(string propertyName)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    private void NotifyChanging(string propertyName)
        => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}

Mengonfigurasi entitas pemberitahuan

Tidak ada cara bagi EF Core untuk memvalidasi bahwa INotifyPropertyChanging atau INotifyPropertyChanged sepenuhnya diimplementasikan untuk digunakan dengan EF Core. Secara khusus, beberapa penggunaan antarmuka ini melakukannya dengan pemberitahuan hanya pada properti tertentu, bukan pada semua properti (termasuk navigasi) seperti yang diperlukan oleh EF Core. Untuk alasan ini, EF Core tidak secara otomatis terhubung ke peristiwa ini.

Sebagai gantinya, EF Core harus dikonfigurasi untuk menggunakan entitas pemberitahuan ini. Ini biasanya dilakukan untuk semua jenis entitas dengan memanggil ModelBuilder.HasChangeTrackingStrategy. Contohnya:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications);
}

(Strategi ini juga dapat diatur secara berbeda untuk jenis entitas yang berbeda menggunakan EntityTypeBuilder.HasChangeTrackingStrategy, tetapi ini biasanya kontraproduktif karena DetectChanges masih diperlukan untuk jenis-jenis yang bukan entitas pemberitahuan.)

Pelacakan perubahan pemberitahuan penuh mengharuskan dan INotifyPropertyChanging INotifyPropertyChanged diimplementasikan. Ini memungkinkan nilai asli disimpan tepat sebelum nilai properti diubah, menghindari kebutuhan EF Core untuk membuat rekam jepret saat melacak entitas. Jenis entitas yang hanya mengimplementasikan juga INotifyPropertyChanged dapat digunakan dengan EF Core. Dalam hal ini, EF masih membuat rekam jepret saat melacak entitas untuk melacak nilai asli, tetapi kemudian menggunakan pemberitahuan untuk mendeteksi perubahan segera, daripada memerlukan DetectChanges untuk dipanggil.

Nilai yang berbeda ChangeTrackingStrategy dirangkum dalam tabel berikut.

ChangeTrackingStrategy Antarmuka diperlukan Perlu DetectChanges Nilai asli rekam jepret
Snapshot Tidak ada Ya Ya
ChangedNotifications INotifyPropertyChanged Tidak Ya
ChangingAndChangedNotifications INotifyPropertyChanged dan INotifyPropertyChanging Tidak Tidak
ChangingAndChangedNotificationsWithOriginalValues INotifyPropertyChanged dan INotifyPropertyChanging Tidak Ya

Menggunakan entitas pemberitahuan

Entitas pemberitahuan bertingkah seperti entitas lain, kecuali bahwa membuat perubahan pada instans entitas tidak memerlukan panggilan untuk ChangeTracker.DetectChanges() mendeteksi perubahan ini. Contohnya:

using var context = new BlogsContext();
var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");

// Change a property value
blog.Name = ".NET Blog (Updated!)";

// Add a new entity to a navigation
blog.Posts.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
    });

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Dengan entitas normal, tampilan debug pelacak perubahan menunjukkan bahwa perubahan ini tidak terdeteksi sampai DetectChanges dipanggil. Melihat tampilan debug saat entitas pemberitahuan digunakan menunjukkan bahwa perubahan ini telah segera terdeteksi:

Blog {Id: 1} Modified
  Id: 1 PK
  Name: '.NET Blog (Updated!)' Modified
  Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
  Id: -2147482643 PK Temporary
  BlogId: 1 FK
  Content: '.NET 5.0 was released recently and has come with many...'
  Title: 'What's next for System.Text.Json?'
  Blog: {Id: 1}
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Proksi pelacakan perubahan

EF Core dapat secara dinamis menghasilkan jenis proksi yang mengimplementasikan INotifyPropertyChanging dan INotifyPropertyChanged. Ini memerlukan penginstalan paket NuGet Microsoft.EntityFrameworkCore.Proxies , dan mengaktifkan proksi pelacakan perubahan dengan UseChangeTrackingProxies Misalnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.UseChangeTrackingProxies();

Membuat proksi dinamis melibatkan pembuatan jenis .NET dinamis baru (menggunakan implementasi proksi Castle.Core ), yang mewarisi dari jenis entitas dan kemudian mengambil alih semua setter properti. Oleh karena itu, jenis entitas untuk proksi harus berupa jenis yang dapat diwarisi dan harus memiliki properti yang dapat ditimpa. Selain itu, navigasi koleksi yang dibuat secara eksplisit harus diterapkan INotifyCollectionChanged Misalnya:

public class Blog
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }

    public virtual IList<Post> Posts { get; } = new ObservableCollection<Post>();
}

public class Post
{
    public virtual int Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Content { get; set; }

    public virtual int BlogId { get; set; }
    public virtual Blog Blog { get; set; }
}

Salah satu kelemahan signifikan untuk proksi pelacakan perubahan adalah bahwa EF Core harus selalu melacak instans proksi, tidak pernah instans jenis entitas yang mendasar. Ini karena instans jenis entitas yang mendasar tidak akan menghasilkan pemberitahuan, yang berarti perubahan yang dilakukan pada entitas ini akan terlewatkan.

EF Core membuat instans proksi secara otomatis saat mengkueri database, sehingga kelemahan ini umumnya terbatas pada pelacakan instans entitas baru. Instans ini harus dibuat menggunakan CreateProxy metode ekstensi, dan bukan dengan cara normal menggunakan new. Ini berarti kode dari contoh sebelumnya sekarang harus menggunakan CreateProxy:

using var context = new BlogsContext();
var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");

// Change a property value
blog.Name = ".NET Blog (Updated!)";

// Add a new entity to a navigation
blog.Posts.Add(
    context.CreateProxy<Post>(
        p =>
        {
            p.Title = "What’s next for System.Text.Json?";
            p.Content = ".NET 5.0 was released recently and has come with many...";
        }));

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Mengubah peristiwa pelacakan

EF Core mengaktifkan ChangeTracker.Tracked peristiwa ketika entitas dilacak untuk pertama kalinya. Perubahan status entitas di masa mendatang mengakibatkan ChangeTracker.StateChanged peristiwa. Lihat Peristiwa .NET di EF Core untuk informasi selengkapnya.

Catatan

Peristiwa StateChanged tidak diaktifkan ketika entitas pertama kali dilacak, meskipun status telah berubah dari Detached ke salah satu status lainnya. Pastikan untuk mendengarkan peristiwa StateChanged dan Tracked untuk mendapatkan semua pemberitahuan yang relevan.