EF Core’da Değişiklik İzleme
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 belge Entity Framework Core (EF Core) ile değişiklik izlemenin yanı sıra sorgular ve güncelleştirmelerle ilişkisine genel bir bakış sunar.
İpucu
GitHub’dan örnek kodu indirerek bu belgedeki tüm kodları çalıştırabilir ve hataları ayıklayabilirsiniz.
İpucu
Kolaylık olması için, bu belgede SaveChangesAsync gibi zaman uyumsuz eşdeğerleri yerine SaveChanges gibi zaman uyumlu yöntemler kullanılır ve bunlara başvuru yapılır. Aksi belirtilmediği sürece çağrı ve bekleme örneklerinde zaman uyumsuz yöntemler de kullanılabilir.
Varlıkları izleme
Varlık örnekleri şu durumlarda izlenir:
- Veritabanında yürütülen bir sorgu tarafından döndürüldüğünde
Add
,Attach
,Update
veya benzer yöntemlerle DbContext ile açıkça iliştirildiğinde- Mevcut izlenen varlıklara bağlı yeni varlıklar olarak algılandığında
Varlık örnekleri şu durumlarda artık izlenmez:
- DbContext atıldığında
- Değişiklik izleyicisi temizlendi
- Varlıklar açıkça ayrıldığında
DbContext, DbContext Başlatma ve Yapılandırma belgesinde açıklandığı gibi kısa süreli bir iş birimini temsil edecek şekilde tasarlanmıştır. Bu, DbContext’i atmanın, varlıkları izlemeyi durdurmanın normal yolu olduğu anlamına gelir. Başka bir deyişle DbContext’in ömrü şu şekilde olmalıdır:
- DbContext örneğini oluşturma
- Bazı varlıkları izleme
- Varlıklarda bazı değişiklikler yapma
- Veritabanını güncelleştirmek için SaveChanges çağrısı yapma
- DbContext örneğini atma
İpucu
Bu yaklaşım izlendiğinde değişiklik izleyicisini temizlemek veya varlık örneklerini açıkça ayırmak şart değildir. Ancak varlıkları ayırmanız gerekiyorsa ChangeTracker.Clear çağrısı yapmak varlıkları teker teker ayırmaktan daha verimlidir.
Varlık durumları
Her varlık belirli bir EntityState ile ilişkilendirilir:
Detached
varlıkları DbContext tarafından izlenmiyor.Added
varlıkları yeni ve henüz veritabanına eklenmedi. Bu, SaveChanges çağrıldığında eklenecekleri anlamına gelir.Unchanged
varlıkları, veritabanından sorgulandıktan sonra değişmedi. Sorgulardan döndürülen tüm varlıklar başlangıçta bu durumdadır.Modified
varlıkları, veritabanından sorgulandıktan sonra değişti. Bu, SaveChanges çağrıldığında güncelleştirilecekleri anlamına gelir.Deleted
varlıkları veritabanında bulunuyor ancak SaveChanges çağrıldığında silinecek şekilde işaretlendi.
EF Core, değişiklikleri özellik düzeyinde izler. Örneğin yalnızca tek bir özellik değeri değiştirilirse veritabanı güncelleştirmesinde yalnızca bu değer değiştirilir. Ancak özellikler yalnızca varlığın kendisi Değiştirildi durumunda olduğunda değiştirilmiş olarak işaretlenebilir. (Alternatif olarak Değiştirildi durumu, en az bir özellik değerinin değiştirilmiş olarak işaretlendiği anlamına gelir.)
Aşağıdaki tabloda farklı durumlar özetlenmiştir:
Varlık durumu | DbContext tarafından izleniyor | Veritabanında var | Özellikler değiştirildi | SaveChanges eylemi |
---|---|---|---|---|
Detached |
Hayır | - | - | - |
Added |
Evet | Hayır | - | Ekleme |
Unchanged |
Yes | Evet | Hayı | - |
Modified |
Evet | Evet | Yes | Güncelleştir |
Deleted |
Yes | Yes | - | Silme |
Not
Bu metnin rahat anlaşılması için ilişkisel veritabanı terimlerini kullanılmıştır. NoSQL veritabanları genellikle benzer işlemleri destekler ancak büyük olasılıkla farklı adlar kullanılır. Daha fazla bilgi için veritabanı sağlayıcınızın belgelerine bakın.
Sorgulardan izleme
EF Core değişiklik izleme özelliği aynı DbContext örneği hem varlıkları sorgulamak hem de SaveChanges çağrısıyla onları güncelleştirmek için kullanıldığında en iyi performansı sunar. Bunun nedeni EF Core’un sorgulanan varlıkların durumunu otomatik olarak izlemesi ve SaveChanges çağrıldığında bu varlıklarda yapılan değişiklikleri algılamasıdır.
Bu yaklaşımın varlık örneklerini açıkça izlemeye kıyasla çeşitli avantajları vardır:
- Basit bir işlemdir. Varlık durumlarının nadiren açıkça değiştirilmesi gerekir. Durum değişiklikleri EF Core tarafından yapılır.
- Güncelleştirmeler yalnızca gerçekten değişmiş olan değerlerle sınırlıdır.
- Gölge özelliklerin değerleri korunur ve gerektiğinde kullanılır. Bu özellikle yabancı anahtarlar gölge durumunda depolandığında geçerlidir.
- Özelliklerin özgün değerleri otomatik olarak korunur ve verimli güncelleştirmeler için kullanılır.
Basit sorgu ve güncelleştirme
Örneğin, basit bir blog/gönderiler modelini düşünün:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? BlogId { get; set; }
public Blog Blog { get; set; }
}
Bu modeli blogları ve gönderileri sorgulamak ve ardından veritabanında bazı güncelleştirmeler yapmak için kullanabiliriz:
using var context = new BlogsContext();
var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");
blog.Name = ".NET Blog (Updated!)";
foreach (var post in blog.Posts.Where(e => !e.Title.Contains("5.0")))
{
post.Title = post.Title.Replace("5", "5.0");
}
context.SaveChanges();
SaveChanges çağrısının neden olduğu veritabanı güncelleştirmeleri, aşağıdaki örnek SQLite veritabanında gösterilmiştir:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p1='2' (DbType = String), @p0='Announcing F# 5.0' (Size = 17)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "Title" = @p0
WHERE "Id" = @p1;
SELECT changes();
Değişiklik izleyicisi hata ayıklama görünümü, hangi varlıkların izlendiğini ve durumlarının ne olduğunu görselleştirmenin harika bir yoludur. Örneğin SaveChanges çağrısı yapmadan önce yukarıdaki örneğin içine aşağıdaki kodu ekleyin:
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Aşağıdaki çıkışı oluşturur:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: 3}]
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} Modified
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'
Blog: {Id: 1}
Özellikle şu noktalara dikkat edin:
Blog.Name
özelliği değiştirildi (Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
) olarak işaretlenmiştir ve bu da blogunModified
durumunda olmasına neden olur.- 2 numaralı gönderinin
Post.Title
özelliği değiştirildi (Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'
) olarak işaretlenmiştir ve bu da gönderininModified
durumunda olmasına neden olur. - 2 numaralı gönderinin diğer özellik değerleri değiştirilmediğinden değiştirildi olarak işaretlenmez. Bu nedenle bu değerler veritabanı güncelleştirmesine dahil edilmez.
- Diğer gönderide hiçbir değişiklik yapılmamıştır. Bu nedenle hâlâ
Unchanged
durumundadır ve veritabanı güncelleştirmesine dahil edilmemiştir.
Sorgulama ve ardından ekleme, güncelleştirme ve silme
Yukarıdaki örnektekine benzer güncelleştirmeler, ekleme ve silme işlemleriyle aynı iş biriminde birleştirilebilir. Örneğin:
using var context = new BlogsContext();
var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");
// Modify property values
blog.Name = ".NET Blog (Updated!)";
// Insert a new Post
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..."
});
// Mark an existing Post as Deleted
var postToDelete = blog.Posts.Single(e => e.Title == "Announcing F# 5");
context.Remove(postToDelete);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
context.SaveChanges();
Bu örnekte:
- Blog ve ilgili gönderiler veritabanından sorgulandı ve izlendi
Blog.Name
özelliği değiştirildi- Blog için mevcut gönderilerin koleksiyonuna yeni bir gönderi eklendi
- Mevcut bir gönderi DbContext.Remove çağrısıyla silinmek üzere işaretlendi
SaveChanges çağrısından önce değişiklik izleyicisi hata ayıklama görünümüne yeniden bakın. EF Core’un bu değişiklikleri nasıl izlediğini görebilirsiniz:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: 3}, {Id: -2147482638}]
Post {Id: -2147482638} Added
Id: -2147482638 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} Deleted
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}
Şunlara dikkat edin:
- Blog
Modified
olarak işaretlendi. Bu işlem bir veritabanı güncelleştirme işlemi oluşturur. - 2. gönderi
Deleted
olarak işaretlendi işaretlenir. Bu işlem bir veritabanı silme işlemi oluşturur. - Geçici kimliği olan yeni bir gönderi blog 1 ile ilişkilendirildi ve
Added
olarak işaretlendi. Bu işlem bir veritabanı ekleme işlemi oluşturur.
Tüm bunların sonucunda SaveChanges çağrıldığında aşağıdaki veritabanı komutları (SQLite kullanarak) çalıştırılır:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 was released recently and has come with many...' (Size = 56), @p2='What's next for System.Text.Json?' (Size = 33)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
Varlıkları ekleme ve silme hakkında daha fazla bilgi için bkz. Varlıkları Açıkça İzleme. EF Core’un bu tür değişiklikleri otomatik olarak nasıl algıladığı hakkında daha fazla bilgi için bkz. Değişiklik Algılama ve Bildirimler.
İpucu
SaveChanges çağrısının veritabanında güncelleştirmeler yapmasına neden olacak değişikliklerin yapılıp yapılmadığını belirlemek için ChangeTracker.HasChanges() çağrısı yapın. “HasChanges” için “false” değeri döndürülürse SaveChanges işlem yapmayacak demektir.