Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
EF Core nabízí spoustu flexibility, pokud jde o mapování typů entit na tabulky v databázi. To se stává ještě užitečnější v případě, že potřebujete použít databázi, kterou ef nevytvořil.
Následující techniky jsou popsány z hlediska tabulek, ale stejný výsledek lze dosáhnout i při mapování na zobrazení.
Dělení tabulky
EF Core umožňuje namapovat dvě nebo více entit na jeden řádek. Tomu se říká rozdělení tabulky nebo sdílení tabulek.
Konfigurace
Pokud chcete použít rozdělení tabulek, musí být typy entit namapované na stejnou tabulku, musí být primární klíče namapované na stejné sloupce a alespoň jedna relace nakonfigurovaná mezi primárním klíčem jednoho typu entity a druhou ve stejné tabulce.
Běžným scénářem pro rozdělení tabulky je použití pouze podmnožiny sloupců v tabulce pro vyšší výkon nebo zapouzdření.
V tomto příkladu Order představuje podmnožinu 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; }
}
Kromě požadované konfigurace využijeme Property(o => o.Status).HasColumnName("Status") k mapování DetailedOrder.Status na stejný sloupec jako 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();
});
Návod
Další kontext najdete v úplném ukázkovém projektu .
Použití
Ukládání a dotazování entit pomocí rozdělení tabulky se provádí stejným způsobem jako jiné entity:
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}");
}
Volitelná závislá entita
Pokud jsou všechny sloupce používané závislou entitou v databázi NULL, žádná její instance nebude při dotazování vytvořena. To umožňuje modelování volitelné závislé entity, kde vlastnost relace na hlavním objektu by byla nulová. Všimněte si, že k tomu dojde také v případě, že všechny vlastnosti závislého objektu jsou volitelné a nastavené na nullhodnotu , která nemusí být očekáváná.
Další kontrola však může ovlivnit výkon dotazových operací. Kromě toho pokud závislý typ entity má vlastní závislé entity, pak určení, zda by se instance měla vytvořit, stává netriviální. Pokud se chcete těmto problémům vyhnout, může být závislý typ entity označený jako povinný, další informace najdete v tématu Povinné závislosti 1:1 .
Tokeny souběžnosti
Pokud některý z typů entit sdílející tabulku má token souběžnosti, musí být zahrnutý i ve všech ostatních typech entit. To je nezbytné, aby se zabránilo zastaralé hodnotě tokenu souběžnosti, pokud se aktualizuje pouze jedna z entit mapovaných na stejnou tabulku.
Aby se zabránilo zveřejnění tokenu souběžnosti pro spotřebující kód, je možné, že ho vytvoříte jako stínovou vlastnost:
modelBuilder.Entity<Order>()
.Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");
modelBuilder.Entity<DetailedOrder>()
.Property(o => o.Version).IsRowVersion().HasColumnName("Version");
Dědičnost
Než budete pokračovat v této části, doporučujeme přečíst si vyhrazenou stránku o dědičnosti .
Závislé typy používající rozdělení tabulky můžou mít hierarchii dědičnosti, ale existují určitá omezení:
- Závislý typ entity nemůže použít mapování TPC, protože odvozené typy by nemohly mapovat na stejnou tabulku.
- Závislý typ entity může použít mapování TPT, ale pouze typ kořenové entity může použít rozdělení tabulky.
- Pokud hlavní typ entity používá TPC, rozdělení tabulky mohou použít pouze ty typy entit, které nemají žádné potomky. Jinak by závislé sloupce bylo potřeba duplikovat v tabulkách odpovídajících odvozeným typům a komplikovat všechny interakce.
Rozdělení entity
EF Core umožňuje namapovat entitu na řádky ve dvou nebo více tabulkách. Tomu se říká dělení entit.
Konfigurace
Představte si například databázi se třemi tabulkami, které obsahují zákaznická data:
- Tabulka
Customerss informacemi o zákaznících - Tabulka
PhoneNumberstelefonního čísla zákazníka - Tabulka
Addressesadresy zákazníka
Tady jsou definice pro tyto tabulky v SQL Serveru:
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
);
Každá z těchto tabulek by se obvykle mapovala na vlastní typ entity s relacemi mezi těmito typy. Pokud se ale všechny tři tabulky vždy používají společně, může být vhodnější je namapovat všechny na jeden typ entity. Například:
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; }
}
Toho dosáhnete v EF7 voláním SplitToTable pro každé rozdělení v typu entity. Například následující kód rozdělí Customer typ entity na Customers, PhoneNumbersa Addresses tabulky uvedené výše:
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);
});
});
Všimněte si také, že v případě potřeby je možné pro každou z tabulek zadat různé názvy sloupců. Chcete-li nakonfigurovat název sloupce pro hlavní tabulku, podívejte se na konfiguraci aspektů specifickou pro tabulku.
Konfigurace propojení cizího klíče
FK propojující mapované tabulky cílí na stejné vlastnosti, na které je deklarován. Normálně by se nevytvořila v databázi, protože by byla redundantní. Existuje ale výjimka, kdy je typ entity namapován na více než jednu tabulku. Pokud chcete změnit jeho aspekty, můžete použít rozhraní Fluent API pro konfiguraci relací:
modelBuilder.Entity<Customer>()
.HasOne<Customer>()
.WithOne()
.HasForeignKey<Customer>(a => a.Id)
.OnDelete(DeleteBehavior.Restrict);
Omezení
- Rozdělení entit se nedá použít pro typy entit v hierarchiích.
- Pro každý řádek v hlavní tabulce musí být v každé rozdělené tabulce řádek (fragmenty nejsou volitelné).
Konfigurace specifických aspektů pro tabulku
Některé vzory mapování mají za následek, že se stejná vlastnost CLR mapuje na sloupec v každé z několika různých tabulek. EF7 umožňuje těmto sloupcům mít různé názvy. Představte si například jednoduchou hierarchii dědičnosti:
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; }
}
U strategie mapování dědičnosti TPT se tyto typy mapují na tři tabulky. Sloupec primárního klíče v každé tabulce ale může mít jiný název. Například:
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 umožňuje konfiguraci tohoto mapování pomocí tvůrce vnořených tabulek:
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"));
Pomocí mapování Breed dědičnosti TPC lze vlastnost také mapovat na různé názvy sloupců v různých tabulkách. Představte si například následující tabulky TPC:
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 podporuje toto mapování tabulek:
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");
});