Devralma

EF bir .NET türü hiyerarşisini bir veritabanına eşleyebilir. Bu, .NET varlıklarınızı temel ve türetilmiş türleri kullanarak her zamanki gibi koda yazmanızı ve EF'nin uygun veritabanı şemasını sorunsuz bir şekilde oluşturmasını, sorgular oluşturmasını vb. sağlar. Tür hiyerarşisinin nasıl eşlendiğine ilişkin gerçek ayrıntılar sağlayıcıya bağımlıdır; bu sayfada ilişkisel veritabanı bağlamında devralma desteği açıklanmaktadır.

Varlık türü hiyerarşi eşlemesi

Kural gereği EF, temel veya türetilmiş türleri otomatik olarak taramaz; Bu, hiyerarşinizdeki bir CLR türünün eşlenmesi istiyorsanız modelinizde bu türü açıkça belirtmeniz gerektiği anlamına gelir. Örneğin, bir hiyerarşinin yalnızca temel türünü belirtmek EF Core'un tüm alt türlerini örtük olarak içermesine neden olmaz.

Aşağıdaki örnek, ve alt sınıfı RssBlogiçin Blog bir DbSet'i kullanıma sunar. Başka bir alt sınıfı varsa Blog modele dahil edilmeyecektir.

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<RssBlog> RssBlogs { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class RssBlog : Blog
{
    public string RssUrl { get; set; }
}

Dekont

Veritabanı sütunları, TPH eşlemesi kullanılırken gerektiğinde otomatik olarak null yapılabilir hale gelir. Örneğin, normal Blog örneklerin RssUrl bu özelliği olmadığından sütun null atanabilir.

Hiyerarşideki bir veya daha fazla varlık için bir DbSet kullanıma açmak istemiyorsanız, bunların modele dahil olduğundan emin olmak için Fluent API'sini de kullanabilirsiniz.

Bahşiş

Kuralları kullanmıyorsanız, kullanarak HasBaseTypetemel türü açıkça belirtebilirsiniz. Bir varlık türünü hiyerarşiden kaldırmak için de kullanabilirsiniz .HasBaseType((Type)null) .

Hiyerarşi başına tablo ve ayrıştırıcı yapılandırması

Varsayılan olarak, EF devralmayı hiyerarşi başına tablo (TPH) desenini kullanarak eşler. TPH, hiyerarşideki tüm türlerin verilerini depolamak için tek bir tablo kullanır ve her satırın hangi türü temsil ettiği belirlemek için ayrıştırıcı sütunu kullanılır.

Yukarıdaki model aşağıdaki veritabanı şemasına eşlenmiştir (her satırda hangi türün Blog depolandığını tanımlayan örtük olarak oluşturulan Discriminator sütunu not edin).

Screenshot of the results of querying the Blog entity hierarchy using table-per-hierarchy pattern

Ayırıcı sütunun adını ve türünü ve hiyerarşideki her türü tanımlamak için kullanılan değerleri yapılandırabilirsiniz:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasDiscriminator<string>("blog_type")
        .HasValue<Blog>("blog_base")
        .HasValue<RssBlog>("blog_rss");
}

Yukarıdaki örneklerde EF, ayrımcıyı örtük olarak hiyerarşinin temel varlığında gölge özelliği olarak eklemektedir. Bu özellik diğer özellikler gibi yapılandırılabilir:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property("Discriminator")
        .HasMaxLength(200);
}

Son olarak, ayrımcı varlığınızdaki normal bir .NET özelliğine de eşlenebilir:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasDiscriminator(b => b.BlogType);

    modelBuilder.Entity<Blog>()
        .Property(e => e.BlogType)
        .HasMaxLength(200)
        .HasColumnName("blog_type");
        
    modelBuilder.Entity<RssBlog>();
}

