Freigeben über


Erweiterte Tabellenzuordnung

EF Core bietet eine Menge Flexibilität bei der Zuordnung von Entitätstypen zu Tabellen in einer Datenbank. Dies wird noch nützlicher, wenn Sie eine Datenbank verwenden müssen, die nicht von EF erstellt wurde.

Die folgenden Techniken werden in Bezug auf Tabellen beschrieben, aber dasselbe Ergebnis kann auch bei der Zuordnung zu Ansichten erreicht werden.

Tabellenaufteilung

EF Core ermöglicht das Zuordnen von zwei oder mehr Entitäten zu einer einzelnen Zeile. Dies wird als Tabellenaufteilung oder Tabellenteilung bezeichnet.

Konfiguration

Um Tabellenaufteilungen zu verwenden, müssen die Entitätstypen derselben Tabelle zugeordnet werden, die Primärschlüssel den gleichen Spalten zugeordnet sein, und es muss mindestens eine Beziehung zwischen dem Primärschlüssel eines Entitätstyps und dem eines anderen in derselben Tabelle konfiguriert sein.

Ein häufiges Szenario für die Tabellenaufteilung ist die Verwendung einer Teilmenge der Spalten in der Tabelle für eine höhere Leistung oder Kapselung.

In diesem Beispiel Order ist eine Teilmenge von DetailedOrder.

public class Order
{
    public int Id { get; set; }
    public OrderStatus? Status { get; set; }
    public DetailedOrder DetailedOrder { get; set; }
}
public class DetailedOrder
{
    public int Id { get; set; }
    public OrderStatus? Status { get; set; }
    public string BillingAddress { get; set; }
    public string ShippingAddress { get; set; }
    public byte[] Version { get; set; }
}

Zusätzlich zur erforderlichen Konfiguration rufen wir Property(o => o.Status).HasColumnName("Status") auf, um DetailedOrder.Status derselben Spalte wie Order.Status zuzuordnen.

modelBuilder.Entity<DetailedOrder>(
    dob =>
    {
        dob.ToTable("Orders");
        dob.Property(o => o.Status).HasColumnName("Status");
    });

modelBuilder.Entity<Order>(
    ob =>
    {
        ob.ToTable("Orders");
        ob.Property(o => o.Status).HasColumnName("Status");
        ob.HasOne(o => o.DetailedOrder).WithOne()
            .HasForeignKey<DetailedOrder>(o => o.Id);
        ob.Navigation(o => o.DetailedOrder).IsRequired();
    });

Tipp

Weitere Kontexte finden Sie im vollständigen Beispielprojekt .

Verwendung

Das Speichern und Abfragen von Entitäten mithilfe der Tabellenteilung erfolgt auf die gleiche Weise wie andere Entitäten:

using (var context = new TableSplittingContext())
{
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();

    context.Add(
        new Order
        {
            Status = OrderStatus.Pending,
            DetailedOrder = new DetailedOrder
            {
                Status = OrderStatus.Pending,
                ShippingAddress = "221 B Baker St, London",
                BillingAddress = "11 Wall Street, New York"
            }
        });

    await context.SaveChangesAsync();
}

using (var context = new TableSplittingContext())
{
    var pendingCount = await context.Orders.CountAsync(o => o.Status == OrderStatus.Pending);
    Console.WriteLine($"Current number of pending orders: {pendingCount}");
}

using (var context = new TableSplittingContext())
{
    var order = await context.DetailedOrders.FirstAsync(o => o.Status == OrderStatus.Pending);
    Console.WriteLine($"First pending order will ship to: {order.ShippingAddress}");
}

Optionale abhängige Entität

Wenn alle Spalten, die eine abhängige Entität verwendet, NULL sind NULL in der Datenbank, wird beim Abfragen keine Instanz dafür erstellt. Dies ermöglicht die Modellierung einer optionalen abhängigen Entität, bei der die Beziehungseigenschaft für den Prinzipal null wäre. Beachten Sie, dass dies auch passieren würde, wenn alle Eigenschaften des Abhängigen optional sind und auf null festgelegt sind, was möglicherweise nicht erwartet wird.

Die zusätzliche Überprüfung kann sich jedoch auf die Abfrageleistung auswirken. Darüber hinaus wird es nicht trivial, zu bestimmen, ob eine Instanz erstellt werden soll, wenn der abhängige Entitätstyp eigene Abhängigkeiten hat. Um diese Probleme zu vermeiden, kann der abhängige Entitätstyp als erforderlich gekennzeichnet werden, finden Sie unter "Erforderliche 1:1-Abhängige" weitere Informationen.

Parallelitätstoken

