Dědičnost

EF může namapovat hierarchii typů .NET na databázi. To vám umožní psát entity .NET do kódu obvyklým způsobem, pomocí základních a odvozených typů a bezproblémově vytvářet příslušné schéma databáze, vydávat dotazy atd. Skutečné podrobnosti o mapování hierarchie typů jsou závislé na poskytovateli; Tato stránka popisuje podporu dědičnosti v kontextu relační databáze.

Mapování hierarchie typů entit

Podle konvence ef automaticky neskenuje základní nebo odvozené typy; to znamená, že pokud chcete, aby byl typ CLR v hierarchii mapován, musíte tento typ explicitně zadat ve svém modelu. Například určení pouze základního typu hierarchie způsobí, že EF Core implicitně zahrne všechny jeho podtypy.

Následující ukázka zveřejňuje sadu DbSet pro Blog a její podtřídu RssBlog. Pokud Blog obsahuje jakoukoli jinou podtřídu, nebude součástí modelu.

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; }
}

Poznámka

Při použití mapování TPH se sloupce databáze automaticky dají nastavit jako null. Sloupec je například nullable, RssUrl protože běžné Blog instance nemají danou vlastnost.

Pokud nechcete zveřejnit DbSet jednu nebo více entit v hierarchii, můžete také použít rozhraní Fluent API k zajištění jejich zahrnutí do modelu.

Tip

Pokud se nespoléháte na konvence, můžete zadat základní typ explicitně pomocí HasBaseType. Můžete také použít .HasBaseType((Type)null) k odebrání typu entity z hierarchie.

Konfigurace tabulek na hierarchii a diskriminace

Ef ve výchozím nastavení mapuje dědičnost pomocí vzoru TPH ( table-per-hierarchy ). Funkce TPH používá jednu tabulku k ukládání dat pro všechny typy v hierarchii a k identifikaci typu, který každý řádek představuje, se používá diskriminace.

Výše uvedený model je namapován na následující schéma databáze (všimněte si implicitně vytvořeného DiscriminatorBlog sloupce, který identifikuje typ, který je uložen v každém řádku).

Snímek obrazovky s výsledky dotazování hierarchie entit blogu pomocí vzoru tabulky na hierarchii

Můžete nakonfigurovat název a typ diskriminačního sloupce a hodnoty, které se používají k identifikaci jednotlivých typů v hierarchii:

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

V příkladech výše ef přidala diskriminaci implicitně jako stínovou vlastnost na základní entitu hierarchie. Tuto vlastnost lze nakonfigurovat stejně jako jakoukoli jinou:

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

Diskriminátor může být také namapován na běžnou vlastnost .NET ve vaší entitě:

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");
}

Při dotazování na odvozené entity, které používají vzor TPH, EF Core přidá predikát nad diskriminací sloupce v dotazu. Tento filtr zajistí, že ve výsledku nezístíme žádné další řádky pro základní typy nebo typy siblingu. Tento predikát filtru se vynechá pro typ základní entity, protože dotazování základní entity získá výsledky pro všechny entity v hierarchii. Při materializaci výsledků z dotazu, pokud narazíme na diskriminující hodnotu, která není namapovaná na žádný typ entity v modelu, vyvoláme výjimku, protože nevíme, jak materializovat výsledky. K této chybě dochází pouze v případě, že databáze obsahuje řádky s diskriminujícími hodnotami, které nejsou mapovány v modelu EF. Pokud taková data máte, můžete označit diskriminující mapování v modelu EF Core jako neúplné, abychom měli vždy přidat predikát filtru pro dotazování libovolného typu v hierarchii. IsComplete(false) vyzývá k tomu, aby diskriminující konfigurace znaménka mapování nebyla neúplná.

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

Sdílené sloupce

Pokud ve výchozím nastavení mají dva typy entit na stejné straně v hierarchii vlastnost se stejným názvem, budou namapovány na dva samostatné sloupce. Pokud je ale jejich typ identický, můžete je namapovat na stejný sloupec databáze:

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; }
}

Poznámka

Poskytovatelé relačních databází, například SQL Server, nebudou při dotazování sdílených sloupců při použití přetypování automaticky používat diskriminační predikát. Dotaz Url = (blog as RssBlog).Url by také vrátil Url hodnotu pro řádky na stejné straně Blog . Pokud chcete dotaz omezit na RssBlog entity, musíte ručně přidat filtr pro diskriminující, například Url = blog is RssBlog ? (blog as RssBlog).Url : null.

Konfigurace tabulky na typ

Poznámka

Funkce TPT (table-per-type) byla představena v EF Core 5.0. EF6 podporuje tabulku na konkrétní typ (TPC), ale ef Core ji zatím nepodporuje.

V modelu mapování TPT se všechny typy mapují na jednotlivé tabulky. Vlastnosti, které patří výhradně do základního typu nebo odvozeného typu, jsou uloženy v tabulce, která se mapuje na daný typ. Tabulky mapované na odvozené typy také ukládají cizí klíč, který spojuje odvozenou tabulku se základní tabulkou.

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

EF vytvoří pro výše uvedený model následující schéma databáze.

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

Poznámka

Pokud se omezení primárního klíče přejmenuje, použije se nový název u všech tabulek namapovaných na hierarchii, budoucí verze EF povolí přejmenování omezení pouze pro konkrétní tabulku, když je opraven problém 19970 .

Pokud používáte hromadnou konfiguraci, můžete načíst název sloupce pro konkrétní tabulku voláním GetColumnName(IProperty, StoreObjectIdentifier).

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

Upozornění

V mnoha případech tpT zobrazuje nižší výkon v porovnání s TPH. Další informace najdete v dokumentaci k výkonu.