Değişiklik Algılama ve Bildirimler

Her DbContext örnek, varlıklarda yapılan değişiklikleri izler. İzlenen bu varlıklar, SaveChanges çağrıldığında veritabanındaki değişikliklere yol açar. Bu, EF Core'da Değişiklik İzleme bölümünde ele alınmıştır ve bu belgede varlık durumlarının ve Entity Framework Core (EF Core) değişiklik izlemesinin temellerinin anlaşıldığı varsayılmaktadır.

özellik ve ilişki değişikliklerini izleme, DbContext'in bu değişiklikleri algılayabilmesini gerektirir. Bu belge, bu algılamanın nasıl gerçekleştiğini ve değişikliklerin anında algılanması için özellik bildirimlerinin veya değişiklik izleme proxy'lerinin nasıl kullanılacağını kapsar.

Tavsiye

GitHub'dan örnek kodu indirerek bu belgedeki tüm kodu çalıştırabilir ve hatalarını ayıklayabilirsiniz.

Anlık görüntüdeki değişikliklerin izlenmesi

Varsayılan olarak, EF Core bir DbContext örneği tarafından ilk izlendiğinde her varlığın özellik değerlerinin anlık görüntüsünü oluşturur. Bu anlık görüntüde depolanan değerler, hangi özellik değerlerinin değiştiğini belirlemek için varlığın geçerli değerleriyle karşılaştırılır.

Bu değişiklik algılama işlemi, veritabanına güncelleştirme göndermeden önce değiştirilen tüm değerlerin algılandığından emin olmak için SaveChanges çağrıldığında gerçekleşir. Ancak, uygulamanın up-totarih izleme bilgileriyle çalıştığından emin olmak için değişikliklerin algılanması başka zamanlarda da gerçekleşir. Değişikliklerin algılanması, herhangi bir zamanda ChangeTracker.DetectChanges() çağrılarak zorlanabilir.

Değişiklik algılama gerektiğinde

Bu değişikliği yapmak için EF Core kullanılmadan bir özellik veya gezinti değiştirildiğinde değişikliklerin algılanması gerekir. Örneğin, blogları ve gönderileri yüklemeyi ve sonra bu varlıklarda değişiklik yapmayı göz önünde bulundurun:

using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(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);

Çağrıdan önce ChangeTracker.DetectChanges() baktığımızda, yapılan değişikliklerin algılanmadığı ve bu nedenle varlık durumlarına ve değiştirilen özellik verilerine yansıtılmadığını gösterir:

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}

Özel olarak, blog girdisinin durumu hala Unchangedşeklindedir ve yeni gönderi izlenen bir varlık olarak görünmez. (Bu değişiklikler EF Core tarafından henüz algılanmamış olsa bile, astute özelliklerin yeni değerlerini raporladığını fark eder. Bunun nedeni, hata ayıklama görünümünün geçerli değerleri doğrudan varlık örneğinden okumasıdır.)

DetectChanges çağrıldıktan sonra hata ayıklama görünümünü karşılaştırın:

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}

Şimdi blog doğru olarak Modified işaretlendi ve yeni gönderi algılandı. Yeni gönderi Added olarak izleniyor.

Bu bölümün başında, değişikliği yapmak için EF Core kullanmadığınızda değişiklikleri algılamanın gerekli olduğunu belirttik. Yukarıdaki kodda bu durum yaşanıyor. Başka bir ifadeyle, özellik ve gezinti değişiklikleri herhangi bir EF Core yöntemi kullanılarak değil doğrudan varlık örneklerinde yapılır.

Varlıkları aynı şekilde değiştiren ancak bu kez EF Core yöntemlerini kullanan aşağıdaki kodla karşıtlık yapın:

using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(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);

Bu durumda değişiklik izleyicisi hata ayıklama görünümü, değişikliklerin algılanmamasına rağmen tüm varlık durumlarının ve özellik değişikliklerinin bilindiğini gösterir. Bunun nedeni PropertyEntry.CurrentValue bir EF Core yöntemi olmasıdır. Bu, EF Core'un bu yöntem tarafından yapılan değişikliği hemen bildiği anlamına gelir. Benzer şekilde çağırma DbContext.Add , EF Core'un yeni varlık hakkında hemen bilgi edinmesini ve uygun şekilde izlemesini sağlar.