Wenn eines der Entitätstypen, die eine Tabelle gemeinsam nutzen, über ein Parallelitätstoken verfügt, muss sie auch in alle anderen Entitätstypen eingeschlossen werden. Dies ist erforderlich, um einen veralteten Parallelitätstokenwert zu vermeiden, wenn nur eine der Entitäten aktualisiert wird, die derselben Tabelle zugeordnet sind.

Um zu vermeiden, dass das Parallelitätstoken für den verbrauchenden Code verfügbar ist, ist es möglich, ein Token als Schatteneigenschaft zu erstellen:

modelBuilder.Entity<Order>()
    .Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");

modelBuilder.Entity<DetailedOrder>()
    .Property(o => o.Version).IsRowVersion().HasColumnName("Version");

Vererbung

Es wird empfohlen, die dedizierte Seite zur Vererbung zu lesen, bevor Sie mit diesem Abschnitt fortfahren.

Die abhängigen Typen, die die Tabellenteilung verwenden, können eine Vererbungshierarchie aufweisen, aber es gibt einige Einschränkungen:

  • Der abhängige Entitätstyp kann die TPC-Zuordnung nicht verwenden, da die abgeleiteten Typen nicht derselben Tabelle zugeordnet werden können.
  • Der abhängige Entitätstyp kann die TPT-Zuordnung verwenden, aber nur der Stammentitätstyp kann die Tabellenaufteilung verwenden.
  • Wenn der Hauptentitätstyp TPC verwendet, können nur die Entitätstypen, die keine Untergeordneten besitzen, Tabellenaufteilung verwenden. Andernfalls müssten die abhängigen Spalten in den Tabellen dupliziert werden, die den abgeleiteten Typen entsprechen, wobei alle Interaktionen erschwert werden.

Entitätsaufteilung

EF Core ermöglicht das Zuordnen einer Entität zu Zeilen in zwei oder mehr Tabellen. Dies wird als Entitätsteilung bezeichnet.

Konfiguration

Betrachten Sie beispielsweise eine Datenbank mit drei Tabellen, die Kundendaten enthalten:

  • Eine Customers Tabelle für Kundeninformationen
  • Eine PhoneNumbers Tabelle für die Telefonnummer des Kunden
  • Eine Addresses Tabelle für die Adresse des Kunden

Hier sind Definitionen für diese Tabellen in SQL Server:

CREATE TABLE [Customers] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);
    
