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.
Objekty uložené do databáze je možné rozdělit do tří širokých kategorií:
- Objekty, které jsou nestrukturované a obsahují jednu hodnotu. Například ,
int,Guidstring,IPAddress. Jedná se o (poněkud volně) označované jako primitivní typy. - Objekty strukturované tak, aby držely více hodnot a kde je identita objektu definována hodnotou klíče. Například ,
Blog,PostCustomer. Ty se nazývají typy entit. - Objekty strukturované tak, aby držely více hodnot, ale objekt nemá žádný klíč definující svou identitu. Například ,
Address,CoordinateMoney. Tyto objekty se nazývají objekty hodnot a EF Core je mapuje jako složité typy.
Složitý typ seskupí několik vlastností do jednoho typu .NET, který je součástí typu entity. Nemá vlastní identitu a nelze ji nezávisle sledovat ani dotazovat. Díky tomu jsou složité typy přirozeným způsobem modelování objektů hodnot.
Tip
V tomto článku o GitHub můžete spustit a ladit úplný ukázkový projekt.
Note
Složité typy byly zavedeny v EF Core 8 a byly podstatně rozšířeny v pozdějších verzích. Níže uvedené funkce jsou opatřeny poznámkami s verzí, která je představila.
Komplexní typy vs. typy vlastněných entit
Než existovaly složité typy, byly typy vlastněných entit doporučeným způsobem modelování objektů bez klíčových vlastností. Vlastní typy jsou však stále typy entit na pozadí: mají skrytý klíč a identitu, a proto pracují s referenční sémantikou. To způsobuje řadu tření, které jsou komplexní typy navrženy k řešení.
Mezi hlavní rozdíly patří:
| Aspect | Vlastněné typy entit | Komplexní typy |
|---|---|---|
| Identity | Mít skrytý klíč a identitu | Žádná identita; porovnání podle hodnoty |
| Sdílení instancí | Stejnou instanci nelze odkazovat dvakrát. | Stejnou instanci lze přiřadit více vlastnostem. |
| Sémantika přiřazení | Sémantika odkazů | Sémantika hodnot (vlastnosti se zkopírují) |
| Typ .NET | Pouze odkazové typy | Odkazy nebo typy hodnot |
| Mapování tabulek | Vlastní tabulka, rozdělení tabulky nebo JSON | Tabulka kontejneru (rozdělení tabulky) nebo JSON |
| Navigations | Může obsahovat navigace k jiným entitám. | Nelze obsahovat navigace. |
Hromadná aktualizace (ExecuteUpdate) |
Nepodporováno | Podporováno |
Například přiřazení fakturační adresy zákazníka tak, aby byla stejná jako jejich dodací adresa, selže s typy vlastněných entit, protože na stejnou instanci entity nejde odkazovat více než jednou:
var customer = await context.Customers.SingleAsync(c => c.Id == someId);
customer.BillingAddress = customer.ShippingAddress;
await context.SaveChangesAsync(); // Throws with owned entity types
Vzhledem k tomu, že komplexní typy mají sémantiku hodnot, stejné přiřazení jednoduše zkopíruje vlastnosti a funguje podle očekávání. Podobně porovnávání dvou složitých hodnot v dotazu LINQ porovnává jejich obsah, zatímco porovnání dvou vlastněných entit porovnává jejich identity.
Z těchto důvodů jsou komplexní typy obecně lepší volbou pro modelování hodnot objektů s rozdělením tabulky nebo mapováním JSON. Uživatelům, kteří v současné době používají typy vlastněných entit pro tyto scénáře, doporučujeme zvážit přechod na složité typy.
Jednoduchý příklad
Address Zvažte typ, který obsahuje několik souvisejících hodnot, ale nemá vlastní identitu:
public record Address
{
public required string Line1 { get; init; }
public string? Line2 { get; init; }
public required string City { get; init; }
public required string Country { get; init; }
public required string PostCode { get; init; }
}
Address je pak možné použít na několika místech napříč modelem zákazníka nebo objednávek:
public class Customer
{
public int Id { get; set; }
public required string Name { get; set; }
// A required (non-nullable) complex property.
public required Address Address { get; set; }
// An optional (nullable) complex property.
public Address? SecondaryAddress { get; set; }
public List<Order> Orders { get; } = new();
}
public class Order
{
public int Id { get; set; }
public required string Contents { get; set; }
public required Address ShippingAddress { get; set; }
public required Address BillingAddress { get; set; }
public Customer Customer { get; set; } = null!;
}
Vytvoření a uložení zákazníka funguje obvyklým způsobem:
var customer = new Customer
{
Name = "Willow",
Address = new Address
{
Line1 = "Barking Gate",
City = "Walpole St Peter",
Country = "UK",
PostCode = "PE14 7AV"
}
};
context.Add(customer);
await context.SaveChangesAsync();
U relační databáze komplexní typ nezískáte vlastní tabulku. Místo toho se jeho vlastnosti ukládají jako další sloupce v tabulce obsahující entitu (označuje se jako rozdělení tabulky):
INSERT INTO [Customers] ([Name], [Address_City], [Address_Country], [Address_Line1], [Address_Line2], [Address_PostCode])
OUTPUT INSERTED.[Id]
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
Vzhledem k tomu, že komplexní typy mají sémantiku hodnot, může být stejná Address instance sdílena napříč více vlastnostmi bez jakýchkoli problémů:
// The same Address instance can be assigned to multiple complex properties.
customer.Orders.Add(
new Order
{
Contents = "Tesco Tasty Treats",
BillingAddress = customer.Address,
ShippingAddress = customer.Address
});
await context.SaveChangesAsync();
Konfigurace komplexních typů
Na rozdíl od většiny typů entit nejsou komplexní typy zjištěny konvencí. Musíte je nakonfigurovat explicitně, a to buď tak, že zadáte poznámky k typu pomocí ComplexTypeAttributenebo zavoláním ComplexProperty rozhraní Fluent API OnModelCreating pro každou vlastnost, která by se měla mapovat jako komplexní typ:
[ComplexType]
public record Address
{
public required string Line1 { get; init; }
public string? Line2 { get; init; }
public required string City { get; init; }
public required string Country { get; init; }
public required string PostCode { get; init; }
}
Konfigurace omezujících vlastností komplexního typu
Vnořený Property tvůrce lze použít ke konfiguraci skalárních vlastností komplexního typu, stejně jako vlastnosti typu entity – například k nastavení názvu sloupce nebo maximální délky:
modelBuilder.Entity<Order>()
.ComplexProperty(
o => o.ShippingAddress,
b =>
{
b.Property(a => a.Line1).HasColumnName("ShipsToStreet").HasMaxLength(100);
b.Property(a => a.City).HasColumnName("ShipsToCity");
});
Od EF Core 11 můžete nakonfigurovat vlastnost vnořenou do komplexního typu přímo zřetězeným přístupem členů v lambda, aniž byste nejprve získali tvůrce komplexního typu:
// EF Core 11 allows configuring a complex-type property directly by chaining
// member access, without first obtaining the complex-type builder.
modelBuilder.Entity<Customer>()
.Property(c => c.Address.Line1)
.HasMaxLength(200);
Odkazy a typy hodnot
Komplexním typem může být typ odkazu .NET (a class nebo) nebo recordtyp hodnoty (structnebo record struct, zavedený v EF Core 10).
Proměnlivost
Vzhledem k tomu, že instanci typu odkazu může sdílet více vlastností, ztlumení jedné z jejích vlastností změní hodnotu všude, kde se používá. To obvykle není to, co chcete. Dobrým způsobem, jak se tomu vyhnout – a přirozenému přizpůsobení objektům hodnot – je nastavit komplexní typ jako neměnný, aby změna hodnoty vyžadovala vytvoření nové instance. Typ Address použitý v tomto článku je neměnný record; změna adresy se proto provádí pomocí výrazu with :
// Address is an immutable record, so create a new instance to change a value.
customer.Address = customer.Address with { Line1 = "Peacock Lodge" };
await context.SaveChangesAsync();
I když je přiřazená celá nová Address instance, EF stále sleduje změny na úrovni jednotlivých vlastností, takže se aktualizují jenom sloupce, jejichž hodnoty se skutečně změnily:
UPDATE [Customers] SET [Address_Line1] = @p0
OUTPUT 1
WHERE [Id] = @p1;
Neměnnost lze vyjádřit neměnnou class (init-only nebo jen pro čtení), a record, a readonly struct, nebo readonly record struct. Typy hodnot (struct) mají sémantiku kopírování, takže jejich přiřazování vždy kopíruje hodnoty a zabraňuje problému s náhodným sdílením, a to i v případě, že se proměnlivé struktury v jazyce C# obecně nedoporučuje, takže se stále doporučuje neměnný formulář.
Tip
Pokud by několik entit mělo při změně sledovat stejnou adresu a aktualizovat ji společně, modelujte ji jako typ entity s vlastní identitou a odkazujte na ni pomocí navigace, a ne pomocí komplexního typu.
Vnořené komplexní typy
Komplexní typ může obsahovat vlastnosti jiných komplexních typů, což umožňuje vytvářet strukturované objekty do jakékoli hloubky.
Contact Například komplexní typ může obsahovat Address jeden nebo více PhoneNumber složitých typů:
public record Address(string Line1, string? Line2, string City, string Country, string PostCode);
public record PhoneNumber(int CountryCode, long Number);
public record Contact
{
public required Address Address { get; init; }
public required PhoneNumber HomePhone { get; init; }
public required PhoneNumber WorkPhone { get; init; }
}
Při mapování pomocí rozdělení tabulky jsou sloupce vnořeného komplexního typu předponou úplné cesty k vlastnosti (například Contact_HomePhone_Number).
Volitelné komplexní typy
Ve výchozím nastavení je vyžadována složitá vlastnost: vlastnost CLR musí mít vždy hodnotu a mapuje se na sloupce bez hodnoty null. Počínaje EF Core 10 může být složitá vlastnost volitelná tím, že ji deklaruje jako nullable:
public class Customer
{
public int Id { get; set; }
public required string Name { get; set; }
// A required (non-nullable) complex property.
public required Address Address { get; set; }
// An optional (nullable) complex property.
public Address? SecondaryAddress { get; set; }
public List<Order> Orders { get; } = new();
}
public class Order
{
public int Id { get; set; }
public required string Contents { get; set; }
public required Address ShippingAddress { get; set; }
public required Address BillingAddress { get; set; }
public Customer Customer { get; set; } = null!;
}
Volitelná složitá vlastnost, která má null za NULL následek hodnoty ve všech jeho sloupcích.
Note
Volitelný komplexní typ v současné době vyžaduje, aby byla u komplexního typu definována alespoň jedna požadovaná vlastnost. Důvodem je, že EF potřebuje alespoň jeden sloupec bez hodnoty null k rozlišení null komplexní hodnoty od komplexní hodnoty, jejíž vlastnosti jsou nullvšechny .
Pokud komplexní typ nemá žádnou požadovanou vlastnost, můžete místo toho nakonfigurovat diskriminující vlastnost. I když EF Core zatím nepodporuje dědičnost složitých typů, diskriminátor se ve výchozím nastavení vytvoří jako požadovaná stínová vlastnost , která splňuje výše uvedený požadavek:
// GeoLocation has no required property, so configure a discriminator. EF creates
// it as a required shadow property, which satisfies the requirement that an
// optional complex type have at least one required property.
modelBuilder.Entity<Place>()
.ComplexProperty(p => p.Location, b => b.HasDiscriminator());
Kolekce komplexních typů
Od EF Core 10 může vlastnost obsahovat kolekci složitých typů. V relačních databázích musí být složité kolekce namapované na jeden sloupec JSON pomocí ToJson – nejde je namapovat na jinou tabulku:
public class Distributor
{
public int Id { get; set; }
public required string Name { get; set; }
// A collection of complex types, mapped to a single JSON column.
public List<Address> ShippingCenters { get; set; } = new();
}
// Collections of complex types must be mapped to JSON on relational providers.
modelBuilder.Entity<Distributor>()
.ComplexCollection(d => d.ShippingCenters, b => b.ToJson());
Každý prvek kolekce se uloží jako objekt JSON uvnitř pole a celá kolekce se mapuje na jeden sloupec:
CREATE TABLE [Distributors] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[ShippingCenters] json NOT NULL,
CONSTRAINT [PK_Distributors] PRIMARY KEY ([Id])
);
Note
Kolekce typů hodnot (struct) nejsou v současné době podporovány. Pro komplexní prvky kolekce použijte odkazový typ (class nebo record).
Mapování složitých typů na JSON
Kromě rozdělení tabulky EF Core 10 umožňuje mapování komplexní vlastnosti (mimo kolekci) na jeden sloupec JSON pomocí ToJson:
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.Address, c => c.ToJson());
b.ComplexProperty(c => c.SecondaryAddress, c => c.ToJson());
});
Každá složitá hodnota se pak serializuje do jednoho sloupce JSON místo toho, aby se rozprostřela mezi více sloupců:
CREATE TABLE [Customers] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[Address] json NOT NULL,
[SecondaryAddress] json NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);
V SQL Server 2025 a Azure SQL ef ve výchozím nastavení používá nativní json datový typ. V jiných databázích a starších verzích SQL Server se JSON ukládá do textového sloupce. Typ HasColumnType sloupce můžete v případě potřeby přepsat.
Na rozdíl od rozdělení tabulky umožňuje mapování JSON kolekce v mapovaném typu a umožňuje dotazovat a aktualizovat jednotlivé vlastnosti v dokumentu stejně jako jakékoli jiné vlastnosti. Hodnoty uvnitř sloupců JSON lze také efektivně aktualizovat hromadně pomocí ExecuteUpdateAsync.
Klíče a indexy vlastností komplexního typu
Od EF Core 11 můžou klíče a indexy cílit na skalární vlastnosti vnořené uvnitř složitých typů, které nejsou kolekcemi. To lze provést pomocí lambda:
// EF Core 11 allows keys and indexes to target scalar properties nested
// inside non-collection complex types.
modelBuilder.Entity<Customer>()
.HasIndex(c => c.Address.PostCode);
Stejné cesty je možné nakonfigurovat pomocí názvu, a to pomocí . přechodu na složitou vlastnost:
modelBuilder.Entity<Customer>()
.HasIndex("Address.PostCode");
U relačních zprostředkovatelů můžou indexy také cílit na cesty uvnitř složitých typů mapovaných na sloupce JSON. Komplexní cesty kolekce slouží [] k odkazování na všechny prvky nebo k číselnému indexeru pro určitý prvek:
// Index a scalar inside every element of a JSON-mapped complex collection.
// Requires a provider/database with JSON index support, such as SQL Server 2025.
modelBuilder.Entity<Distributor>()
.HasIndex("ShippingCenters[].City");
Note
Indexování do komplexní kolekce mapované ve formátu JSON vyžaduje databázi, která podporuje indexy JSON, například SQL Server 2025.
Další informace najdete v tématu Klíče a indexy a omezení.
Komplexní typy s dědičností entit
Od EF Core 11 je možné komplexní typy a sloupce JSON použít u typů entit, které používají TPT (tabulka na typ) nebo TPC (typ tabulky na konkrétní typ). Díky tomu můžete kombinovat flexibilitu těchto strategií dědičnosti s výkonem modelování složitých typů.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>()
.UseTptMappingStrategy()
.ComplexProperty(a => a.Details);
}
Sledování změn
EF Core sleduje změny jednotlivých vlastností komplexního typu, takže při volání SaveChangesse aktualizují pouze ovlivněné sloupce . Tento stav sledování můžete zkontrolovat a manipulovat s ním prostřednictvím sledování změn.
Slouží EntityEntry.ComplexProperty k dosažení komplexní vlastnosti a následnému přechodu k podrobnostem o jeho skalárních vlastnostech:
var addressEntry = context.Entry(customer).ComplexProperty(c => c.Address);
Console.WriteLine($"City is currently: {addressEntry.Property(a => a.City).CurrentValue}");
Console.WriteLine($"Address was modified: {addressEntry.Property(a => a.City).IsModified}");
Rozhraní ComplexPropertyEntry API zrcadlí rozhraní API entity EntityEntry : můžete číst a nastavit CurrentValue, kontrolovat a nastavit IsModifieda přejít do dalších vnořených složitých vlastností nebo složitých kolekcí. Komplexní vlastnosti jsou také zpřístupněny prostřednictvím rozhraní API hodnot vlastností entity.CurrentValues/OriginalValues
Dotazování složitých typů
Členy komplexního typu je možné použít v dotazech LINQ stejně jako vlastnosti samotné entity – můžete je filtrovat, projektovat je a uspořádat podle nich:
// Filter and project members of a complex property.
var ukCities = await context.Customers
.Where(c => c.Address.Country == "UK")
.Select(c => c.Address.City)
.ToListAsync();
Vzhledem k tomu, že komplexní typy mají sémantiku hodnot, můžete také porovnat celou komplexní hodnotu v dotazu a EF porovná všechny jeho vlastnosti:
var ordersToHomeAddress = await context.Orders
.Where(o => o.ShippingAddress == o.BillingAddress)
.ToListAsync();
U složitých typů mapovaných na JSON přidá EF.Functions.JsonPathExistsEF Core 11 , který zkontroluje, jestli v dokumentu existuje daná cesta JSON:
var withPostCode = await context.Customers
.Where(c => EF.Functions.JsonPathExists(c.Address, "$.PostCode"))
.ToListAsync();
Limitations
Komplexní typy jsou navržené pro objekty hodnot modelování a záměrně nepodporují všechny funkce typů entit. Hlavními omezeními jsou:
- Žádná identita ani sledování jejich vlastní identity. Komplexní typ může existovat pouze jako součást entity; Nemůžete mít
DbSet<T>komplexní typ, ani ho nezávisle sledovat ani dotazovat. - Žádná navigace. Komplexní typ nemůže obsahovat navigační vlastnosti na typy entit.
- Žádná samostatná tabulka. U relačních databází je komplexní typ vždy uložený v tabulce kontejneru (prostřednictvím rozdělení tabulky) nebo ve sloupci JSON – nikdy ve vlastní tabulce.
- Kolekce vyžadují JSON. U relačních zprostředkovatelů musí být komplexní kolekce namapované na JSON.
ToJsonNejde je namapovat pomocí rozdělení tabulky. - Kolekce typů hodnot nejsou podporovány. Komplexní prvky kolekce musí být odkazové typy.
- Volitelné komplexní typy vyžadují požadovanou vlastnost. Volitelný komplexní typ (s možnou hodnotou null) musí definovat aspoň jednu požadovanou vlastnost.
Podpora komplexního typu se i nadále rozšiřuje napříč verzemi; podívejte se, jaké jsou nové stránky pro nejnovější doplňky.