Tavsiye

Varlık değişiklikleri yapmak için her zaman EF Core yöntemlerini kullanarak değişiklikleri algılamaktan kaçınmaya çalışma. Bunu yapmak genellikle daha hantaldır ve varlıklarda normal şekilde değişiklik yapmaktan daha az iyi performans gösterir. Bu belgenin amacı, değişikliklerin ne zaman algılanması gerektiğini ve ne zaman gerekmediğini bildirmektir. Amaç, değişiklik algılamanın önlenmesini teşvik etmek değildir.

Değişiklikleri otomatik olarak algılayan yöntemler

DetectChanges() , bunu yapmanın sonuçları etkileme olasılığı olan yöntemler tarafından otomatik olarak çağrılır. Bu yöntemler şunlardır:

Değişiklikleri algılamanın izlenen varlıkların tüm grafiği yerine yalnızca tek bir varlık örneğinde gerçekleştiği bazı yerler de vardır. Bu yerler şunlardır:

  • kullanırken DbContext.Entryvarlığın durumunun ve değiştirilen özelliklerinin up-to-date olduğundan emin olun.
  • EntityEntry gibi Property, Collection, Reference veya Member yöntemleri kullanılırken, özellik değişikliklerinin, geçerli değerlerin, vb. up-to-tarihe uygun olduğundan emin olunmalıdır.
  • Gerekli bir ilişki kesildiği için bağımlı/alt varlık silinecekse. Bu, bir varlığın yeniden üst öğe atanması nedeniyle silinmemesi gerektiği durumları algılar.

Tek bir varlık için değişikliklerin yerel olarak algılanması, çağrılarak EntityEntry.DetectChanges()açıkça tetiklenebilir.

Uyarı

Yerel algılama değişiklikleri, tam algılamanın bulabileceği bazı değişiklikleri kaçırabilir. Diğer varlıklarda algılanmayan değişikliklerden kaynaklanan art arda eylemlerin söz konusu varlık üzerinde etkisi olduğunda bu durum ortaya çıkar. Böyle durumlarda, uygulama ChangeTracker.DetectChanges() çağrısı yaparak tüm varlıkların tam taramasını zorlamak zorunda kalabilir.

Otomatik değişiklik algılamayı devre dışı bırakma

Değişiklikleri algılama performansı çoğu uygulama için bir performans sorunu değildir. Ancak, değişikliklerin algılanması binlerce varlığı izleyen bazı uygulamalar için bir performans sorunu haline gelebilir. (Tam sayı, varlıktaki özelliklerin sayısı gibi birçok şeye bağımlıdır.) Bu nedenle değişiklikleri otomatik algılama özelliği kullanılarak ChangeTracker.AutoDetectChangesEnableddevre dışı bırakılabilir. Örneğin, yük içeren çoka çok ilişkilerdeki birleştirme varlıklarını işlemeyi göz önünde bulundurun.

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    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 await base.SaveChangesAsync(cancellationToken); // Avoid automatically detecting changes again here
    }
    finally
    {
        ChangeTracker.AutoDetectChangesEnabled = true;
    }
}

Önceki bölümden bildiğimiz gibi, hem ChangeTracker.Entries<TEntity>() hem de DbContext.SaveChanges değişiklikleri otomatik olarak algılar. Ancak, Girdileri çağırdıktan sonra kod herhangi bir varlık veya özellik durumu değişikliği yapmaz. (Eklenen varlıklarda normal özellik değerlerinin ayarlanması herhangi bir durum değişikliğine neden olmaz.) Bu nedenle kod, temel SaveChanges yöntemine çağrı yaparken gereksiz otomatik değişiklik algılamayı devre dışı bırakır. Kod ayrıca SaveChanges başarısız olsa bile varsayılan ayarın geri yüklendiğinden emin olmak için try/finally bloğunu kullanır.

Tavsiye

