Aracılığıyla paylaş


Ek Değişiklik İzleme Özellikleri

Bu belge, değişiklik izleme ile ilgili çeşitli özellikleri ve senaryoları kapsar.

Tavsiye

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 bkz. EF Core'da Değişiklik İzleme .

Tavsiye

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

Add ile AddAsync

Entity Framework Core (EF Core), bir yöntemin kullanımı veritabanı etkileşimiyle sonuçlanabileceğinde 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. EF Core ile birlikte gelen ve bunu yapan tek değer oluşturucu HiLoValueGenerator<TValue>'dir. Bu jeneratörün kullanımı yaygın değildir; varsayılan olarak hiçbir zaman yapılandırılmaz. Bu, uygulamaların büyük çoğunluğunun Add kullanması ve AddAsync kullanmaması gerektiği anlamına gelir.

Diğer benzer yöntemler olan Update, Attach ve Remove, hiçbir zaman yeni anahtar değerleri oluşturmadıkları ve bu nedenle veritabanına erişmeleri gerekmediği için zaman uyumsuz aşırı yüklemelere sahip değildir.

AddRange, UpdateRange, AttachRange ve RemoveRange

DbSet<TEntity> ve DbContext, tek bir çağrıda birden çok örneği kabul eden alternatif Add, Update, Attach ve Remove sürümlerini sağlar. Bu yöntemler sırasıyla , AddRange, UpdateRangeve AttachRange yöntemleridirRemoveRange.

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.

Uyarı

Bu, EF6'dan farklıdır; burada AddRange ve Add her ikisi de otomatik olarak DetectChanges'yi çağırır, ancak Add'ün birden çok kez çağrılması, 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 DetectChanges metodunu otomatik olarak çağırmaz.

DbContext ile DbSet yöntemleri karşılaştırması

Birçok yöntem, Add, Update, Attach ve Remove gibi, hem DbSet<TEntity> hem de 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ı, esas olarak çoka çok ilişki varlıkları için kullanılan paylaşılan türdeki 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 , Add, Updateve Attach gibi Removeyöntemler, hangi EF Core model türünün kullanıldığı konusunda herhangi bir belirsizlik olmadan DbSet'te kullanılabilir.

Çoka çok ilişkiler içinde birleştirilmiş varlıklar için varsayılan olarak paylaşılan türdeki varlık türleri 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 Dictionary<string, int> öğesini birleştirme varlık türü olarak yapılandırır.

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 Yönlendirmelerin Değiştirilmesi, yeni bir birleşim varlık örneği izleyerek iki varlığın nasıl ilişkilendirileceğini 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 = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(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);

await context.SaveChangesAsync();

DbContext.Set<TEntity>(String) kullanılarak PostTag varlık türü için bir DbSet oluşturulduğuna dikkat edin. Bu DbSet, yeni birleştirme varlık örneği ile Add metotunu çağırmak 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. Destekleyici Alanlar modeldeki destekleyici alanları yapılandırma hakkında daha fazla bilgi için bkz.

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 kullanıcı arayüzüne bildirimler oluşturabilir. Bu, PropertyAccessMode değiştirerek elde edilebilir.

Özellik erişim modları Field ve PreferField EF Core'un özellik değerine yedekleme alanı üzerinden erişmesine neden olur. Benzer şekilde, Property ve PreferProperty EF Core'un özellik değerine alma ve ayarlama işlevi yoluyla erişmesine neden olur.

Field veya Property kullanıldığında ve EF Core değere sırasıyla alan veya özellik alıcı/ayarlayıcı üzerinden erişemiyorsa, EF Core bir istisna fırlatır. Bu, EF Core'un, beklediğiniz gibi, her zaman alan/özellik erişimini kullanmasını sağlar.

Öte yandan, PreferField ve PreferProperty modları, 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:

ÖzellikErişimModu Tercih Varlık oluşturma tercihi Geri dönüş Geri dönüş varlık oluşturma
Field Alan Alan Atar Atar
Property Mülkiyet Mülkiyet Atar Atar
PreferField Alan Alan Mülkiyet Mülkiyet
PreferProperty Mülkiyet Mülkiyet Alan Alan
FieldDuringConstruction Mülkiyet Alan Alan Atar
PreferFieldDuringConstruction Mülkiyet Alan Alan Mülkiyet

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 bir bakış için bkz. EF Core'da Değişiklik İzleme .

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 bir varlık koleksiyonu oluşturulabilir ve ardından seri hale getirilip sunucuya geri gönderilebilir. 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);

await context.SaveChangesAsync();

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Şunlara 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.
  • Her varlık izlendiğinde, IsTemporary ayarlanarak PK değerleri 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. Örneğin:

using var context = new BlogsContext();

context.AddRange(
    new Token { Name = "A" },
    new Token { Name = "B", ValidFrom = new DateTime(1111, 11, 11, 11, 11, 11) });

await context.SaveChangesAsync();

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'

Uyarı

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. EF Core geçişleri, HasDefaultValueSql veya HasDefaultValue kullanılırken bunu otomatik olarak gerçekleştirir. EF Core geçişlerini kullanmadığınızda sütunda varsayılan kısıtlamayı başka bir şekilde oluşturduğunuzdan emin olun.

Nullable (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ığı zaman varsayılan -1 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. Örneğin:

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);
await context.SaveChangesAsync();

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);
await context.SaveChangesAsync();

Debug.Assert(fooA.Count == 10);
Debug.Assert(fooB.Count == 0);
Debug.Assert(fooC.Count == -1);

Null değeri kabul edebilen yedekleme alanları kullanma

Özelliği null atanabilir hale getirme sorunu, etki alanı modelinde kavramsal olarak null atanabilir olmamasından kaynaklanmaktadır. 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. Örneğin:

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. Örneğin:

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);
await context.SaveChangesAsync();

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. CLR'nin bool için varsayılan değeri "false" olduğundan, "false" ifadesinin 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);

await context.SaveChangesAsync();

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