VererbungInheritance

EF kann eine .net-Typhierarchie einer Datenbank zuordnen.EF can map a .NET type hierarchy to a database. Dies ermöglicht es Ihnen, Ihre .NET-Entitäten wie gewohnt mit Basis-und abgeleiteten Typen in Code zu schreiben und EF nahtlos das geeignete Datenbankschema zu erstellen, Abfragen auszugeben usw. Die tatsächlichen Details, wie eine Typhierarchie zugeordnet wird, sind Anbieter abhängig. Diese Seite beschreibt die Vererbungs Unterstützung im Kontext einer relationalen Datenbank.This allows you to write your .NET entities in code as usual, using base and derived types, and have EF seamlessly create the appropriate database schema, issue queries, etc. The actual details of how a type hierarchy is mapped are provider-dependent; this page describes inheritance support in the context of a relational database.

Entitätstyp-Hierarchie ZuordnungEntity type hierarchy mapping

Gemäß der Konvention scannt EF nicht automatisch nach Basis-oder abgeleiteten Typen. Dies bedeutet, dass Sie den Typ für das Modell explizit angeben müssen, wenn Sie möchten, dass ein CLR-Typ in der Hierarchie zugeordnet wird.By convention, EF will not automatically scan for base or derived types; this means that if you want a CLR type in your hierarchy to be mapped, you must explicitly specify that type on your model. Wenn Sie z. b. nur den Basistyp einer Hierarchie angeben, bewirkt dies nicht, dass EF Core implizit alle seine Untertypen einschließt.For example, specifying only the base type of a hierarchy will not cause EF Core to implicitly include all of its sub-types.

Im folgenden Beispiel wird ein dbset für Blog und seine Unterklasse verfügbar gemacht RssBlog .The following sample exposes a DbSet for Blog and its subclass RssBlog. Wenn Blog eine andere Unterklasse aufweist, wird Sie nicht in das Modell eingeschlossen.If Blog has any other subclass, it will not be included in the model.

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

Hinweis

Bei Verwendung der TPH-Zuordnung werden bei Bedarf automatisch NULL-Werte für Daten Bank Spalten festgelegt.Database columns are automatically made nullable as necessary when using TPH mapping. Beispielsweise kann die-Spalte auf NULL festgelegt werden, RssUrl da reguläre Blog Instanzen nicht über diese Eigenschaft verfügen.For example, the RssUrl column is nullable because regular Blog instances do not have that property.

Wenn Sie DbSet für eine oder mehrere Entitäten in der Hierarchie keinen verfügbar machen möchten, können Sie auch die fließende API verwenden, um sicherzustellen, dass Sie im Modell enthalten sind.If you don't want to expose a DbSet for one or more entities in the hierarchy, you can also use the Fluent API to ensure they are included in the model.

Tipp

Wenn Sie sich nicht auf Konventionen verlassen, können Sie den Basistyp explizit mithilfe von angeben HasBaseType .If you don't rely on conventions, you can specify the base type explicitly using HasBaseType. Sie können auch verwenden .HasBaseType((Type)null) , um einen Entitätstyp aus der Hierarchie zu entfernen.You can also use .HasBaseType((Type)null) to remove an entity type from the hierarchy.

Tabelle pro Hierarchie und diskriminatorkonfigurationTable-per-hierarchy and discriminator configuration

Standardmäßig ordnet EF die Vererbung mithilfe des TPH-Musters ( Table-per Hierarchy ) zu.By default, EF maps the inheritance using the table-per-hierarchy (TPH) pattern. TPH verwendet eine einzelne Tabelle zum Speichern der Daten für alle Typen in der Hierarchie, und eine diskriminatorspalte wird verwendet, um den Typ zu identifizieren, den jede Zeile darstellt.TPH uses a single table to store the data for all types in the hierarchy, and a discriminator column is used to identify which type each row represents.

Das obige Modell ist dem folgenden Datenbankschema zugeordnet (Beachten Sie die implizit erstellte Discriminator Spalte, die angibt, welcher Typ von Blog in den einzelnen Zeilen gespeichert ist).The model above is mapped to the following database schema (note the implicitly created Discriminator column, which identifies which type of Blog is stored in each row).

Screenshot der Ergebnisse der Abfrage der Blog-Entitäts Hierarchie mithilfe eines "Tabelle pro Hierarchie"-Musters

Sie können den Namen und den Typ der diskriminatorspalte und die Werte, die zum Identifizieren der einzelnen Typen in der Hierarchie verwendet werden, konfigurieren:You can configure the name and type of the discriminator column and the values that are used to identify each type in the hierarchy:

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

In den obigen Beispielen wurde der Diskriminator von EF implizit als Schatten Eigenschaft für die Basis Entität der Hierarchie hinzugefügt.In the examples above, EF added the discriminator implicitly as a shadow property on the base entity of the hierarchy. Diese Eigenschaft kann wie jede andere konfiguriert werden:This property can be configured like any other:

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

Schließlich kann der Diskriminator auch einer regulären .net-Eigenschaft in der Entität zugeordnet werden:Finally, the discriminator can also be mapped to a regular .NET property in your entity:

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