TPH desenini kullanan türetilmiş varlıkları sorgularken, EF Core sorgudaki ayırıcı sütuna bir koşul ekler. Bu filtre, sonuçta bulunmayan temel türler veya eşdüzey türler için ek satır almamamızı sağlar. Bu filtre koşulu, temel varlık türü için atlanır, çünkü temel varlığı sorgulamak hiyerarşideki tüm varlıkların sonuçlarını alır. Bir sorgudan sonuçlar elde ederken modeldeki herhangi bir varlık türüne eşlenmeyen bir ayırıcı değerle karşılaşırsak, sonuçların nasıl gerçekleştirilmesi gerektiğini bilmediğimiz için bir özel durum oluştururuz. Bu hata yalnızca veritabanınızda EF modelinde eşlenmeyen ayırıcı değerleri içeren satırlar varsa oluşur. Bu tür verileriniz varsa, hiyerarşideki herhangi bir türü sorgulamak için her zaman filtre koşulu eklememiz gerektiğini belirtmek için EF Core modelindeki ayırıcı eşlemeyi tamamlanmamış olarak işaretleyebilirsiniz. IsComplete(false) ayrımcı yapılandırmasında çağrısı eşlemenin tamamlanmamış olduğunu işaretler.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasDiscriminator()
        .IsComplete(false);
}

Paylaşılan sütunlar

Varsayılan olarak, hiyerarşideki iki eşdüzey varlık türü aynı ada sahip bir özelliğe sahip olduğunda, bunlar iki ayrı sütuna eşlenir. Ancak, türleri aynıysa, aynı veritabanı sütununa eşlenebilir:

public class MyContext : DbContext
{
    public DbSet<BlogBase> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Url)
            .HasColumnName("Url");

        modelBuilder.Entity<RssBlog>()
            .Property(b => b.Url)
            .HasColumnName("Url");
    }
}

public abstract class BlogBase
{
    public int BlogId { get; set; }
}

public class Blog : BlogBase
{
    public string Url { get; set; }
}

public class RssBlog : BlogBase
{
    public string Url { get; set; }
}

Dekont

SQL Server gibi ilişkisel veritabanı sağlayıcıları, atama kullanırken paylaşılan sütunları sorgularken otomatik olarak ayrıştırıcı koşulunu kullanmaz. Sorgu Url = (blog as RssBlog).Url eşdüzey Blog satırların Url değerini de döndürür. Sorguyu varlıklarla kısıtlamak için RssBlog ayırıcıya el ile bir filtre eklemeniz gerekir; örneğin Url = blog is RssBlog ? (blog as RssBlog).Url : null: .

Tür başına tablo yapılandırması

TPT eşleme deseninde, tüm türler tek tek tablolara eşlenir. Yalnızca bir temel türe veya türetilmiş türe ait özellikler, bu türle eşlenen bir tabloda depolanır. Türetilmiş türlerle eşlenen tablolar, türetilmiş tabloyu temel tabloyla birleştiren bir yabancı anahtar da depolar.

modelBuilder.Entity<Blog>().ToTable("Blogs");
modelBuilder.Entity<RssBlog>().ToTable("RssBlogs");

Bahşiş

Her varlık türünde çağırmak ToTable yerine her kök varlık türünde çağırabilirsiniz modelBuilder.Entity<Blog>().UseTptMappingStrategy() ve tablo adları EF tarafından oluşturulur.

Bahşiş

Her tablodaki birincil anahtar sütunları için farklı sütun adları yapılandırmak için bkz . Tabloya özgü model yapılandırması.

EF yukarıdaki model için aşağıdaki veritabanı şemasını oluşturur.

CREATE TABLE [Blogs] (
    [BlogId] int NOT NULL IDENTITY,
    [Url] nvarchar(max) NULL,
    CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId])
);

