Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Objekt som sparats i databasen kan delas upp i tre breda kategorier:
- Objekt som är ostrukturerade och innehåller ett enda värde. Till exempel
int,Guid,string,IPAddress. Dessa kallas (något löst) primitiva typer. - Objekt som är strukturerade för att innehålla flera värden och där objektets identitet definieras av ett nyckelvärde. Till exempel
Blog,Post,Customer. Dessa kallas entitetstyper. - Objekt som är strukturerade för att innehålla flera värden, men objektet har ingen nyckel som definierar dess identitet. Till exempel
Address,Coordinate,Money. Dessa kallas värdeobjekt och EF Core mappar dem som komplexa typer.
En komplex typ grupperar flera egenskaper i en enda .NET typ som finns i en entitetstyp. Den har ingen egen identitet och kan inte spåras eller frågas oberoende av varandra. Detta gör komplexa typer till det naturliga sättet att modellera värdeobjekt.
Tips/Råd
Du kan köra och felsöka i det fullständiga exempelprojektet för den här artikeln på GitHub.
Anmärkning
Komplexa typer introducerades i EF Core 8 och har utökats avsevärt i senare versioner. Funktionerna kommenteras nedan med den version som introducerade dem.
Komplexa typer jämfört med ägda entitetstyper
Innan komplexa typer fanns var ägda entitetstyper det rekommenderade sättet att modellera objekt utan nyckelegenskaper. Ägda typer är dock fortfarande entitetstyper i bakgrunden: de har en dold nyckel och identitet och fungerar därför med referenssemantik. Detta orsakar ett antal friktionspunkter som komplexa typer är utformade för att lösa.
De viktigaste skillnaderna är:
| Aspect | Ägda entitetstyper | Komplexa typer |
|---|---|---|
| Identity | Ha en dold nyckel och identitet | Ingen identitet; jämfört med värde |
| Instansdelning | Samma instans kan inte refereras två gånger | Samma instans kan tilldelas till flera egenskaper |
| Tilldelningssemantik | Referenssemantik | Värdesemantik (egenskaper kopieras) |
| .NET-typ | Endast referenstyper | Referens - eller värdetyper |
| Tabellmappning | Egen tabell, tabelldelning eller JSON | Containerns tabell (tabelldelning) eller JSON |
| Navigations | Kan innehålla navigering till andra entiteter | Det går inte att innehålla navigering |
Massuppdatering (ExecuteUpdate) |
Stöds ej | Understödd |
Om du till exempel tilldelar en kunds faktureringsadress till samma som leveransadressen misslyckas med ägda entitetstyper, eftersom samma entitetsinstans inte kan refereras mer än en gång:
var customer = await context.Customers.SingleAsync(c => c.Id == someId);
customer.BillingAddress = customer.ShippingAddress;
await context.SaveChangesAsync(); // Throws with owned entity types
Eftersom komplexa typer har värdesemantik kopierar samma tilldelning bara egenskaperna och fungerar som förväntat. På samma sätt jämför jämförelsen av två komplexa värden i en LINQ-fråga deras innehåll, medan jämförelse av två ägda entiteter jämför deras identiteter.
Därför är komplexa typer vanligtvis det bättre valet för modellering av värdeobjekt med tabelldelning eller JSON-mappning. Användare som för närvarande använder ägda entitetstyper för dessa scenarier uppmanas att överväga att byta till komplexa typer.
Ett enkelt exempel
Överväg en Address typ som innehåller flera relaterade värden men som inte har någon egen identitet:
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 sedan användas på flera platser i en kund/order-modell:
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!;
}
Att skapa och spara en kund fungerar som vanligt:
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();
I en relationsdatabas får den komplexa typen inte en egen tabell. I stället sparas dess egenskaper infogade som ytterligare kolumner i den innehållande entitetens tabell (detta kallas tabelldelning):
INSERT INTO [Customers] ([Name], [Address_City], [Address_Country], [Address_Line1], [Address_Line2], [Address_PostCode])
OUTPUT INSERTED.[Id]
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
Eftersom komplexa typer har värdesemantik kan samma Address instans delas mellan flera egenskaper utan problem:
// 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();
Konfigurera komplexa typer
Till skillnad från de flesta entitetstyper identifieras inte komplexa typer av konventioner. Du måste konfigurera dem explicit, antingen genom att kommentera typen med ComplexTypeAttributeeller genom att anropa Fluent API:et ComplexProperty för OnModelCreating varje egenskap som ska mappas som en komplex 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; }
}
Konfigurera fasetter av komplexa typegenskaper
Den kapslade Property byggaren kan användas för att konfigurera skalära egenskaper för en komplex typ, precis som egenskaper för en entitetstyp, till exempel för att ange kolumnnamnet eller maximal längd:
modelBuilder.Entity<Order>()
.ComplexProperty(
o => o.ShippingAddress,
b =>
{
b.Property(a => a.Line1).HasColumnName("ShipsToStreet").HasMaxLength(100);
b.Property(a => a.City).HasColumnName("ShipsToCity");
});
Från och med EF Core 11 kan du konfigurera en egenskap som är kapslad i en komplex typ direkt genom att länka medlemsåtkomst i lambda, utan att först hämta byggverktyget av komplex typ:
// 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);
Referens- och värdetyper
En komplex typ kan vara en .NET referenstyp (a class eller record) eller en värdetyp (en struct eller record struct, som introducerades i EF Core 10).
Mutability
Eftersom en referenstypinstans kan delas av flera egenskaper ändrar mutering av en av dess egenskaper värdet överallt där den används. Detta är vanligtvis inte vad du vill. Ett bra sätt att undvika det – och en naturlig passform för värdeobjekt – är att göra den komplexa typen oföränderlig, så att en ändring av ett värde kräver att en ny instans skapas. Den Address typ som används i hela den här artikeln är oföränderlig record. Om du ändrar en adress görs det därför med ett with uttryck:
// 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();
Även om en helt ny Address instans har tilldelats spårar EF fortfarande ändringar på den enskilda egenskapsnivån, så endast de kolumner vars värden faktiskt har ändrats uppdateras:
UPDATE [Customers] SET [Address_Line1] = @p0
OUTPUT 1
WHERE [Id] = @p1;
Oföränderlighet kan uttryckas med oföränderliga class (init-only eller skrivskyddade egenskaper), en record, en readonly structeller en readonly record struct. Värdetyper (struct) har kopieringssemantik, så att tilldela dem kopierar alltid värdena och undviker problemet med oavsiktlig delning, även när de är föränderliga , men föränderliga structs rekommenderas vanligtvis inte i C#, så en oföränderlig form rekommenderas fortfarande.
Tips/Råd
Om flera entiteter verkligen bör observera samma adress och uppdateras tillsammans när den ändras, modellerar du adressen som en entitetstyp med sin egen identitet och refererar till den via en navigering i stället för att använda en komplex typ.
Kapslade komplexa typer
En komplex typ kan innehålla egenskaper för andra komplexa typer, så att du kan bygga upp strukturerade objekt till valfritt djup. En komplex typ kan till exempel Contact innehålla både en Address och en eller flera PhoneNumber komplexa typer:
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; }
}
När de mappas via tabelldelning är kolumnerna av en kapslad komplex typ prefix med den fullständiga sökvägen till egenskapen (till exempel Contact_HomePhone_Number).
Valfria komplexa typer
Som standard krävs en komplex egenskap: CLR-egenskapen måste alltid ha ett värde och mappas till icke-nullbara kolumner. Från och med EF Core 10 kan en komplex egenskap göras valfri genom att deklarera den som nullbar:
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!;
}
En valfri komplex egenskap som resulterar null i värden i NULL alla dess kolumner.
Anmärkning
En valfri komplex typ kräver för närvarande att minst en nödvändig egenskap definieras för den komplexa typen. Det beror på att EF behöver minst en icke-nullbar kolumn för att skilja ett null komplext värde från ett komplext värde vars egenskaper alla råkar vara null.
Om den komplexa typen inte har någon egen obligatorisk egenskap kan du i stället konfigurera en diskriminerande egenskap. EF Core har ännu inte stöd för arv för komplexa typer, men diskrimineringen skapas som en nödvändig skuggegenskap som standard, vilket uppfyller kravet ovan:
// 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());
Samlingar av komplexa typer
Från och med EF Core 10 kan en egenskap innehålla en samling komplexa typer. På relationsdatabaser måste komplexa samlingar mappas till en enda JSON-kolumn med – ToJson de kan inte mappas till en annan tabell:
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());
Varje element i samlingen lagras som ett JSON-objekt i matrisen och hela samlingen mappas till en kolumn:
CREATE TABLE [Distributors] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[ShippingCenters] json NOT NULL,
CONSTRAINT [PK_Distributors] PRIMARY KEY ([Id])
);
Anmärkning
Samlingar av värdetyper (struct) stöds inte för närvarande. Använd en referenstyp (class eller record) för komplexa samlingselement.
Mappa komplexa typer till JSON
Förutom tabelldelning tillåter EF Core 10 mappning av en (icke-samling) komplex egenskap till en enda JSON-kolumn med ToJson:
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.Address, c => c.ToJson());
b.ComplexProperty(c => c.SecondaryAddress, c => c.ToJson());
});
Varje komplext värde serialiseras sedan till en enda JSON-kolumn i stället för att spridas över flera kolumner:
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])
);
På SQL Server 2025 och Azure SQL använder EF den interna json datatypen som standard. I andra databaser och äldre SQL Server versioner lagras JSON i en textkolumn. Du kan åsidosätta kolumntypen med HasColumnType om det behövs.
Till skillnad från tabelldelning tillåter JSON-mappning samlingar inom den mappade typen, och du kan köra frågor mot och uppdatera enskilda egenskaper i dokumentet precis som andra egenskaper. Värden i JSON-kolumner kan också uppdateras effektivt i bulk med ExecuteUpdateAsync.
Nycklar och index för komplexa typegenskaper
Från och med EF Core 11 kan nycklar och index rikta in sig på skalära egenskaper kapslade i komplexa typer som inte är samlingskomplexa. Detta kan göras med en 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);
Samma sökvägar kan konfigureras med namn, med hjälp . av för att navigera till en komplex egenskap:
modelBuilder.Entity<Customer>()
.HasIndex("Address.PostCode");
För relationsprovidrar kan index också rikta sökvägar i komplexa typer som mappas till JSON-kolumner. Komplexa samlingssökvägar används [] för att referera till alla element eller en numerisk indexerare för ett specifikt 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");
Anmärkning
Indexering till en JSON-mappad komplex samling kräver en databas som stöder JSON-index, till exempel SQL Server 2025.
Mer information finns i Nycklar och index och begränsningar.
Komplexa typer med entitetsarv
Från och med EF Core 11 kan komplexa typer och JSON-kolumner användas för entitetstyper som använder TPT (tabell per typ) eller TPC (table-per-concrete-type). På så sätt kan du kombinera flexibiliteten i dessa arvsstrategier med modelleringskraften hos komplexa typer.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>()
.UseTptMappingStrategy()
.ComplexProperty(a => a.Details);
}
Spårning av ändringar
EF Core spårar ändringar av enskilda egenskaper av en komplex typ, så endast de berörda kolumnerna uppdateras när du anropar SaveChanges. Du kan inspektera och ändra spårningstillståndet via ändringsspåraren.
Använd EntityEntry.ComplexProperty för att nå en komplex egenskap och sedan öka detaljnivån för dess skalära egenskaper:
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}");
ComplexPropertyEntry API:et speglar entitets-APIEntityEntry:et: du kan läsa och ange CurrentValue, kontrollera och ange IsModifiedoch navigera till ytterligare kapslade komplexa egenskaper eller komplexa samlingar. Komplexa egenskaper exponeras också via entitetens api:er för egenskapsvärden (CurrentValues/OriginalValues).
Köra frågor mot komplexa typer
Komplexa typmedlemmar kan användas i LINQ-frågor precis som egenskaperna för själva entiteten – du kan filtrera på dem, projicera dem och sortera efter dem:
// 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();
Eftersom komplexa typer har värdesemantik kan du också jämföra ett helt komplext värde i en fråga, och EF jämför alla dess egenskaper:
var ordersToHomeAddress = await context.Orders
.Where(o => o.ShippingAddress == o.BillingAddress)
.ToListAsync();
För komplexa typer som mappas till JSON lägger EF Core 11 till EF.Functions.JsonPathExists, som kontrollerar om det finns en viss JSON-sökväg i dokumentet:
var withPostCode = await context.Customers
.Where(c => EF.Functions.JsonPathExists(c.Address, "$.PostCode"))
.ToListAsync();
Limitations
Komplexa typer är utformade för modellering av värdeobjekt och stöder avsiktligt inte alla funktioner för entitetstyper. De viktigaste begränsningarna är:
- Ingen egen identitet eller spårning. En komplex typ kan bara finnas som en del av en entitet. du kan inte ha en
DbSet<T>komplex typ eller spåra eller köra frågor mot den oberoende. - Inga navigeringar. En komplex typ får inte innehålla navigeringsegenskaper för entitetstyper.
- Ingen separat tabell. På relationsdatabaser lagras alltid en komplex typ i containerns tabell (via tabelldelning) eller i en JSON-kolumn – aldrig i en egen tabell.
- Samlingar kräver JSON. På relationsprovidrar måste komplexa samlingar mappas till JSON med
ToJson. De kan inte mappas via tabelldelning. - Samlingar av värdetyper stöds inte. Komplexa samlingselement måste vara referenstyper.
- Valfria komplexa typer kräver en obligatorisk egenskap. En valfri (nullbar) komplex typ måste definiera minst en obligatorisk egenskap.
Stöd för komplexa typer fortsätter att breddas mellan olika versioner. se de nya sidorna för de senaste tilläggen.