Beim Abfragen abgeleiteter Entitäten, die das TPH-Muster verwenden, fügt EF Core in der Abfrage ein Prädikat für die diskriminatorspalte hinzu.When querying for derived entities, which use the TPH pattern, EF Core adds a predicate over discriminator column in the query. Dieser Filter stellt sicher, dass keine zusätzlichen Zeilen für Basis Typen oder gleich geordnete Typen, die nicht im Ergebnis sind, angezeigt werden.This filter makes sure that we don't get any additional rows for base types or sibling types not in the result. Dieses Filter Prädikat wird für den Basis Entitätstyp übersprungen, da Abfragen für die Basis Entität Ergebnisse für alle Entitäten in der Hierarchie erhalten.This filter predicate is skipped for the base entity type since querying for the base entity will get results for all the entities in the hierarchy. Wenn beim Materialisieren der Ergebnisse aus einer Abfrage ein Diskriminatorwert vorhanden ist, der keinem Entitätstyp im Modell zugeordnet ist, wird eine Ausnahme ausgelöst, da wir nicht wissen, wie die Ergebnisse materialisiert werden.When materializing results from a query, if we come across a discriminator value, which isn't mapped to any entity type in the model, we throw an exception since we don't know how to materialize the results. Dieser Fehler tritt nur auf, wenn die Datenbank Zeilen mit diskriminatorwerten enthält, die im EF-Modell nicht zugeordnet sind.This error only occurs if your database contains rows with discriminator values, which aren't mapped in the EF model. Wenn Sie solche Daten haben, können Sie die diskriminatorzuordnung in EF Core Modell als unvollständig kennzeichnen, um anzugeben, dass wir immer ein Filter Prädikat zum Abfragen beliebiger Typen in der Hierarchie hinzufügen sollten.If you have such data, then you can mark the discriminator mapping in EF Core model as incomplete to indicate that we should always add filter predicate for querying any type in the hierarchy. IsComplete(false) der-Befehl für die diskriminatorkonfiguration markiert die Zuordnung als unvollständig.IsComplete(false) call on the discriminator configuration marks the mapping to be incomplete.

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

Freigegebene SpaltenShared columns

Wenn zwei gleich geordnete Entitäts Typen in der Hierarchie über eine Eigenschaft mit demselben Namen verfügen, werden Sie standardmäßig zwei separaten Spalten zugeordnet.By default, when two sibling entity types in the hierarchy have a property with the same name, they will be mapped to two separate columns. Wenn Ihr Typ jedoch identisch ist, können Sie derselben Daten Bank Spalte zugeordnet werden:However, if their type is identical they can be mapped to the same database column:

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

"Tabelle pro Typ"-KonfigurationTable-per-type configuration

Hinweis

Die Funktion "Tabelle pro Typ" (TPT) wurde in EF Core 5,0 eingeführt.The table-per-type (TPT) feature was introduced in EF Core 5.0. "Table-per-Concrete-Type" (TPC) wird von EF6 unterstützt, aber noch nicht von EF Core unterstützt.Table-per-concrete-type (TPC) is supported by EF6, but is not yet supported by EF Core.

Im TPT-Zuordnungsmuster werden alle Typen einzelnen Tabellen zugeordnet.In the TPT mapping pattern, all the types are mapped to individual tables. Eigenschaften, die nur zu einem Basistyp oder einem abgeleiteten Typ gehören, werden in einer Tabelle gespeichert, die diesem Typ zugeordnet ist.Properties that belong solely to a base type or derived type are stored in a table that maps to that type. Tabellen, die abgeleiteten Typen zugeordnet sind, speichern auch einen Fremdschlüssel, der die abgeleitete Tabelle mit der Basistabelle verbindet.Tables that map to derived types also store a foreign key that joins the derived table with the base table.

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

EF erstellt das folgende Datenbankschema für das obige Modell.EF will create the following database schema for the model above.

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

Hinweis

Wenn die PRIMARY KEY-Einschränkung umbenannt wird, wird der neue Name auf alle Tabellen angewendet, die der Hierarchie zugeordnet sind. in zukünftigen EF-Versionen wird das Umbenennen der Einschränkung nur für eine bestimmte Tabelle ermöglicht, wenn das Problem 19970 behoben ist.If the primary key constraint is renamed the new name will be applied to all the tables mapped to the hierarchy, future EF versions will allow renaming the constraint only for a particular table when issue 19970 is fixed.

Wenn Sie die Massen Konfiguration verwenden, können Sie den Spaltennamen für eine bestimmte Tabelle abrufen, indem Sie aufrufen GetColumnName(IProperty, StoreObjectIdentifier) .If you are employing bulk configuration you can retrieve the column name for a specific table by calling 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();
}

Warnung

In vielen Fällen zeigt TPT im Vergleich zu TPH eine geringere Leistung.In many cases, TPT shows inferior performance when compared to TPH. Weitere Informationen findenSie in der Dokumentation zur Leistung.See the performance docs for more information.