CREATE TABLE [RssBlogs] (
    [BlogId] int NOT NULL,
    [RssUrl] nvarchar(max) NULL,
    CONSTRAINT [PK_RssBlogs] PRIMARY KEY ([BlogId]),
    CONSTRAINT [FK_RssBlogs_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([BlogId]) ON DELETE NO ACTION
);

Dekont

Birincil anahtar kısıtlaması yeniden adlandırılırsa, yeni ad hiyerarşiye eşlenen tüm tablolara uygulanır, gelecekteki EF sürümleri kısıtlamanın yalnızca sorun 19970 düzeltildiğinde belirli bir tablo için yeniden adlandırılabilmesine izin verir.

Toplu yapılandırma kullanıyorsanız, çağırarak GetColumnName(IProperty, StoreObjectIdentifier)belirli bir tablonun sütun adını alabilirsiniz.

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    var tableIdentifier = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);

    Console.WriteLine($"{entityType.DisplayName()}\t\t{tableIdentifier}");
    Console.WriteLine(" Property\tColumn");

    foreach (var property in entityType.GetProperties())
    {
        var columnName = property.GetColumnName(tableIdentifier.Value);
        Console.WriteLine($" {property.Name,-10}\t{columnName}");
    }

    Console.WriteLine();
}

Uyarı

Çoğu durumda TPT, TPH ile karşılaştırıldığında daha alt performans gösterir. Daha fazla bilgi için performans belgelerine bakın.

Dikkat

Türetilmiş bir türün sütunları farklı tablolara eşlenir, bu nedenle hem devralınan hem de bildirilen özellikleri kullanan bileşik FK kısıtlamaları ve dizinleri veritabanında oluşturulamaz.

Somut tür başına tablo yapılandırması

Dekont

Beton türü başına tablo (TPC) özelliği EF Core 7.0'da kullanıma sunulmuştur.

TPC eşleme düzeninde, tüm türler tek tek tablolara eşlenir. Her tablo, ilgili varlık türündeki tüm özellikler için sütunlar içerir. Bu, TPT stratejisiyle ilgili bazı yaygın performans sorunlarını giderir.

Bahşiş

EF Ekibi, .NET Veri Topluluğu Standup'ın bir bölümünde TPC eşlemesi hakkında ayrıntılı bir şekilde bilgi verdi ve konuştu. Tüm Topluluk Standup bölümlerinde olduğu gibi, TPC bölümünü artık YouTube'da izleyebilirsiniz.

modelBuilder.Entity<Blog>().UseTpcMappingStrategy()
    .ToTable("Blogs");
modelBuilder.Entity<RssBlog>()
    .ToTable("RssBlogs");

Bahşiş

Her varlık türünde çağırmak ToTable yerine yalnızca her kök varlık türünde çağırmak modelBuilder.Entity<Blog>().UseTpcMappingStrategy() tablo adlarını kurala göre oluşturur.

Bahşiş

Her tablodaki birincil anahtar sütunları için farklı sütun adları yapılandırmak için bkz . Tabloya özgü model yapılandırması.

EF yukarıdaki model için aşağıdaki veritabanı şemasını oluşturur.

CREATE TABLE [Blogs] (
    [BlogId] int NOT NULL DEFAULT (NEXT VALUE FOR [BlogSequence]),
    [Url] nvarchar(max) NULL,
    CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId])
);

CREATE TABLE [RssBlogs] (
    [BlogId] int NOT NULL DEFAULT (NEXT VALUE FOR [BlogSequence]),
    [Url] nvarchar(max) NULL,
    [RssUrl] nvarchar(max) NULL,
    CONSTRAINT [PK_RssBlogs] PRIMARY KEY ([BlogId])
);

TPC veritabanı şeması

TPC stratejisi TPT stratejisine benzer, ancak hiyerarşideki her somut tür için farklı bir tablo oluşturulur, ancak tablolar soyut türler için oluşturulmaz; bu nedenle "somut tür başına tablo" adıdır. TPT'de olduğu gibi, tablonun kendisi kaydedilen nesnenin türünü gösterir. Ancak, TPT eşlemesinin aksine, her tablo somut türdeki ve temel türlerindeki her özelliğin sütunlarını içerir. TPC veritabanı şemaları normal dışıdır.

