Aracılığıyla paylaş


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ı AddAddAsyncgerektiği anlamına gelir.

, ve gibi UpdateAttachdiğ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, AttachRangeve RemoveRange

DbSet<TEntity>ve DbContext tek bir çağrıda birden çok örneği kabul eden alternatif , Update, Attachve Remove sürümlerini Addsağ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ı

, UpdateAttach, ve Removegibi Addbirç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, Attachve Remove gibi Addyö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:

Ö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ı, PreferFieldPreferProperty 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.

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