Porovnání funkcí Table Splitting a Owned Entity Types v EF Core
Článek se vztahuje k verzi produktu EF Core 2.0
Tento článek byl napsán v roce 2018. Vývojářské technologie se neustále inovují a článek již nemusí popisovat aktuální stav technologie, ideální řešení a můj současný pohled na dané téma.
Jednou z nových funkcí, kterou nabízí EF Core je tzv. Table Splitting. Do jisté míry tato funkce vypadá podobně jako komplexní typy (tzv. Owned Entity Types), ale v některých detailech se liší. V článku porovnám obě funkce a nastíním rozdíly mezi nimi.
Table Splitting
Z hlediska doménového modelu se dá říci, že se jedná o mapování typů 1:1 s tím, že obě doménové třídy se mapují na jednu společnou DB tabulku. Obě entity pak následně sdílí společný primární klíč. Výhodou promapování je především projekce, při které lze pracovat jen s hlavní množinou dat, přičemž druhá část vazby se donačítá až v případě potřeby.
public class Category {
public int CategoryId {get; set;}
public string Name {get; set;}
public CategoryDetail CategoryDetail {get; set;}
}
public class CategoryDetail {
public int CategoryId {get; set;}
public DateTime Created {get; set;}
}
Konfigurace
Konfigurace vychází ze vztahu 1:1 a liší se pouze tím, že obě konfigurované entity mají definovaný stejný název DB tabulky, do které se mapují. Konfigurace je možná jak pomocí Fluent API (doporučuji), tak pomocí Data Annotations.
// pro Order.cs
builder.ToTable("Categories");
builder.HasOne(x => x.CategoryDetail)
.WithOne(x => x.Category)
.HasForeignKey<CategoryDetail>(x => x.CategoryId);
// pro OrderDetail.cs
builder.ToTable("Categories");
builder.HasOne(x => x.Category)
.WithOne(x => x.CategoryDetail)
.HasForeignKey<CategoryDetail>(x => x.CategoryId);
Performance
Výhoda Table splittingu spočívá v tom, že veškerá data vztahu 1:1 jsou v jedné DB tabulce. Díky tomu se nemusí provádět JOIN v případě, kdy chce uživatel načíst všechna data. V případě, že uživatel potřebuje jen řídící entitu, proběhne projekce s načtením jen nezbytných sloupců relevantních dané CS třídě.
var categories = context.Categories.ToList(); SELECT [c].[CategoryId], [c].[Name] FROM [dbo].[Categories] AS [c] var categoriesWithDetails = context.Categories.Include(x => x.CategoryDetail).ToList(); SELECT [x].[CategoryId], [x].[Name], [x].[Created] FROM [dbo].[Categories] AS [x]
Owned Entity Types
V porovnání s Table Splitingem jsou Owned Entity Types v některých drobnostech odlišné. Owned Entity Types jsou v doménovém modelu reprezentovány CS třídou (tzv. komplexní typ), která je k řídící entitě vázána jako property bez jakéhokoliv klíče. Oproti Table Splittingu tedy komplexní typ nemá primární klíč a ve své podstatě jen sdružuje některé vlastnosti do třídy. Taková třída se v rámci řídíci entity často používá opakovaně. Typickým užitím jsou komplexní třídy Name, Address nebo Price.
public class Order {
public int OrderId {get; set;}
public Address BillingAddress {get; set;}
public Address ShippingAddress {get; set;}
}
public class Address {
public string FirstName {get; set;}
public string LastName {get; set;}
public string Email {get; set;}
}
Konfigurace
Pro řídící entitu se nastavuje vlastnosti OwnsOne proti vybrané property, která drží daný komplexní typ. Příjemné je, že pro každý Owned Type lze nastavit dodatečná pravidla mapování a změnit tak název výsledného sloupce, datový typ nebo například úplnou ignoraci sloupce (viz př. níže).
// pro Order.cs
builder.OwnsOne(x => x.BillingAddress);
builder.OwnsOne(x => x.ShippingAddress, sa =>
{
sa.Ignore(x => x.Email);
});
Performance
Podobně jako v předchozím případě probíhá pouze projekce nad jednou DB tabulkou. Oproti Table Splittingu však probíhá vždy kompletní načtení všech Owned Types.
var order = context.Orders.ToList(); = SELECT [o].[OrderId], [o].[ShippingAddress_FirstName], [o].[ShippingAddress_LastName], [o].[BillingAddress_Email], [o].[BillingAddress_FirstName], [o].[BillingAddress_LastName] FROM [dbo].[Orders] AS [o]
Závěr
Obě funkce najdou uplatnění v mnoha modelech a mohou vývojáři usnadnit život. Ani jedna z funkcí nepředstavuje rizika z hlediska výkonnosti a přitom nabízí možnost pohodlnější práce s doménovým modelem. Owned Entity Types jsou ideální pro opakující se komplexní typy jako je například cena, nad kterou lze aplikovat další užitečné modely (dopočtení ceny s DPH, bez DPH atd.).