Örneğin, şu hiyerarşiyi eşlemeyi göz önünde bulundurun:

public abstract class Animal
{
    protected Animal(string name)
    {
        Name = name;
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public abstract string Species { get; }

    public Food? Food { get; set; }
}

public abstract class Pet : Animal
{
    protected Pet(string name)
        : base(name)
    {
    }

    public string? Vet { get; set; }

    public ICollection<Human> Humans { get; } = new List<Human>();
}

public class FarmAnimal : Animal
{
    public FarmAnimal(string name, string species)
        : base(name)
    {
        Species = species;
    }

    public override string Species { get; }

    [Precision(18, 2)]
    public decimal Value { get; set; }

    public override string ToString()
        => $"Farm animal '{Name}' ({Species}/{Id}) worth {Value:C} eats {Food?.ToString() ?? "<Unknown>"}";
}

public class Cat : Pet
{
    public Cat(string name, string educationLevel)
        : base(name)
    {
        EducationLevel = educationLevel;
    }

    public string EducationLevel { get; set; }
    public override string Species => "Felis catus";

    public override string ToString()
        => $"Cat '{Name}' ({Species}/{Id}) with education '{EducationLevel}' eats {Food?.ToString() ?? "<Unknown>"}";
}

public class Dog : Pet
{
    public Dog(string name, string favoriteToy)
        : base(name)
    {
        FavoriteToy = favoriteToy;
    }

    public string FavoriteToy { get; set; }
    public override string Species => "Canis familiaris";

    public override string ToString()
        => $"Dog '{Name}' ({Species}/{Id}) with favorite toy '{FavoriteToy}' eats {Food?.ToString() ?? "<Unknown>"}";
}

public class Human : Animal
{
    public Human(string name)
        : base(name)
    {
    }

    public override string Species => "Homo sapiens";

    public Animal? FavoriteAnimal { get; set; }
    public ICollection<Pet> Pets { get; } = new List<Pet>();

