Değişiklik Algılama ve Bildirimler

Her DbContext örneği, varlıklarda yapılan değişiklikleri izler. Bu izlenen varlıklar da SaveChanges çağrıldığında değişiklikleri veritabanına yönlendirir. Bu, EF Core'daki Değişiklik İzleme 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ılı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.

Bahşiş

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

Anlık görüntü değişikliği izleme

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 güncel izleme bilgileriyle çalıştığından emin olmak için değişikliklerin algılanması başka zamanlarda da gerçekleşir. Değişiklikleri algılamak istediğiniz zaman çağrılarak ChangeTracker.DetectChanges()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 = 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);

Çağrıdan ChangeTracker.DetectChanges() önce değişiklik izleyicisi hata ayıklama görünümüne 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üyle karşıtlık:

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ı ve olarak Addedizlendi.

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

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.

Bahşiş

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.Entry, varlığın durumunun ve değiştirilen özelliklerinin güncel olduğundan emin olmak için.
  • Özellik değişikliklerinin, geçerli değerlerin vb. güncel olduğundan emin olmak için , veya gibi CollectionPropertyyöntemler kullanılırkenEntityEntry. ReferenceMember
  • Gerekli bir ilişki kesildiği için bağımlı/alt varlık silinecekse. Bu, bir varlığın yeniden üst öğesi yapıldığından ne zaman silinmemesi gerektiğini algılar.

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

Dekont

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 uygulamanın açıkça çağırarak ChangeTracker.DetectChanges()tüm varlıkların tam taramasını zorlaması gerekebilir.

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üklerle çoka çok ilişkisinde birleştirme varlıklarını işlemeyi göz önünde bulundurun:

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

Önceki bölümde bildiğimiz gibi, değişiklikleri hem hem de ChangeTracker.Entries<TEntity>()DbContext.SaveChanges 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.

Bahşiş

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 özellik değerinin anlık görüntüsünü alma
  • Bu değeri özelliğin geçerli değeriyle karşılaştırın
  • Değer için karma kod oluşturma

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 varlıkları

Ç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ığının INotifyPropertyChanging (BCL) parçası olan ve INotifyPropertyChanged arabirimlerini kullanır. Bu arabirimler, bir özellik değeri değiştirilmeden önce ve sonra tetiklenecek olayları tanımlar. Örnek:

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ıralamaya bağlı olarak daha verimli aramalar içeren 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. Örnek:

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 EF Core ile kullanım için tam olarak INotifyPropertyChanged uygulandığını doğrulamanın INotifyPropertyChanging bir yolu yoktur. Özellikle, bu arabirimlerin bazı kullanımları, EF Core'un gerektirdiği tüm özelliklerde (gezintiler dahil) değil yalnızca belirli özelliklerde bildirimlerle yapar. 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 çağrılarak ModelBuilder.HasChangeTrackingStrategytüm varlık türleri için yapılır. Örnek:

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

(kullanarak farklı varlık türleri EntityTypeBuilder.HasChangeTrackingStrategyiçin de strateji farklı ayarlanabilir, ancak DetectChanges bildirim varlıkları olmayan türler için hala gerekli olduğundan bu genellikle karşı üretime neden olur.)

Tam bildirim değişikliği izleme için hem hem de INotifyPropertyChangingINotifyPropertyChanged uygulama gerekir. 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.

ChangeTrackingStrategy Gerekli arabirimler DetectChanges gerekiyor Anlık görüntüler özgün değerleri
Anlık Görüntü None Evet Evet
ChangedNotifications Inotifypropertychanged No. Evet
ChangingAndChangedNotifications INotifyPropertyChanged ve INotifyPropertyChanging Hayır Hayır
ChangingAndChangedNotificationsWithOriginalValues INotifyPropertyChanged ve INotifyPropertyChanging No. Evet

Bildirim varlıklarını kullanma

Bildirim varlıkları, varlık örneklerinde değişiklik yapmanın bu değişiklikleri algılamak için ChangeTracker.DetectChanges() çağrı gerektirmemesi dışında diğer varlıklar gibi davranır. Örnek:

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

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, ve INotifyPropertyChangeduygulayan INotifyPropertyChanging proxy türlerini dinamik olarak oluşturabilir. Bunun için Microsoft.EntityFrameworkCore.Proxies NuGet paketinin yüklenmesi ve UseChangeTrackingProxies ö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 de uygulanması INotifyCollectionChanged 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 temel alınan varlık türünün örneklerini değil proxy örneklerini izlemesi 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 CreateProxy kullanılarak normal şekilde değil, uzantı yöntemleri kullanılarak newoluşturulması gerekir. Bu, önceki örneklerde yer alan kodun şu şekilde kullanılması CreateProxygerektiği anlamına gelir:

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

Değişiklik izleme olayları

EF Core, bir varlık ilk kez izlendiğinde olayı tetikler ChangeTracker.Tracked . Gelecekteki varlık durumu değişiklikleri olaylarla sonuçlanır ChangeTracker.StateChanged . Daha fazla bilgi için bkz. EF Core'da .NET Olayları.

Dekont

Durum StateChanged diğer durumlardan birine değişmiş Detached olsa bile, bir varlık ilk kez izlendiğinde olay tetiklenmez. Tüm ilgili bildirimleri almak için hem hem de StateChangedTracked olayları dinlediğinden emin olun.