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 = 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 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 Added
izlendi.
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.
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:
- DbContext.SaveChanges ve DbContext.SaveChangesAsync, veritabanını güncelleştirmeden önce tüm değişikliklerin algılandığından emin olmak için.
- ChangeTracker.Entries() ve ChangeTracker.Entries<TEntity>()varlık durumlarının ve değiştirilen özelliklerin güncel olduğundan emin olmak için.
- ChangeTracker.HasChanges(), sonucun doğru olduğundan emin olmak için.
- ChangeTracker.CascadeChanges(), basamaklanmadan önce asıl/üst varlıklar için doğru varlık durumlarını güvence altına almak için.
- DbSet<TEntity>.Local, izlenen grafiğin güncel olduğundan emin olmak için.
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
Collection
Property
yöntemler kullanılırkenEntityEntry.Reference
Member
- 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 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ü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ı INotifyCollectionChanged
gerekir; 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 INotifyPropertyChanging
INotifyPropertyChanged
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 = 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, 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 new
oluşturulması gerekir. Bu, önceki örneklerde yer alan kodun şu şekilde kullanılması CreateProxy
gerektiğ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 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 StateChanged
Tracked
olayları dinlediğinden emin olun.