    public override string ToString()
        => $"Human '{Name}' ({Species}/{Id}) with favorite animal '{FavoriteAnimal?.Name ?? "<Unknown>"}'" +
           $" eats {Food?.ToString() ?? "<Unknown>"}";
}

SQL Server kullanılırken, bu hiyerarşi için oluşturulan tablolar şunlardır:

CREATE TABLE [Cats] (
    [Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
    [Name] nvarchar(max) NOT NULL,
    [FoodId] uniqueidentifier NULL,
    [Vet] nvarchar(max) NULL,
    [EducationLevel] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Cats] PRIMARY KEY ([Id]));

CREATE TABLE [Dogs] (
    [Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
    [Name] nvarchar(max) NOT NULL,
    [FoodId] uniqueidentifier NULL,
    [Vet] nvarchar(max) NULL,
    [FavoriteToy] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Dogs] PRIMARY KEY ([Id]));

CREATE TABLE [FarmAnimals] (
    [Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
    [Name] nvarchar(max) NOT NULL,
    [FoodId] uniqueidentifier NULL,
    [Value] decimal(18,2) NOT NULL,
    [Species] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_FarmAnimals] PRIMARY KEY ([Id]));

CREATE TABLE [Humans] (
    [Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
    [Name] nvarchar(max) NOT NULL,
    [FoodId] uniqueidentifier NULL,
    [FavoriteAnimalId] int NULL,
    CONSTRAINT [PK_Humans] PRIMARY KEY ([Id]));

Şu noktalara dikkat edin:

  • Nesne modelinde Animal olduğundan veya Pet türleri abstract için tablo yoktur. C# öğesinin soyut türlerin örneklerine izin vermediğini ve bu nedenle soyut tür örneğinin veritabanına kaydedileceği bir durum olmadığını unutmayın.

  • Temel türlerdeki özelliklerin eşlemesi her beton türü için yinelenir. Örneğin, her tablonun bir Name sütunu vardır ve hem Cats hem de Dogs'un bir Vet sütunu vardır.

  • Bazı verilerin bu veritabanına kaydedilmesi aşağıdaki sonuçlara neden olur:

Cats tablosu

Id Ad FoodId Veteriner EducationLevel
Kategori 1 Alice 99ca3e98-b26d-4a0c-d4ae-08da7aca624f Pengelly MBA
2 Mac 99ca3e98-b26d-4a0c-d4ae-08da7aca624f Pengelly Okul öncesi
8 Baxter 5dc5019e-6f72-454b-d4b0-08da7aca624f Bothell Evcil Hayvan Hastanesi Bsc

Köpekler masası

Id Ad FoodId Veteriner FavoriteToy
3 Tost 011aaf6f-d588-4fad-d4ac-08da7aca624f Pengelly Bay Sincap

FarmAnimals tablosu

Id Ad FoodId Değer Türler
4 Clyde 1d495075-f527-4498-d4af-08da7aca624f 100.00 Equus africanus asinus

İnsanlar tablosu

Id Ad FoodId FavoriteAnimalId
5 Wendy 5418fd81-7660-432f-d4b1-08da7aca624f 2
6 Arthur 59b495d4-0414-46bf-d4ad-08da7aca624f Kategori 1
9 Katie boş 8

TPT eşlemesinin aksine, tek bir nesnenin tüm bilgilerinin tek bir tabloda yer aldığına dikkat edin. Ayrıca, TPH eşlemesinin aksine, model tarafından hiçbir zaman kullanılmayan herhangi bir tabloda sütun ve satır birleşimi yoktur. Aşağıda bu özelliklerin sorgular ve depolama için nasıl önemli olabileceğini göreceğiz.

Anahtar oluşturma

Seçilen devralma eşleme stratejisi, birincil anahtar değerlerinin nasıl oluşturulup yönetildiğinin sonuçlarına neden olur. TPH'deki anahtarlar kolaydır çünkü her varlık örneği tek bir tabloda tek bir satırla temsil edilir. Herhangi bir tür anahtar değeri oluşturma kullanılabilir ve ek kısıtlama gerekmez.

TPT stratejisi için, tabloda her zaman hiyerarşinin temel türüne eşlenmiş bir satır vardır. Bu satırda herhangi bir tür anahtar oluşturma kullanılabilir ve diğer tabloların anahtarları yabancı anahtar kısıtlamaları kullanılarak bu tabloya bağlanır.

TPC için işler biraz daha karmaşık hale geliyor. İlk olarak, EF Core'un, varlıkların farklı türleri olsa bile bir hiyerarşideki tüm varlıkların benzersiz bir anahtar değerine sahip olmasını gerektirdiğini anlamak önemlidir. Örneğin, örnek modelimizi kullanarak bir Köpek, Kedi ile aynı kimlik anahtarı değerine sahip olamaz. İkincisi, TPT'nin aksine, anahtar değerlerin yaşadığı ve oluşturulabileceği tek bir yer olarak davranabilen ortak bir tablo yoktur. Bu, basit Identity bir sütunun kullanılamayacağı anlamına gelir.

Sıraları destekleyen veritabanları için anahtar değerleri, her tablo için varsayılan kısıtlamada başvurulan tek bir sıra kullanılarak oluşturulabilir. Bu, yukarıda gösterilen TPC tablolarında kullanılan ve her tablonun aşağıdakilere sahip olduğu stratejidir:

[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence])

AnimalSequence , EF Core tarafından oluşturulan bir veritabanı dizisidir. Bu strateji, SQL Server için EF Core veritabanı sağlayıcısı kullanılırken varsayılan olarak TPC hiyerarşileri için kullanılır. Sıraları destekleyen diğer veritabanları için veritabanı sağlayıcıları da benzer bir varsayılan değere sahip olmalıdır. Hi-Lo desenleri gibi dizileri kullanan diğer anahtar oluşturma stratejileri de TPC ile kullanılabilir.

Standart Kimlik sütunları TPC ile çalışmasa da, her tablo uygun bir tohumla yapılandırılırsa ve her tablo için oluşturulan değerler hiçbir zaman çakışmayacak şekilde artırılırsa Kimlik sütunlarını kullanmak mümkündür. Örnek:

modelBuilder.Entity<Cat>().ToTable("Cats", tb => tb.Property(e => e.Id).UseIdentityColumn(1, 4));
modelBuilder.Entity<Dog>().ToTable("Dogs", tb => tb.Property(e => e.Id).UseIdentityColumn(2, 4));
modelBuilder.Entity<FarmAnimal>().ToTable("FarmAnimals", tb => tb.Property(e => e.Id).UseIdentityColumn(3, 4));
modelBuilder.Entity<Human>().ToTable("Humans", tb => tb.Property(e => e.Id).UseIdentityColumn(4, 4));

Önemli

Bu stratejinin kullanılması, hiyerarşideki toplam tür sayısının önceden bilinmesini gerektirdiğinden türetilmiş türleri daha sonra eklemeyi zorlaştırır.

SQLite dizileri veya Kimlik çekirdek/artışını desteklemez ve bu nedenle TPC stratejisiyle SQLite kullanılırken tamsayı anahtar değeri oluşturma desteklenmez. Ancak, istemci tarafı oluşturma veya genel olarak benzersiz anahtarlar (GUID'ler gibi) SQLite dahil olmak üzere tüm veritabanlarında desteklenir.

Yabancı anahtar kısıtlamaları

TPC eşleme stratejisi, normal olmayan bir SQL şeması oluşturur. Bu, bazı veritabanı temizleyicilerinin buna karşı olmasının bir nedenidir. Örneğin, yabancı anahtar sütununu FavoriteAnimalIdgöz önünde bulundurun. Bu sütundaki değer, bir hayvanın birincil anahtar değeriyle eşleşmelidir. Bu, TPH veya TPT kullanılırken veritabanında basit bir FK kısıtlaması ile zorunlu kılınabilir. Örnek:

CONSTRAINT [FK_Animals_Animals_FavoriteAnimalId] FOREIGN KEY ([FavoriteAnimalId]) REFERENCES [Animals] ([Id])

Ancak TPC kullanırken, belirli bir hayvanın birincil anahtarı, o hayvanın beton türüne karşılık gelen tabloda depolanır. Örneğin, bir kedinin Cats.Id birincil anahtarı sütunda depolanırken, köpeğin birincil anahtarı sütunda Dogs.Id depolanır ve bu şekilde devam edilir. Bu, bu ilişki için bir FK kısıtlaması oluşturulamayacağı anlamına gelir.

Uygulamada, uygulama geçersiz veri eklemeye çalışmadığı sürece bu bir sorun değildir. Örneğin, tüm veriler EF Core tarafından eklenirse ve varlıkları ilişkilendirmek için gezintileri kullanırsa, FK sütununun her zaman geçerli PK değerleri içermesi garanti edilir.

Özet ve rehberlik

Özetle, TPH genellikle çoğu uygulama için uygundur ve çok çeşitli senaryolar için iyi bir varsayılandır, bu nedenle ihtiyacınız yoksa TPC'nin karmaşıklığını eklemeyin. Özel olarak, kodunuz çoğunlukla temel türe göre sorgu yazma gibi birçok türdeki varlıkları sorgulayacaksa, TPC üzerinden TPH'ye doğru eğilin.

Bununla birlikte, TPC aynı zamanda kodunuzun çoğunlukla tek bir yaprak türündeki varlıkları sorguladığında ve karşılaştırmalarınız TPH ile karşılaştırıldığında bir iyileştirme gösterdiğinde kullanmak için iyi bir eşleme stratejisidir.

TPT'yi yalnızca dış etmenler tarafından kısıtlanmışsa kullanın.