Kodunuzun iyi performans için otomatik değişiklik algılamayı devre dışı bırakması gerektiğini varsaymayın. Bu yalnızca birçok varlığı izleyen bir uygulama profili oluşturulurken gereklidir ve değişiklik algılama performansının bir sorun olduğunu gösterir.

Değişiklikleri ve değer dönüştürmelerini algılama

Varlık türüyle anlık görüntü değişikliği izlemeyi kullanmak için EF Core'un şunları yapabilmesi gerekir:

  • Varlık izlendiğinde her bir özellik değerinin anlık görüntüsü alın.
  • Bu değeri özelliğin geçerli değeriyle karşılaştırın
  • Değer için bir karma kodu oluştur

Bu, doğrudan veritabanına eşlenebilen türler için EF Core tarafından otomatik olarak işlenir. Ancak, bir özelliği eşlemek için bir değer dönüştürücüsü kullanıldığında, bu dönüştürücü bu eylemlerin nasıl gerçekleştirileceğini belirtmelidir. Bu, bir değer karşılaştırıcı ile elde edilir ve Değer Karşılaştırıcıları belgelerinde ayrıntılı olarak açıklanmıştır.

Bildirim unsurları

Çoğu uygulama için anlık görüntü değişikliği izlemesi önerilir. Ancak, birçok varlığı izleyen ve/veya bu varlıklarda birçok değişiklik yapan uygulamalar, özellikleri ve gezinti değerleri değiştiğinde EF Core'a otomatik olarak bildirimde bulunan varlıkların uygulanmasından yararlanabilir. Bunlar "bildirim varlıkları" olarak bilinir.

Bildirim varlıklarını uygulama

Bildirim varlıkları, .NET temel sınıf kitaplığı (BCL) parçası olan INotifyPropertyChanging ve INotifyPropertyChanged arabirimlerini kullanır. Bu arabirimler, bir özellik değeri değiştirilmeden önce ve sonra tetiklenecek olayları tanımlar. Örneğin:

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

Buna ek olarak, tüm koleksiyon gezintilerinin uygulaması INotifyCollectionChangedgerekir; yukarıdaki örnekte bu, gönderilerden biri ObservableCollection<T> kullanılarak karşılanır. EF Core ayrıca kararlı sıralamanın maliyeti karşılığında daha verimli aramalar sunan bir ObservableHashSet<T> uygulamayla birlikte de kullanıma alınır.

Bu bildirim kodunun çoğu genellikle eşlenmemiş bir temel sınıfa taşınır. Örneğin:

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

Bildirim varlıklarını yapılandırma

EF Core'un INotifyPropertyChanging veya INotifyPropertyChanged'in EF Core ile kullanım için tamamen uygulanmış olduğunu doğrulamasının hiçbir yolu yoktur. Özellikle, bu arabirimler bazı durumlarda EF Core'un gerektirdiği tüm özelliklerde (navigasyonlar dahil) değil, yalnızca belirli özelliklerde bildirimlerle çalışmaktadır. Bu nedenle EF Core bu olaylara otomatik olarak bağlanmaz.

Bunun yerine, EF Core'un bu bildirim varlıklarını kullanacak şekilde yapılandırılması gerekir. Bu işlem genellikle ModelBuilder.HasChangeTrackingStrategy çağrılarak tüm varlık türleri için yapılır. Örneğin:

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

(farklı varlık türleri için strateji, EntityTypeBuilder.HasChangeTrackingStrategy kullanılarak farklı şekilde ayarlanabilir, ancak bildirimde bulunmayan varlık türleri için DetectChanges hala gerekli olduğundan, bu genellikle ters etki yaratır.)

Tam bildirim değişikliği takibi için hem INotifyPropertyChanging hem INotifyPropertyChanged uygulanmalıdır. Bu, özgün değerlerin özellik değeri değiştirilmeden hemen önce kaydedilmesine olanak tanır ve varlığı takip ederken EF Core'un anlık görüntü oluşturma gereksinimini önler. Yalnızca INotifyPropertyChanged uygulayan varlık türleri EF Core ile de kullanılabilir. Bu durumda EF, özgün değerleri izlemek için bir varlığı izlerken anlık görüntü oluşturmaya devam eder, ancak ardından DetectChanges'in çağrılması yerine değişiklikleri hemen algılamak için bildirimleri kullanır.