CREATE TABLE [PhoneNumbers] (
    [CustomerId] int NOT NULL,
    [PhoneNumber] nvarchar(max) NULL,
    CONSTRAINT [PK_PhoneNumbers] PRIMARY KEY ([CustomerId]),
    CONSTRAINT [FK_PhoneNumbers_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE
);

CREATE TABLE [Addresses] (
    [CustomerId] int NOT NULL,
    [Street] nvarchar(max) NOT NULL,
    [City] nvarchar(max) NOT NULL,
    [PostCode] nvarchar(max) NULL,
    [Country] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Addresses] PRIMARY KEY ([CustomerId]),
    CONSTRAINT [FK_Addresses_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE
);

Jede dieser Tabellen wird in der Regel ihrem eigenen Entitätstyp mit Beziehungen zwischen den Typen zugeordnet. Wenn jedoch alle drei Tabellen immer zusammen verwendet werden, kann es praktischer sein, sie einem einzelnen Entitätstyp zuzuordnen. Beispiel:

public class Customer
{
    public Customer(string name, string street, string city, string? postCode, string country)
    {
        Name = name;
        Street = street;
        City = city;
        PostCode = postCode;
        Country = country;
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string? PhoneNumber { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string? PostCode { get; set; }
    public string Country { get; set; }
}

Dies wird in EF7 erreicht, indem SplitToTable für jede Aufteilung im Entitätstyp aufgerufen wird. Mit dem folgenden Code wird der Customer-Entitätstyp beispielsweise auf die oben gezeigten Tabellen Customers, PhoneNumbers und Addresses aufgeteilt.

modelBuilder.Entity<Customer>(
    entityBuilder =>
    {
        entityBuilder
            .ToTable("Customers")
            .SplitToTable(
                "PhoneNumbers",
                tableBuilder =>
                {
                    tableBuilder.Property(customer => customer.Id).HasColumnName("CustomerId");
                    tableBuilder.Property(customer => customer.PhoneNumber);
                })
            .SplitToTable(
                "Addresses",
                tableBuilder =>
                {
                    tableBuilder.Property(customer => customer.Id).HasColumnName("CustomerId");
                    tableBuilder.Property(customer => customer.Street);
                    tableBuilder.Property(customer => customer.City);
                    tableBuilder.Property(customer => customer.PostCode);
                    tableBuilder.Property(customer => customer.Country);
                });
    });

Beachten Sie auch, dass bei Bedarf unterschiedliche Spaltennamen für jede der Tabellen angegeben werden können. Informationen zum Konfigurieren des Spaltennamens für die Haupttabelle finden Sie unter "Tabellenspezifische Facetkonfiguration".

Konfigurieren des Verknüpfungs-Fremdschlüssels

Der FK, der die zugeordneten Tabellen verknüpft, zielt auf die gleichen Eigenschaften, auf denen er deklariert ist. Normalerweise würde sie nicht in der Datenbank erstellt werden, da sie redundant wäre. Es gibt jedoch eine Ausnahme, wenn der Entitätstyp mehreren Tabellen zugeordnet ist. Um seine Facets zu ändern, können Sie die Fluent-API für die Beziehungskonfiguration verwenden:

modelBuilder.Entity<Customer>()
    .HasOne<Customer>()
    .WithOne()
    .HasForeignKey<Customer>(a => a.Id)
    .OnDelete(DeleteBehavior.Restrict);

Einschränkungen

  • Entitätsteilung kann nicht für Entitätstypen in Hierarchien verwendet werden.
  • Für jede Zeile in der Haupttabelle muss in jeder der geteilten Tabellen eine Zeile vorhanden sein (die Fragmente sind nicht optional).

Tabellenspezifische Facetkonfiguration

Einige Zuordnungsmuster führen dazu, dass dieselbe CLR-Eigenschaft einer Spalte in jeder der verschiedenen Tabellen zugeordnet wird. EF7 ermöglicht es diesen Spalten, unterschiedliche Namen zu haben. Betrachten Sie beispielsweise eine einfache Vererbungshierarchie:

public abstract class Animal
{
    public int Id { get; set; }
    public string Breed { get; set; } = null!;
}

public class Cat : Animal
{
    public string? EducationalLevel { get; set; }
}

public class Dog : Animal
{
    public string? FavoriteToy { get; set; }
}

Mit der TPT-Vererbungsstrategie werden diese Typen drei Tabellen zugeordnet. Die Primärschlüsselspalte in jeder Tabelle hat jedoch möglicherweise einen anderen Namen. Beispiel:

CREATE TABLE [Animals] (
    [Id] int NOT NULL IDENTITY,
    [Breed] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])
);

CREATE TABLE [Cats] (
    [CatId] int NOT NULL,
    [EducationalLevel] nvarchar(max) NULL,
    CONSTRAINT [PK_Cats] PRIMARY KEY ([CatId]),
    CONSTRAINT [FK_Cats_Animals_CatId] FOREIGN KEY ([CatId]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE
);

CREATE TABLE [Dogs] (
    [DogId] int NOT NULL,
    [FavoriteToy] nvarchar(max) NULL,
    CONSTRAINT [PK_Dogs] PRIMARY KEY ([DogId]),
    CONSTRAINT [FK_Dogs_Animals_DogId] FOREIGN KEY ([DogId]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE
);

EF7 ermöglicht die Konfiguration dieser Zuordnung mithilfe eines verschachtelten Tabellen-Generators:

modelBuilder.Entity<Animal>().ToTable("Animals");

modelBuilder.Entity<Cat>()
    .ToTable(
        "Cats",
        tableBuilder => tableBuilder.Property(cat => cat.Id).HasColumnName("CatId"));

modelBuilder.Entity<Dog>()
    .ToTable(
        "Dogs",
        tableBuilder => tableBuilder.Property(dog => dog.Id).HasColumnName("DogId"));

Mit der TPC-Vererbungszuordnung kann die Breed Eigenschaft auch verschiedenen Spaltennamen in verschiedenen Tabellen zugeordnet werden. Betrachten Sie beispielsweise die folgenden TPC-Tabellen:

CREATE TABLE [Cats] (
    [CatId] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
    [CatBreed] nvarchar(max) NOT NULL,
    [EducationalLevel] nvarchar(max) NULL,
    CONSTRAINT [PK_Cats] PRIMARY KEY ([CatId])
);

CREATE TABLE [Dogs] (
    [DogId] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
    [DogBreed] nvarchar(max) NOT NULL,
    [FavoriteToy] nvarchar(max) NULL,
    CONSTRAINT [PK_Dogs] PRIMARY KEY ([DogId])
);

EF7 unterstützt diese Tabellenzuordnung:

modelBuilder.Entity<Animal>().UseTpcMappingStrategy();

modelBuilder.Entity<Cat>()
    .ToTable(
        "Cats",
        builder =>
        {
            builder.Property(cat => cat.Id).HasColumnName("CatId");
            builder.Property(cat => cat.Breed).HasColumnName("CatBreed");
        });

modelBuilder.Entity<Dog>()
    .ToTable(
        "Dogs",
        builder =>
        {
            builder.Property(dog => dog.Id).HasColumnName("DogId");
            builder.Property(dog => dog.Breed).HasColumnName("DogBreed");
        });