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.
Objecten die zijn opgeslagen in de database, kunnen worden gesplitst in drie algemene categorieën:
- Objecten die ongestructureerd zijn en één waarde bevatten. Bijvoorbeeld
int,Guid,string,IPAddress. Dit worden (enigszins losjes) primitieve typen genoemd. - Objecten die zijn gestructureerd om meerdere waarden op te slaan en waarbij de identiteit van het object wordt gedefinieerd door een sleutelwaarde. Bijvoorbeeld,
Blog,Post.CustomerDit worden entiteitstypen genoemd. - Objecten die zijn gestructureerd om meerdere waarden op te slaan, maar het object heeft geen sleutel voor het definiëren van de identiteit. Bijvoorbeeld,
Address,Coordinate.MoneyDeze worden waardeobjecten genoemd en EF Core wijst ze toe als complexe typen.
Een complex type groepeert verschillende eigenschappen in één .NET type dat zich in een entiteitstype bevindt. Het heeft geen eigen identiteit en kan niet onafhankelijk worden bijgehouden of opgevraagd. Dit maakt complexe typen de natuurlijke manier om waardeobjecten te modelleren.
Tip
U kunt in het volledige voorbeeldproject voor dit artikel uitvoeren en fouten opsporen in GitHub.
Note
Complexe typen zijn geïntroduceerd in EF Core 8 en zijn aanzienlijk uitgebreid in latere versies. Hieronder vindt u een aantekening van de functies met de versie waarmee deze zijn geïntroduceerd.
Complexe typen versus entiteitstypen in eigendom
Voordat complexe typen bestonden, waren entiteitstypen in eigendom de aanbevolen manier om objecten zonder sleuteleigenschappen te modelleren. Eigendomstypen zijn echter nog steeds entiteitstypen achter de schermen: ze hebben een verborgen sleutel en identiteit en werken daarom met verwijzingssemantiek. Dit veroorzaakt een aantal wrijvingspunten die complexe typen zijn ontworpen om op te lossen.
De belangrijkste verschillen zijn:
| Aspect | Entiteitstypen in eigendom | Complexe typen |
|---|---|---|
| Identiteit | Een verborgen sleutel en identiteit hebben | Geen identiteit; vergeleken met waarde |
| Exemplaar delen | Er kan niet tweemaal naar hetzelfde exemplaar worden verwezen | Hetzelfde exemplaar kan worden toegewezen aan meerdere eigenschappen |
| Semantiek van toewijzing | Verwijzingssemantiek | Waardesemantiek (eigenschappen worden gekopieerd) |
| .NET-type | Alleen referentietypen | Verwijzings- of waardetypen |
| Tabelverwijzing | Eigen tabel, tabel splitsen of JSON | De tabel van de container (tabel splitsen) of JSON |
| Navigations | Kan navigatie naar andere entiteiten bevatten | Kan geen navigatie bevatten |
Bulksgewijs bijwerken (ExecuteUpdate) |
Niet ondersteund | Supported |
Als u bijvoorbeeld het factuuradres van een klant toewijst aan hetzelfde adres als het verzendadres, mislukt met entiteitstypen in eigendom, omdat er niet meer dan één keer naar hetzelfde entiteitsexemplaar kan worden verwezen:
var customer = await context.Customers.SingleAsync(c => c.Id == someId);
customer.BillingAddress = customer.ShippingAddress;
await context.SaveChangesAsync(); // Throws with owned entity types
Omdat complexe typen waardesemantiek hebben, kopieert dezelfde toewijzing gewoon de eigenschappen over en werkt deze zoals verwacht. Op dezelfde manier vergelijkt u twee complexe waarden in een LINQ-query de inhoud, terwijl het vergelijken van twee entiteiten in eigendom hun identiteiten vergelijkt.
Om deze redenen zijn complexe typen over het algemeen de betere keuze voor het modelleren van waardeobjecten met tabelsplitsing of JSON-toewijzing. Gebruikers die momenteel entiteitstypen in eigendom gebruiken voor deze scenario's, worden aangemoedigd om over te schakelen naar complexe typen.
Een eenvoudig voorbeeld
Overweeg een Address type dat verschillende gerelateerde waarden bevat, maar geen eigen identiteit heeft:
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 kan vervolgens op verschillende plaatsen worden gebruikt in een klant-/ordersmodel:
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!;
}
Het maken en opslaan van een klant werkt zoals gebruikelijk:
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();
In een relationele database krijgt het complexe type geen eigen tabel. In plaats daarvan worden de eigenschappen inline opgeslagen als extra kolommen in de tabel van de entiteit die de entiteit bevat (dit wordt ook wel tabelsplitsing genoemd):
INSERT INTO [Customers] ([Name], [Address_City], [Address_Country], [Address_Line1], [Address_Line2], [Address_PostCode])
OUTPUT INSERTED.[Id]
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
Omdat complexe typen waarde-semantiek hebben, kan hetzelfde Address exemplaar zonder problemen worden gedeeld over meerdere eigenschappen:
// 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();
Complexe typen configureren
In tegenstelling tot de meeste entiteitstypen worden complexe typen niet gedetecteerd volgens conventie. U moet deze expliciet configureren door aantekeningen te maken op het type met ComplexTypeAttributeof door de ComplexProperty Fluent-API aan OnModelCreating te roepen voor elke eigenschap die moet worden toegewezen als een complex type:
[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; }
}
Facetten van complexe typeeigenschappen configureren
De geneste Property opbouwfunctie kan worden gebruikt om de scalaire eigenschappen van een complex type te configureren, net als eigenschappen van een entiteitstype, bijvoorbeeld om de kolomnaam of maximale lengte in te stellen:
modelBuilder.Entity<Order>()
.ComplexProperty(
o => o.ShippingAddress,
b =>
{
b.Property(a => a.Line1).HasColumnName("ShipsToStreet").HasMaxLength(100);
b.Property(a => a.City).HasColumnName("ShipsToCity");
});
Vanaf EF Core 11 kunt u een eigenschap die rechtstreeks in een complex type is genest configureren door lidtoegang in de lambda te koppelen, zonder eerst de opbouwfunctie voor complexe typen te verkrijgen:
// 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);
Verwijzings- en waardetypen
Een complex type kan een .NET verwijzingstype (a class ofrecord) of een waardetype (a struct of record struct, geïntroduceerd in EF Core 10) zijn.
Mutability
Omdat een exemplaar van het verwijzingstype kan worden gedeeld door meerdere eigenschappen, wijzigt het dempen van een van de eigenschappen de waarde overal waar deze wordt gebruikt. Dit is meestal niet wat u wilt. Een goede manier om dit te voorkomen - en een natuurlijke pasvorm voor waardeobjecten - is om het complexe type onveranderbaar te maken, zodat het wijzigen van een waarde vereist dat er een nieuw exemplaar wordt gemaakt. Het Address type dat in dit artikel wordt gebruikt, is onveranderbaar record; het wijzigen van een adres wordt daarom uitgevoerd met een with expressie:
// 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();
Hoewel een geheel nieuw Address exemplaar is toegewezen, houdt EF nog steeds wijzigingen bij op het niveau van de afzonderlijke eigenschap, dus alleen de kolommen waarvan de waarden daadwerkelijk zijn gewijzigd, worden bijgewerkt:
UPDATE [Customers] SET [Address_Line1] = @p0
OUTPUT 1
WHERE [Id] = @p1;
Onveranderbaarheid kan worden uitgedrukt met een onveranderbare class (alleen-lezen- of alleen-lezeneigenschappen), een , een readonly structrecordof een readonly record struct. Waardetypen (struct) hebben kopieersemantiek, dus als u ze toewijst, worden altijd de waarden gekopieerd en wordt het probleem met het onbedoeld delen voorkomen, zelfs wanneer onveranderbaar- maar onveranderbare structs over het algemeen worden afgeraden in C#, dus een onveranderbare vorm wordt nog steeds aanbevolen.
Tip
Als meerdere entiteiten hetzelfde adres echt moeten observeren en bijwerken wanneer het wordt gewijzigd, modelleert u het adres als een entiteitstype met een eigen identiteit en verwijst u ernaar via een navigatie, in plaats van een complex type te gebruiken.
Geneste complexe typen
Een complex type kan eigenschappen van andere complexe typen bevatten, zodat u gestructureerde objecten kunt opbouwen tot elke diepte. Een Contact complex type kan bijvoorbeeld zowel een Address als een of meer PhoneNumber complexe typen bevatten:
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; }
}
Wanneer de kolommen van een genest complex type worden toegewezen via tabelsplitsing, worden de kolommen van een genest complex type voorafgegaan door het volledige pad naar de eigenschap (bijvoorbeeld Contact_HomePhone_Number).
Optionele complexe typen
Standaard is een complexe eigenschap vereist: de CLR-eigenschap moet altijd een waarde hebben en wordt toegewezen aan niet-null-kolommen. Vanaf EF Core 10 kan een complexe eigenschap optioneel worden gemaakt door deze als null-baar te declareren:
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!;
}
Een optionele complexe eigenschap die null resulteert in NULL waarden in alle kolommen.
Note
Voor een optioneel complex type moet momenteel ten minste één vereiste eigenschap worden gedefinieerd voor het complexe type. Dit komt doordat EF ten minste één niet-nullbare kolom nodig heeft om een null complexe waarde te onderscheiden van een complexe waarde waarvan de eigenschappen allemaal voorkomen null.
Als het complexe type geen eigen eigenschap heeft, kunt u in plaats daarvan een discriminatoreigenschap configureren. Hoewel EF Core nog geen ondersteuning biedt voor overname van complexe typen, wordt de discriminator standaard gemaakt als een vereiste schaduweigenschap , die voldoet aan de bovenstaande vereiste:
// 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());
Verzamelingen van complexe typen
Vanaf EF Core 10 kan een eigenschap een verzameling complexe typen bevatten. In relationele databases moeten complexe verzamelingen worden toegewezen aan één JSON-kolom met behulp van ToJson : ze kunnen niet worden toegewezen aan een andere tabel:
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());
Elk element van de verzameling wordt opgeslagen als een JSON-object in de matrix en de hele verzameling wordt toegewezen aan één kolom:
CREATE TABLE [Distributors] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[ShippingCenters] json NOT NULL,
CONSTRAINT [PK_Distributors] PRIMARY KEY ([Id])
);
Note
Verzamelingen waardetypen (struct) worden momenteel niet ondersteund. Gebruik een verwijzingstype (class of record) voor complexe verzamelingselementen.
Complexe typen toewijzen aan JSON
Naast het splitsen van tabellen kunt u met EF Core 10 een complexe eigenschap (niet-verzameling) toewijzen aan één JSON-kolom met ToJson:
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.Address, c => c.ToJson());
b.ComplexProperty(c => c.SecondaryAddress, c => c.ToJson());
});
Elke complexe waarde wordt vervolgens geserialiseerd in één JSON-kolom in plaats van over meerdere kolommen te verdelen:
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])
);
Op SQL Server 2025 en Azure SQL gebruikt EF standaard het systeemeigen json gegevenstype; voor andere databases en oudere SQL Server versies wordt JSON opgeslagen in een tekstkolom. U kunt het kolomtype HasColumnType desgewenst overschrijven.
In tegenstelling tot tabelsplitsing zijn met JSON-toewijzing verzamelingen binnen het toegewezen type toegestaan en kunt u afzonderlijke eigenschappen in het document opvragen en bijwerken, net zoals elke andere eigenschap. Waarden in JSON-kolommen kunnen ook efficiënt bulksgewijs worden bijgewerkt met ExecuteUpdateAsync.
Sleutels en indexen voor complexe typeeigenschappen
Vanaf EF Core 11 kunnen sleutels en indexen zich richten op scalaire eigenschappen die zijn genest binnen complexe typen die niet in verzamelingen zijn genest. Dit kan worden gedaan met een 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);
Dezelfde paden kunnen op naam worden geconfigureerd, waarbij u . naar een complexe eigenschap navigeert:
modelBuilder.Entity<Customer>()
.HasIndex("Address.PostCode");
Voor relationele providers kunnen indexen ook gericht zijn op paden binnen complexe typen die zijn toegewezen aan JSON-kolommen. Complexe verzamelingspaden gebruiken [] om te verwijzen naar alle elementen of een numerieke indexeerfunctie voor een specifiek element:
// 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
Voor indexering in een door JSON toegewezen complexe verzameling is een database vereist die ondersteuning biedt voor JSON-indexen, zoals SQL Server 2025.
Zie Sleutels en indexen en beperkingen voor meer informatie.
Complexe typen met entiteitsovername
Vanaf EF Core 11 kunnen complexe typen en JSON-kolommen worden gebruikt voor entiteitstypen die gebruikmaken van TPT (tabel per type) of TPC (tabel-per-betontype). Hiermee kunt u de flexibiliteit van deze overnamestrategieën combineren met de modelleringskracht van complexe typen.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>()
.UseTptMappingStrategy()
.ComplexProperty(a => a.Details);
}
Wijzigingen bijhouden
EF Core houdt wijzigingen bij in de afzonderlijke eigenschappen van een complex type, zodat alleen de betreffende kolommen worden bijgewerkt wanneer u aanroept SaveChanges. U kunt deze traceringsstatus inspecteren en manipuleren via de wijzigingentracker.
Gebruik EntityEntry.ComplexProperty dit om een complexe eigenschap te bereiken en zoom vervolgens in op de scalaire eigenschappen:
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}");
De ComplexPropertyEntry API weerspiegelt de entiteits-APIEntityEntry: u kunt lezen en instellen, controleren en instellen CurrentValueIsModifieden navigeren naar verder geneste complexe eigenschappen of complexe verzamelingen. Complexe eigenschappen worden ook weergegeven via de eigenschapswaarden-API's van de entiteit (CurrentValues/OriginalValues).
Query's uitvoeren op complexe typen
Complexe typeleden kunnen worden gebruikt in LINQ-query's, net zoals eigenschappen van de entiteit zelf. U kunt erop filteren, ze projecteren en rangschikken:
// 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();
Omdat complexe typen waardesemantiek hebben, kunt u ook een hele complexe waarde in een query vergelijken en vergelijkt EF alle eigenschappen:
var ordersToHomeAddress = await context.Orders
.Where(o => o.ShippingAddress == o.BillingAddress)
.ToListAsync();
Voor complexe typen die zijn toegewezen aan JSON, voegt EF Core 11 toe EF.Functions.JsonPathExists, waarmee wordt gecontroleerd of een bepaald JSON-pad bestaat in het document:
var withPostCode = await context.Customers
.Where(c => EF.Functions.JsonPathExists(c.Address, "$.PostCode"))
.ToListAsync();
Limitations
Complexe typen zijn ontworpen voor het modelleren van waardeobjecten en bieden opzettelijk geen ondersteuning voor elke mogelijkheid van entiteitstypen. De belangrijkste beperkingen zijn:
- Geen identiteit of tracering van hun eigen identiteit. Een complex type kan alleen bestaan als onderdeel van een entiteit; u kunt geen
DbSet<T>complex type hebben, noch het afzonderlijk bijhouden of er query's op uitvoeren. - Geen navigatie. Een complex type kan geen navigatie-eigenschappen bevatten voor entiteitstypen.
- Geen afzonderlijke tabel. In relationele databases wordt een complex type altijd opgeslagen in de tabel van de container (via tabelsplitsing) of in een JSON-kolom, nooit in een eigen tabel.
- Voor verzamelingen is JSON vereist. Bij relationele providers moeten complexe verzamelingen worden toegewezen aan JSON;
ToJsonze kunnen niet worden toegewezen via tabelsplitsing. - Verzamelingen waardetypen worden niet ondersteund. Complexe verzamelingselementen moeten verwijzingstypen zijn.
- Voor optionele complexe typen is een vereiste eigenschap vereist. Een optioneel (nullable) complex type moet ten minste één vereiste eigenschap definiëren.
Ondersteuning voor complexe typen wordt nog steeds uitgebreid in releases; bekijk de nieuwe pagina's voor de nieuwste toevoegingen.