Ek Değişiklik İzleme Özellikleri
Bu belge, değişiklik izleme ile ilgili çeşitli özellikleri ve senaryoları kapsar.
Bahşiş
Bu belgede varlık durumlarının ve EF Core değişiklik izlemenin temellerinin anlaşıldığı varsayılır. Bu konular hakkında daha fazla bilgi için EF Core'daki Değişiklik İzleme bakın.
Bahşiş
GitHub’dan örnek kodu indirerek bu belgedeki tüm kodları çalıştırabilir ve hataları ayıklayabilirsiniz.
Add
ile AddAsync
Entity Framework Core (EF Core), bu yöntemin kullanılması veritabanı etkileşimiyle sonuçlanabilir olduğunda zaman uyumsuz yöntemler sağlar. Yüksek performanslı zaman uyumsuz erişimi desteklemeyen veritabanları kullanılırken ek yükü önlemek için zaman uyumlu yöntemler de sağlanır.
DbContext.Add ve DbSet<TEntity>.Add normalde veritabanına erişmez, çünkü bu yöntemler doğal olarak varlıkları izlemeye başlar. Ancak, bazı değer oluşturma biçimleri bir anahtar değeri oluşturmak için veritabanına erişebilir. Bunu yapıp EF Core ile birlikte gelen tek değer oluşturucudur HiLoValueGenerator<TValue>. Bu oluşturucu sık kullanılan bir durumdur; varsayılan olarak hiçbir zaman yapılandırılmaz. Bu, uygulamaların büyük çoğunluğunun kullanmaması Add
AddAsync
gerektiği anlamına gelir.
, ve gibi Update
Attach
diğer benzer yöntemlerin hiçbir zaman yeni anahtar değerleri oluşturmadığından ve Remove
bu nedenle veritabanına erişmeleri gerekmediğinden zaman uyumsuz aşırı yüklemeleri yoktur.
AddRange
, UpdateRange
, AttachRange
ve RemoveRange
DbSet<TEntity>ve DbContext tek bir çağrıda birden çok örneği kabul eden alternatif , Update
, Attach
ve Remove
sürümlerini Add
sağlayın. Bu yöntemler sırasıyla , UpdateRange, AttachRangeve RemoveRange yöntemleridirAddRange.
Bu yöntemler kolaylık sağlamak için sağlanır. Bir "aralık" yönteminin kullanılması, eşdeğer aralık dışı yönteme yapılan birden çok çağrıyla aynı işleve sahiptir. İki yaklaşım arasında önemli bir performans farkı yoktur.
Dekont
Bu, ef6'dan farklıdır; burada AddRange
ve Add
her ikisi de otomatik olarak olarak çağrılır DetectChanges
, ancak birden çok kez çağrılması Add
DetectChanges'in bir kez yerine birden çok kez çağrılmasına neden olur. Bu, EF6'da daha verimli hale getirdi AddRange
. EF Core'da, bu yöntemlerin hiçbiri otomatik olarak öğesini çağırmaz DetectChanges
.
DbContext ile DbSet yöntemleri karşılaştırması
, Update
Attach
, ve Remove
gibi Add
birçok yöntemin hem hem de DbSet<TEntity> DbContextüzerinde uygulamaları vardır. Bu yöntemler normal varlık türleri için tam olarak aynı davranışa sahiptir. Bunun nedeni, varlığın CLR türünün EF Core modelindeki tek bir varlık türüne eşlenmesidir. Bu nedenle CLR türü, varlığın modele nereye uyduğunu tam olarak tanımlar ve bu nedenle kullanılacak DbSet örtük olarak belirlenebilir.
Bu kuralın istisnası, birincil olarak çoka çok birleştirme varlıkları için kullanılan paylaşılan tür varlık türlerinin kullanılmasıdır. Paylaşılan tür varlık türü kullanılırken, kullanılmakta olan EF Core model türü için önce bir DbSet oluşturulmalıdır. Daha sonra , Update
, Attach
ve Remove
gibi Add
yöntemler, hangi EF Core model türünün kullanıldığı konusunda herhangi bir belirsizlik olmadan DbSet'te kullanılabilir.
Paylaşılan tür varlık türleri, çoka çok ilişkilerinde birleştirme varlıkları için varsayılan olarak kullanılır. Paylaşılan türdeki varlık türü, çoka çok ilişkisinde kullanılmak üzere açıkça yapılandırılabilir. Örneğin, aşağıdaki kod birleştirme varlık türü olarak yapılandırılır Dictionary<string, int>
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.SharedTypeEntity<Dictionary<string, int>>(
"PostTag",
b =>
{
b.IndexerProperty<int>("TagId");
b.IndexerProperty<int>("PostId");
});
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<Dictionary<string, int>>(
"PostTag",
j => j.HasOne<Tag>().WithMany(),
j => j.HasOne<Post>().WithMany());
}
Yabancı Anahtarlar ve Gezintiler'in değiştirilmesi, yeni bir birleşim varlık örneğini izleyerek iki varlığın nasıl ilişkilendirileceklerini gösterir. Aşağıdaki kod, birleştirme varlığı için Dictionary<string, int>
kullanılan paylaşılan tür varlık türü için bunu yapar:
using var context = new BlogsContext();
var post = context.Posts.Single(e => e.Id == 3);
var tag = context.Tags.Single(e => e.Id == 1);
var joinEntitySet = context.Set<Dictionary<string, int>>("PostTag");
var joinEntity = new Dictionary<string, int> { ["PostId"] = post.Id, ["TagId"] = tag.Id };
joinEntitySet.Add(joinEntity);
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
context.SaveChanges();
DbContext.Set<TEntity>(String) Varlık türü için bir DbSet oluşturmak için kullanıldığına PostTag
dikkat edin. Bu DbSet daha sonra yeni birleştirme varlık örneğiyle çağrı Add
yapmak için kullanılabilir.
Önemli
Birleştirme varlık türleri için kurala göre kullanılan CLR türü, performansı geliştirmek için gelecek sürümlerde değişebilir. Yukarıdaki kodda olduğu Dictionary<string, int>
gibi açıkça yapılandırılmadığı sürece belirli bir birleştirme varlık türüne bağımlı değildir.
Özellik ve alan erişimi karşılaştırması
Varlık özelliklerine erişim özelliğin yedekleme alanını varsayılan olarak kullanır. Bu verimlidir ve özellik alıcılarını ve ayarlayıcılarını çağırmaktan kaynaklanan yan etkileri tetiklemekten kaçınılır. Örneğin, gecikmeli yükleme, sonsuz döngülerin tetiklenmesinden bu şekilde kaçınır. Modeldeki yedekleme alanlarını yapılandırma hakkında daha fazla bilgi için bkz. Alanları Yedekleme.
Bazen EF Core'un özellik değerlerini değiştirdiğinde yan etkiler oluşturması istenebilir. Örneğin, varlıklara veri bağlama sırasında bir özelliğin ayarlanması, alanı doğrudan ayarlarken gerçekleşmeyen ABD'ye bildirimler oluşturabilir. Bu, aşağıdakiler için değiştirilerek PropertyAccessMode elde edilebilir:
- kullanan modeldeki tüm varlık türleri ModelBuilder.UsePropertyAccessMode
- kullanarak belirli bir varlık türünün tüm özellikleri ve gezintileri EntityTypeBuilder<TEntity>.UsePropertyAccessMode
- Kullanan belirli bir özellik PropertyBuilder.UsePropertyAccessMode
- Kullanarak belirli bir gezinti NavigationBuilder.UsePropertyAccessMode
Özellik erişim modları Field
ve PreferField
EF Core'un özellik değerine yedekleme alanı üzerinden erişmesine neden olur. Benzer şekilde ve PreferProperty
EF Core'un Property
özellik değerine alıcı ve ayarlayıcı aracılığıyla erişmesine neden olur.
veya Property
kullanılıyorsa Field
ve EF Core değere sırasıyla alan veya özellik alıcı/ayarlayıcı üzerinden erişemiyorsa EF Core bir özel durum oluşturur. Bu, EF Core'un düşündüğünüzde her zaman alan/özellik erişimi kullanmasını sağlar.
Öte yandan ve modları, PreferField
PreferProperty
tercih edilen erişimin kullanılması mümkün değilse sırasıyla özellik veya yedekleme alanını kullanmaya geri döner. PreferField
varsayılan değerdir. Bu, EF Core'un kullanabildiği her durumda alanları kullanacağı, ancak bunun yerine bir özelliğe alıcı veya ayarlayıcı üzerinden erişilmesi gerektiğinde başarısız olmadığı anlamına gelir.
FieldDuringConstruction
ve PreferFieldDuringConstruction
EF Core'ı yalnızca varlık örnekleri oluştururken yedekleme alanlarını kullanacak şekilde yapılandırın. Bu, sorguların alıcı ve ayarlayıcı yan etkileri olmadan yürütülmesini sağlarken, EF Core'un daha sonraki özellik değişiklikleri bu yan etkilere neden olur.
Farklı özellik erişim modları aşağıdaki tabloda özetlenir:
PropertyAccessMode | Tercih | Varlık oluşturma tercihi | Geri dönüş | Geri dönüş varlık oluşturma |
---|---|---|---|---|
Field |
Alan | Alan | Atar | Atar |
Property |
Özellik | Özellik | Atar | Atar |
PreferField |
Alan | Alan | Özellik | Özellik |
PreferProperty |
Özellik | Özellik | Alan | Alan |
FieldDuringConstruction |
Özellik | Alan | Alan | Atar |
PreferFieldDuringConstruction |
Özellik | Alan | Alan | Özellik |
Geçici değerler
EF Core, SaveChanges çağrıldığında veritabanı tarafından oluşturulan gerçek anahtar değerlerine sahip olacak yeni varlıkların izlenmesi sırasında geçici anahtar değerleri oluşturur. Bu geçici değerlerin nasıl kullanıldığına genel bakış için ef core'daki Değişiklik İzleme bakın.
Geçici değerlere erişme
Geçici değerler değişiklik izleyicisinde depolanır ve doğrudan varlık örneklerine ayarlanmaz. Ancak, bu geçici değerler İzlenen Varlıklara Erişmeye yönelik çeşitli mekanizmalar kullanılırken ortaya çıkar. Örneğin, aşağıdaki kod kullanarak EntityEntry.CurrentValuesgeçici bir değere erişir:
using var context = new BlogsContext();
var blog = new Blog { Name = ".NET Blog" };
context.Add(blog);
Console.WriteLine($"Blog.Id set on entity is {blog.Id}");
Console.WriteLine($"Blog.Id tracked by EF is {context.Entry(blog).Property(e => e.Id).CurrentValue}");
Bu kodun çıktısı şu şekildedir:
Blog.Id set on entity is 0
Blog.Id tracked by EF is -2147482643
PropertyEntry.IsTemporary geçici değerleri denetlemek için kullanılabilir.
Geçici değerleri düzenleme
Bazen geçici değerlerle açıkça çalışmak yararlı olabilir. Örneğin, bir web istemcisinde yeni varlık koleksiyonu oluşturulabilir ve ardından sunucuya geri seri hale getirilebilir. Yabancı anahtar değerleri, bu varlıklar arasındaki ilişkileri ayarlamanın bir yoludur. Aşağıdaki kod, yeni varlıkların grafiğini yabancı anahtarla ilişkilendirmek için bu yaklaşımı kullanır ve SaveChanges çağrıldığında gerçek anahtar değerlerinin oluşturulmasına izin verir.
var blogs = new List<Blog> { new Blog { Id = -1, Name = ".NET Blog" }, new Blog { Id = -2, Name = "Visual Studio Blog" } };
var posts = new List<Post>
{
new Post
{
Id = -1,
BlogId = -1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = -2,
BlogId = -2,
Title = "Disassembly improvements for optimized managed debugging",
Content = "If you are focused on squeezing out the last bits of performance for your .NET service or..."
}
};
using var context = new BlogsContext();
foreach (var blog in blogs)
{
context.Add(blog).Property(e => e.Id).IsTemporary = true;
}
foreach (var post in posts)
{
context.Add(post).Property(e => e.Id).IsTemporary = true;
}
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
context.SaveChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Şu noktalara dikkat edin:
- Negatif sayılar geçici anahtar değerleri olarak kullanılır; bu gerekli değildir, ancak önemli çakışmaları önlemeye yönelik yaygın bir kuraldır.
Post.BlogId
FK özelliğine, ilişkili blogun PK'sı ile aynı negatif değer atanır.- PK değerleri, her varlık izlendikten sonra ayarlanarak IsTemporary geçici olarak işaretlenir. Uygulama tarafından sağlanan tüm anahtar değerlerinin gerçek bir anahtar değeri olduğu varsayıldığı için bu gereklidir.
SaveChanges çağrısından önce değişiklik izleyicisi hata ayıklama görünümüne baktığımızda PK değerlerinin geçici olarak işaretlendiğini ve gönderilerin gezinti düzeltmesi de dahil olmak üzere doğru bloglarla ilişkilendirildiğini gösterir:
Blog {Id: -2} Added
Id: -2 PK Temporary
Name: 'Visual Studio Blog'
Posts: [{Id: -2}]
Blog {Id: -1} Added
Id: -1 PK Temporary
Name: '.NET Blog'
Posts: [{Id: -1}]
Post {Id: -2} Added
Id: -2 PK Temporary
BlogId: -2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: {Id: -2}
Tags: []
Post {Id: -1} Added
Id: -1 PK Temporary
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}
çağrıldıktan SaveChangessonra, bu geçici değerler veritabanı tarafından oluşturulan gerçek değerlerle değiştirilmiştir:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}]
Blog {Id: 2} Unchanged
Id: 2 PK
Name: 'Visual Studio Blog'
Posts: [{Id: 2}]
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}
Tags: []
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: {Id: 2}
Tags: []
Varsayılan değerlerle çalışma
EF Core, bir özelliğin çağrıldığında SaveChanges veritabanından varsayılan değerini almasına olanak tanır. Oluşturulan anahtar değerlerinde olduğu gibi EF Core da yalnızca açıkça ayarlanmamış bir değer varsa veritabanından varsayılan değeri kullanır. Örneğin, aşağıdaki varlık türünü göz önünde bulundurun:
public class Token
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime ValidFrom { get; set; }
}
ValidFrom
özelliği, veritabanından varsayılan bir değer alacak şekilde yapılandırılır:
modelBuilder
.Entity<Token>()
.Property(e => e.ValidFrom)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
Bu tür bir varlık eklerken, EF Core bunun yerine açık bir değer ayarlanmadığı sürece veritabanının değeri oluşturmasına izin verir. Örnek:
using var context = new BlogsContext();
context.AddRange(
new Token { Name = "A" },
new Token { Name = "B", ValidFrom = new DateTime(1111, 11, 11, 11, 11, 11) });
context.SaveChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Değişiklik izleyicisi hata ayıklama görünümüne baktığımızda ilk belirtecin ValidFrom
veritabanı tarafından oluşturulduğunu, ikinci belirtecin ise açıkça ayarlanan değeri kullandığını gösterir:
Token {Id: 1} Unchanged
Id: 1 PK
Name: 'A'
ValidFrom: '12/30/2020 6:36:06 PM'
Token {Id: 2} Unchanged
Id: 2 PK
Name: 'B'
ValidFrom: '11/11/1111 11:11:11 AM'
Dekont
Veritabanı varsayılan değerlerinin kullanılması, veritabanı sütununun yapılandırılmış bir varsayılan değer kısıtlaması olmasını gerektirir. Bu, veya HasDefaultValuekullanılırken HasDefaultValueSql EF Core geçişleri tarafından otomatik olarak gerçekleştirilir. EF Core geçişlerini kullanmadığınızda sütunda varsayılan kısıtlamayı başka bir şekilde oluşturduğunuzdan emin olun.
Null atanabilir özellikleri kullanma
EF Core, özellik değerini bu tür için CLR varsayılanı ile karşılaştırarak bir özelliğin ayarlanıp ayarlanmadığını belirleyebilir. Bu çoğu durumda düzgün çalışır, ancak CLR varsayılanı açıkça veritabanına eklenemez anlamına gelir. Örneğin, tamsayı özelliğine sahip bir varlığı göz önünde bulundurun:
public class Foo1
{
public int Id { get; set; }
public int Count { get; set; }
}
Bu özelliğin veritabanı varsayılanı -1 olacak şekilde yapılandırıldığı yer:
modelBuilder
.Entity<Foo1>()
.Property(e => e.Count)
.HasDefaultValue(-1);
Amaç, açık bir değer ayarlanmadığı her durumda -1 varsayılan değerinin kullanılmasıdır. Ancak, değerin 0 (tamsayılar için CLR varsayılanı) olarak ayarlanması, herhangi bir değer ayarlanmamasından EF Core'a ayırt edilemez, bu özellik için 0 eklenmesinin mümkün olmadığı anlamına gelir. Örnek:
using var context = new BlogsContext();
var fooA = new Foo1 { Count = 10 };
var fooB = new Foo1 { Count = 0 };
var fooC = new Foo1();
context.AddRange(fooA, fooB, fooC);
context.SaveChanges();
Debug.Assert(fooA.Count == 10);
Debug.Assert(fooB.Count == -1); // Not what we want!
Debug.Assert(fooC.Count == -1);
Açıkça 0 olarak ayarlanan örneğin Count
yine de veritabanından varsayılan değeri aldığına dikkat edin; bu bizim istediğimiz şey değildir. Bununla başa çıkmanın kolay bir yolu, özelliği null atanabilir hale getirmektir Count
:
public class Foo2
{
public int Id { get; set; }
public int? Count { get; set; }
}
Bu, CLR'yi 0 yerine varsayılan null yapar; bu da açıkça ayarlandığında 0'ın eklendiği anlamına gelir:
using var context = new BlogsContext();
var fooA = new Foo2 { Count = 10 };
var fooB = new Foo2 { Count = 0 };
var fooC = new Foo2();
context.AddRange(fooA, fooB, fooC);
context.SaveChanges();
Debug.Assert(fooA.Count == 10);
Debug.Assert(fooB.Count == 0);
Debug.Assert(fooC.Count == -1);
Boş değer atanabilir yedekleme alanları kullanma
Özelliği, etki alanı modelinde kavramsal olarak null atanabilir olmaması için null atanabilir hale getirme sorunu. Bu nedenle özelliğin null atanabilir olması zorlanması modelin güvenliğini tehlikeye atabilir.
Özelliği null atanamaz durumda bırakılabilir ve yalnızca yedekleme alanı null atanabilir. Örnek:
public class Foo3
{
public int Id { get; set; }
private int? _count;
public int Count
{
get => _count ?? -1;
set => _count = value;
}
}
Bu, özellik açıkça 0 olarak ayarlanırsa CLR varsayılan değerinin (0) eklenmesini sağlarken, özelliğin etki alanı modelinde null atanabilir olarak kullanıma sunmasına gerek yoktur. Örnek:
using var context = new BlogsContext();
var fooA = new Foo3 { Count = 10 };
var fooB = new Foo3 { Count = 0 };
var fooC = new Foo3();
context.AddRange(fooA, fooB, fooC);
context.SaveChanges();
Debug.Assert(fooA.Count == 10);
Debug.Assert(fooB.Count == 0);
Debug.Assert(fooC.Count == -1);
Bool özellikleri için boş değer atanabilir yedekleme alanları
Bu düzen özellikle depo tarafından oluşturulan varsayılan değerlerle bool özellikleri kullanılırken kullanışlıdır. için bool
CLR varsayılan değeri "false" olduğundan, "false" ifadesi normal desen kullanılarak açıkça eklenemeyeceği anlamına gelir. Örneğin, bir User
varlık türünü göz önünde bulundurun:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
private bool? _isAuthorized;
public bool IsAuthorized
{
get => _isAuthorized ?? true;
set => _isAuthorized = value;
}
}
IsAuthorized
özelliği veritabanı varsayılan değeri olan "true" ile yapılandırılır:
modelBuilder
.Entity<User>()
.Property(e => e.IsAuthorized)
.HasDefaultValue(true);
IsAuthorized
Özellik, eklemeden önce açıkça "true" veya "false" olarak ayarlanabilir veya bu durumda veritabanı varsayılanı kullanılır:
using var context = new BlogsContext();
var userA = new User { Name = "Mac" };
var userB = new User { Name = "Alice", IsAuthorized = true };
var userC = new User { Name = "Baxter", IsAuthorized = false }; // Always deny Baxter access!
context.AddRange(userA, userB, userC);
context.SaveChanges();
SQLite kullanılırken SaveChanges'ten alınan çıktı, Veritabanı varsayılan değerinin Mac için kullanıldığını, Alice ve Baxter için ise açık değerlerin ayarlandığını gösterir:
-- Executed DbCommand (0ms) [Parameters=[@p0='Mac' (Size = 3)], CommandType='Text', CommandTimeout='30']
INSERT INTO "User" ("Name")
VALUES (@p0);
SELECT "Id", "IsAuthorized"
FROM "User"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
-- Executed DbCommand (0ms) [Parameters=[@p0='True' (DbType = String), @p1='Alice' (Size = 5)], CommandType='Text', CommandTimeout='30']
INSERT INTO "User" ("IsAuthorized", "Name")
VALUES (@p0, @p1);
SELECT "Id"
FROM "User"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
-- Executed DbCommand (0ms) [Parameters=[@p0='False' (DbType = String), @p1='Baxter' (Size = 6)], CommandType='Text', CommandTimeout='30']
INSERT INTO "User" ("IsAuthorized", "Name")
VALUES (@p0, @p1);
SELECT "Id"
FROM "User"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
Yalnızca şema varsayılanları
Bazen, EF Core geçişleri tarafından oluşturulan veritabanı şemasında, EF Core'un eklemeler için bu değerleri kullanmadığı durumlarda varsayılan değerlerin olması yararlı olabilir. Bu, özelliği örneğin olarak PropertyBuilder.ValueGeneratedNever yapılandırılarak elde edilebilir:
modelBuilder
.Entity<Bar>()
.Property(e => e.Count)
.HasDefaultValue(-1)
.ValueGeneratedNever();