Farklı ChangeTrackingStrategy değerler aşağıdaki tabloda özetlenir.

DeğişiklikTakipStratejisi Gerekli arabirimler DetectChanges özelliği gerekiyor Anlık görüntüler özgün değerleri
Anlık Görüntü Hiç kimse Evet Evet
DeğiştirilenBildirimler INotifyPropertyChanged Hayı Evet
Değiştirilen ve Değişmekte Olan Bildirimler INotifyPropertyChanged ve INotifyPropertyChanging Hayı Hayı
DeğişenVeDeğiştirilenBildirimlerOrijinalDeğerlerle INotifyPropertyChanged ve INotifyPropertyChanging Hayı Evet

Bildirim varlıkları kullanma

Bildirim varlıkları, diğer varlıklar gibi davranır, ancak varlık örneklerinde yapılan değişiklikleri algılamak için ChangeTracker.DetectChanges() çağrısına ihtiyaç duyulmaz. Örneğin:

using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(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);

Normal varlıklarda değişiklik izleyicisi hata ayıklama görünümü DetectChanges çağrılana kadar bu değişikliklerin algılanmadığını gösterdi. Bildirim varlıkları kullanıldığında hata ayıklama görünümüne bakmak, bu değişikliklerin hemen algılandığını gösterir:

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}

Değişiklik izleme proxy'leri

EF Core, INotifyPropertyChanging ve INotifyPropertyChanged uygulayan proxy türlerini dinamik olarak oluşturabilir. Bunun için Microsoft.EntityFrameworkCore.Proxies NuGet paketinin yüklenmesi ve UseChangeTrackingProxies değişiklik izleme proxy'lerinin etkinleştirilmesi gerekir. Örneğin:

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

Dinamik ara sunucu oluşturmak, varlık türünden devralan ve ardından tüm özellik ayarlayıcılarını geçersiz kılan yeni, dinamik bir .NET türü ( Castle.Core proxy'leri uygulamasını kullanarak) oluşturmayı içerir. Bu nedenle proxy'ler için varlık türlerinin devralınabilecek türler olması ve geçersiz kılınabilecek özelliklere sahip olması gerekir. Ayrıca, açıkça oluşturulan koleksiyon gezintilerinin INotifyCollectionChanged'yi uygulaması gerekir. Örneğin:

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

Değişiklik izleme proxy'lerinin önemli bir dezavantajı, EF Core'un her zaman sadece proxy örneklerini izlemesi, temel alınan varlık türlerinin örneklerini asla izlememesi gerektiğidir. Bunun nedeni, temel alınan varlık türünün örneklerinin bildirim oluşturmamasıdır ve bu da bu varlıklarda yapılan değişikliklerin kaçırılacağı anlamına gelir.

EF Core, veritabanını sorgularken otomatik olarak ara sunucu örnekleri oluşturur, bu nedenle bu dezavantaj genellikle yeni varlık örneklerini izlemekle sınırlıdır. Bu örneklerin uzantı yöntemleri kullanılarak CreateProxy oluşturulması ve normal şekilde değilnew yapılması gerekir. Bu, önceki örneklerde yer alan kodun şu şekilde kullanılması CreateProxygerektiği anlamına gelir:

using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(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);

Değişiklik izleme olayları

EF Core, bir varlık ilk kez izlemeye alındığında ChangeTracker.Tracked olayını tetikler. Gelecekteki varlık durumu değişiklikleri ChangeTracker.StateChanged olaylarına neden olur. Daha fazla bilgi için bkz. EF Core'da .NET Olayları .

Uyarı

Bir varlık ilk kez izlendiğinde, durum StateChanged'den diğer durumlardan birine değişmiş olsa bile Detached olayı tetiklenmez. Tüm ilgili bildirimleri almak için hem StateChanged olaylarını hem de Tracked olaylarını dinlediğinizden emin olun.