Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
EF Core biedt veel flexibiliteit als het gaat om het toewijzen van entiteitstypen aan tabellen in een database. Dit wordt nog nuttiger wanneer u een database moet gebruiken die niet is gemaakt door EF.
De onderstaande technieken worden beschreven in termen van tabellen, maar hetzelfde resultaat kan ook worden bereikt door ze toe te passen op weergaven.
Tabel splitsen
Met EF Core kunnen twee of meer entiteiten aan één rij worden toegewezen. Dit wordt tabel splitsen of delen van tabellen genoemd.
Configuratie
Om tabelsplitsing te gebruiken, moeten de entiteitstypen aan dezelfde tabel worden toegewezen, moeten de primaire sleutels aan dezelfde kolommen worden toegewezen en moet er ten minste één relatie worden geconfigureerd tussen de primaire sleutel van het ene entiteitstype en een ander entiteitstype in dezelfde tabel.
Een veelvoorkomend scenario voor het splitsen van tabellen is het gebruik van slechts een subset van de kolommen in de tabel voor betere prestaties of inkapseling.
In dit voorbeeld Order
wordt een subset van 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; }
}
Naast de vereiste configuratie roepen we Property(o => o.Status).HasColumnName("Status")
aan om DetailedOrder.Status
toe te wijzen aan dezelfde kolom als Order.Status
.
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();
});
Aanbeveling
Zie het volledige voorbeeldproject voor meer context.
Gebruik
Het opslaan en opvragen van entiteiten met behulp van tabelsplitsing wordt op dezelfde manier uitgevoerd als andere entiteiten:
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}");
}
Optionele afhankelijke entiteit
Als alle kolommen die door een afhankelijke entiteit NULL
worden gebruikt, zich in de database bevinden, wordt er geen exemplaar voor gemaakt wanneer er een query wordt uitgevoerd. Hierdoor kan een optionele afhankelijke entiteit worden gemodelleerd, waarbij de relatie-eigenschap voor de principal null zou zijn. Houd er rekening mee dat dit ook zou gebeuren als alle afhankelijke eigenschappen optioneel zijn en null
zijn ingesteld, wat misschien niet verwacht wordt.
De extra controle kan echter van invloed zijn op de queryprestaties. Daarnaast, als het type afhankelijke entiteit zelf afhankelijke entiteiten heeft, wordt het bepalen of er een exemplaar moet worden gemaakt een complexe zaak. Als u deze problemen wilt voorkomen, kunt u het type afhankelijke entiteit als vereist markeren. Zie Vereiste een-op-een-afhankelijke afhankelijkheden voor meer informatie.
Gegevensconsistentietokens
Als een van de entiteitstypen die een tabel delen, een gelijktijdigheidstoken heeft, moet deze ook worden opgenomen in alle andere entiteitstypen. Dit is nodig om een verouderde gelijktijdigheidstokenwaarde te voorkomen wanneer slechts één van de entiteiten die zijn toegewezen aan dezelfde tabel wordt bijgewerkt.
Om te voorkomen dat het gelijktijdigheidstoken wordt blootgesteld aan de verbruikende code, is het mogelijk er een aan te maken als een schaduweigenschap.
modelBuilder.Entity<Order>()
.Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");
modelBuilder.Entity<DetailedOrder>()
.Property(o => o.Version).IsRowVersion().HasColumnName("Version");
Erfenis
Het is raadzaam om de toegewezen pagina over overname te lezen voordat u verdergaat met deze sectie.
De afhankelijke typen met behulp van tabelsplitsing kunnen een overnamehiërarchie hebben, maar er zijn enkele beperkingen:
- Het afhankelijke entiteitstype kan geen TPC-toewijzing gebruiken omdat de afgeleide typen niet kunnen worden toegewezen aan dezelfde tabel.
- Het afhankelijke entiteitstype kan TPT-toewijzing gebruiken, maar alleen het hoofdentiteitstype kan tabelsplitsing gebruiken.
- Als het type principal-entiteit TPC gebruikt, kunnen alleen de entiteitstypen die geen afstammelingen hebben, tabelsplitsing gebruiken. Anders zouden de afhankelijke kolommen moeten worden gedupliceerd in de tabellen die overeenkomen met de afgeleide typen, wat alle interacties zou bemoeilijken.
Entiteit splitsen
Met EF Core kan een entiteit worden toegewezen aan rijen in twee of meer tabellen. Dit wordt entiteit splitsen genoemd.
Configuratie
Denk bijvoorbeeld aan een database met drie tabellen die klantgegevens bevatten:
- Een
Customers
tabel voor klantgegevens - Een
PhoneNumbers
tabel voor het telefoonnummer van de klant - Een
Addresses
tabel voor het adres van de klant
Dit zijn definities voor deze 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
);
Elk van deze tabellen wordt doorgaans toegewezen aan hun eigen entiteitstype, met relaties tussen de typen. Als alle drie de tabellen echter altijd samen worden gebruikt, is het handiger om ze allemaal toe te wijzen aan één entiteitstype. Voorbeeld:
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; }
}
Dit wordt bereikt in EF7 door aan te roepen SplitToTable
voor elke splitsing in het entiteitstype. De volgende code splitst bijvoorbeeld het Customer
-entiteitstype naar de Customers
, PhoneNumbers
, en Addresses
-tabellen zoals hierboven weergegeven.
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);
});
});
U ziet ook dat, indien nodig, verschillende kolomnamen voor elk van de tabellen kunnen worden opgegeven. Zie tabelspecifieke facetconfiguratie om de kolomnaam voor de hoofdtabel te configureren.
Het koppelen van de verwijzende sleutel configureren
De FK die de gekoppelde tabellen aanstuurt, richt zich op dezelfde eigenschappen waarop hij is gedeclareerd. Normaal gesproken zou deze niet worden gemaakt in de database, omdat deze redundant zou zijn. Er is echter een uitzondering op het moment dat het entiteitstype is toegewezen aan meer dan één tabel. Als u de facetten ervan wilt wijzigen, kunt u de fluent-API voor de relatieconfiguratie gebruiken:
modelBuilder.Entity<Customer>()
.HasOne<Customer>()
.WithOne()
.HasForeignKey<Customer>(a => a.Id)
.OnDelete(DeleteBehavior.Restrict);
Beperkingen
- Entiteit splitsen kan niet worden gebruikt voor entiteitstypen in hiërarchieën.
- Voor elke rij in de hoofdtabel moet er een rij in elk van de gesplitste tabellen staan (de fragmenten zijn niet optioneel).
Tabelspecifieke facetconfiguratie
Sommige toewijzingspatronen leiden ertoe dat dezelfde CLR-eigenschap wordt toegewezen aan een kolom in elk van meerdere verschillende tabellen. MET EF7 kunnen deze kolommen verschillende namen hebben. Denk bijvoorbeeld aan een eenvoudige overnamehiërarchie:
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; }
}
Met de TPT-overervingstoewijzingsstrategie worden deze typen toegewezen aan drie tabellen. De primaire sleutelkolom kan echter in elke tabel een andere naam hebben. Voorbeeld:
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
);
MET EF7 kan deze toewijzing worden geconfigureerd met behulp van een geneste opbouwfunctie voor tabellen:
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"));
Met de TPC-overnametoewijzing kan de Breed
eigenschap ook worden toegewezen aan verschillende kolomnamen in verschillende tabellen. Denk bijvoorbeeld aan de volgende 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 ondersteunt deze tabeltoewijzing:
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");
});