Miroslav Holec
Premium

Porovnání funkcí Table Splitting a Owned Entity Types v EF Core

Miroslav Holec   22. května 2018

Č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.).