Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Az adatbázisba mentett objektumok három átfogó kategóriára oszthatók:
- Strukturálatlan és egyetlen értéket tartalmazó objektumok. Például:
int,Guid,string,IPAddress. Ezeket (kissé lazán) primitív típusoknak nevezzük. - Olyan objektumok, amelyek több érték tárolására vannak strukturálva, és ahol az objektum identitását kulcsérték határozza meg. Például,
Blog,Post.CustomerEzeket entitástípusoknak nevezzük. - Azok az objektumok, amelyek több érték tárolására vannak strukturálva, de az objektum nem rendelkezik az identitását meghatározó kulccsal. Például,
Address,Coordinate.MoneyEzeket értékobjektumoknak nevezzük, és az EF Core összetett típusként képezi le őket.
Egy összetett típus több tulajdonságot csoportosít egyetlen .NET típusba, amely egy entitástípuson belül található; nem rendelkezik saját identitással, és nem követhető nyomon és nem kérdezhető le egymástól függetlenül. Így az összetett típusok természetes módon modellezhetik az értékobjektumokat.
Tip
A GitHub című cikk teljes mintaprojektjének futtatásával és hibakeresésével.
Megjegyzés:
Az összetett típusok az EF Core 8-ban jelentek meg, és a későbbi kiadásokban jelentősen bővültek. A funkciók az alábbiakban a bevezetett verzióval vannak eljegyzve.
Összetett típusok és saját entitástípusok
Mielőtt összetett típusok léteztek volna, a tulajdonos entitástípusok javasolták az objektumok kulcstulajdonságok nélküli modellezését. A saját típusok azonban továbbra is entitástípusok a színfalak mögött: rejtett kulccsal és identitással rendelkeznek, ezért referencia szemantikával működnek. Ez számos olyan súrlódási pontot okoz, amelyeket összetett típusok megoldására terveztek.
A fő különbségek a következők:
| Tulajdonság | Saját entitástípusok | Összetett típusok |
|---|---|---|
| Identitás | Rejtett kulcs és identitás | Nincs identitás; összehasonlítva érték szerint |
| Példánymegosztás | Ugyanarra a példányra nem lehet kétszer hivatkozni | Ugyanaz a példány több tulajdonsághoz is hozzárendelhető |
| Hozzárendelés szemantikája | Referencia szemantikák | Érték szemantikája (a tulajdonságok másolása) |
| .NET-típus | Csak referenciatípusok | Referencia - vagy értéktípusok |
| Táblaleképezés | Saját táblázat, tábla felosztása vagy JSON | Tároló táblája (tábla felosztása) vagy JSON |
| Navigations | Tartalmazhat más entitásokra való navigációt | Nem tartalmazhat navigációkat |
Tömeges frissítés (ExecuteUpdate) |
Nem támogatott | Támogatott |
Ha például egy ügyfél számlázási címét ugyanazzal a címmel rendeli hozzá, mint a szállítási cím, a saját entitástípusokkal meghiúsul, mert ugyanazon entitáspéldányra nem lehet többször hivatkozni:
var customer = await context.Customers.SingleAsync(c => c.Id == someId);
customer.BillingAddress = customer.ShippingAddress;
await context.SaveChangesAsync(); // Throws with owned entity types
Mivel az összetett típusok értékszemantikával rendelkeznek, ugyanaz a hozzárendelés egyszerűen átmásolja a tulajdonságokat, és a várt módon működik. Hasonlóképpen, egy LINQ-lekérdezés két összetett értékének összehasonlítása összehasonlítja azok tartalmát, míg két saját entitás összehasonlítása összehasonlítja az identitásukat.
Ezen okok miatt általában az összetett típusok a jobb választás értékobjektumok modellezéséhez táblafelosztással vagy JSON-leképezéssel. A jelenleg saját entitástípusokat használó felhasználók számára ajánlott megfontolni az összetett típusokra való váltást.
Egy egyszerű példa
Vegyünk egy olyan típust Address , amely több kapcsolódó értéket tartalmaz, de nem rendelkezik saját identitással:
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 ezután több helyen is használható egy ügyfél-/rendelési modellben:
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!;
}
Az ügyfél létrehozása és mentése a szokásos módon működik:
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();
Egy relációs adatbázisban az összetett típus nem kap saját táblát. Ehelyett a tulajdonságokat a rendszer további oszlopokként menti a beágyazottba az adott entitás tábláján (ezt táblafelosztásnak nevezzük):
INSERT INTO [Customers] ([Name], [Address_City], [Address_Country], [Address_Line1], [Address_Line2], [Address_PostCode])
OUTPUT INSERTED.[Id]
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
Mivel az összetett típusok értékszemantikával rendelkeznek, ugyanaz Address a példány több tulajdonság között is megosztható probléma nélkül:
// 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();
Összetett típusok konfigurálása
A legtöbb entitástípustól eltérően az összetett típusokat nem a konvenciók derítik fel. Ezeket explicit módon kell konfigurálnia, akár a típus megjegyzésével, akár a ComplexProperty Fluent API meghívásával ComplexTypeAttributeminden olyan tulajdonságbanOnModelCreating, amelyet összetett típusként kell leképezni:
[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; }
}
Összetett típusú tulajdonságok aspektusainak konfigurálása
A beágyazott Property szerkesztő egy összetett típus skaláris tulajdonságainak konfigurálására használható, ugyanúgy, mint egy entitástípus tulajdonságai – például az oszlopnév vagy a maximális hossz beállításához:
modelBuilder.Entity<Order>()
.ComplexProperty(
o => o.ShippingAddress,
b =>
{
b.Property(a => a.Line1).HasColumnName("ShipsToStreet").HasMaxLength(100);
b.Property(a => a.City).HasColumnName("ShipsToCity");
});
Az EF Core 11-től kezdve konfigurálhat egy összetett típusba beágyazott tulajdonságot közvetlenül a lambdában való taghozzáférés láncolásával anélkül, hogy először beszerezné a komplex típusú szerkesztőt:
// 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);
Referencia- és értéktípusok
Az összetett típus lehet .NET referenciatípus (a class vagy record) vagy értéktípus (structaz EF Core 10-ben bevezetett a vagyrecord struct).
Mutability
Mivel egy referencia típusú példány több tulajdonsággal is megosztható, az egyik tulajdonságának mutációja mindenhol megváltoztatja az értéket. Általában nem ezt szeretné. Ennek elkerülésének jó módja – és az értékobjektumok természetes illeszkedése – az, hogy az összetett típus nem módosítható, így az érték módosítása új példány létrehozását igényli. Az Address ebben a cikkben használt típus nem módosítható record, ezért a cím módosítása egy with kifejezéssel történik:
// 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();
Annak ellenére, hogy egy teljesen új Address példány van hozzárendelve, az EF továbbra is nyomon követi a változásokat az egyes tulajdonságszinten, így csak azok az oszlopok frissülnek, amelyek értékei ténylegesen módosultak:
UPDATE [Customers] SET [Address_Line1] = @p0
OUTPUT 1
WHERE [Id] = @p1;
A nem módosíthatóság kifejezhető nem módosítható class (nem módosítható vagy írásvédett tulajdonságokkal), a record, a readonly structvagy a readonly record struct. Az értéktípusok (struct) másolási szemantikával rendelkeznek, ezért az értékek hozzárendelése mindig másolja az értékeket, és elkerüli a véletlen megosztási problémát még akkor is, ha a rendszer nem tudja használni a dokumentumokat – de A C#-ban általában elriasztja a rendszer a mutable-szerkezeteket, ezért továbbra is javasolt a nem módosítható űrlap használata.
Tip
Ha több entitásnak valóban meg kell figyelnie ugyanazt a címet, és együtt kell frissítenie, amikor változik, akkor a címet saját identitással rendelkező entitástípusként kell modelleznie, és egy navigáción keresztül kell hivatkoznia rá ahelyett, hogy összetett típust használna.
Beágyazott összetett típusok
Az összetett típusok más összetett típusok tulajdonságait is tartalmazhatják, így bármilyen mélységig strukturált objektumokat hozhat létre. Egy összetett típus tartalmazhat például Contact egy és egy vagy több PhoneNumber összetett típust Address is:
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; }
}
Táblázatfelosztással történő megfeleltetéskor a beágyazott komplex típusú oszlopok előtagja a tulajdonság teljes elérési útja (például Contact_HomePhone_Number).
Választható összetett típusok
Alapértelmezés szerint összetett tulajdonságra van szükség: a CLR tulajdonságnak mindig rendelkeznie kell értékkel, és nem null értékű oszlopokra van leképezve. Az EF Core 10-től kezdődően egy összetett tulajdonság választhatóvá tehető úgy, hogy null értékűként deklaráljuk:
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!;
}
Nem kötelező összetett tulajdonság, amely null az összes oszlopában értékeket eredményez NULL .
Megjegyzés:
Az opcionális összetett típushoz jelenleg legalább egy szükséges tulajdonságot kell definiálni az összetett típuson. Ennek az az oka, hogy az EF-nek legalább egy nem null értékű oszlopra van szüksége ahhoz, hogy meg lehessen különböztetni egy null összetett értéket egy összetett értéktől, amelynek a tulajdonságai mind előfordulnak null.
Ha az összetett típusnak nincs saját kötelező tulajdonsága, akkor ehelyett konfigurálhat egy diszkriminatív tulajdonságot. Bár az EF Core még nem támogatja az összetett típusok öröklését, a diszkriminatív alapértelmezés szerint kötelező árnyéktulajdonságként jön létre, amely megfelel a fenti követelménynek:
// 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());
Összetett típusok gyűjteményei
Az EF Core 10-től kezdve egy tulajdonság összetett típusok gyűjteményét is tartalmazhatja. A relációs adatbázisokban az összetett gyűjteményeket egyetlen JSON-oszlopra ToJson kell leképezni– nem képezhetők le másik táblára:
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());
A gyűjtemény minden eleme JSON-objektumként van tárolva a tömbben, és a teljes gyűjtemény egy oszlopra van leképezve:
CREATE TABLE [Distributors] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[ShippingCenters] json NOT NULL,
CONSTRAINT [PK_Distributors] PRIMARY KEY ([Id])
);
Megjegyzés:
Az értéktípusok gyűjteményei (struct) jelenleg nem támogatottak; összetett gyűjteményelemekhez használjon referenciatípust (class vagy record) .
Összetett típusok leképezése JSON-ra
A táblák felosztása mellett az EF Core 10 lehetővé teszi egy (nem gyűjteménybeli) összetett tulajdonság egyetlen JSON-oszlopra való leképezését a következőkkel ToJson:
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.Address, c => c.ToJson());
b.ComplexProperty(c => c.SecondaryAddress, c => c.ToJson());
});
Ezután minden összetett érték egyetlen JSON-oszlopba van szerializálva, nem pedig több oszlopra:
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])
);
A 2025-ös és Azure SQL SQL Server az EF alapértelmezés szerint a natív json adattípust használja; más adatbázisokban és régebbi SQL Server verziókban a JSON egy szövegoszlopban van tárolva. Szükség esetén felülbírálhatja az oszloptípust HasColumnType .
A táblázatfelosztástól eltérően a JSON-leképezés lehetővé teszi a leképezett típuson belüli gyűjtemények használatát, és lehetővé teszi a dokumentum egyes tulajdonságainak lekérdezését és frissítését, mint bármely más tulajdonságot. A JSON-oszlopokban lévő értékek tömegesen ExecuteUpdateAsyncis frissíthetők.
Kulcsok és indexek összetett típustulajdonságokon
Az EF Core 11-től kezdve a kulcsok és az indexek a nem gyűjtemény-összetett típusokba ágyazott skaláris tulajdonságokat célozhatják meg. Ez lambdával is elvégezhető:
// 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);
Ugyanezek az útvonalak név szerint konfigurálhatók egy összetett tulajdonságra való navigáláshoz . :
modelBuilder.Entity<Customer>()
.HasIndex("Address.PostCode");
A relációs szolgáltatók esetében az indexek A JSON-oszlopokra leképezett összetett típusok útvonalait is megcélzhatják. Az összetett gyűjteményútvonalak [] az összes elemre, vagy egy adott elem numerikus indexelőire hivatkoznak:
// 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");
Megjegyzés:
A JSON-megfeleltetett összetett gyűjteménybe való indexeléshez olyan adatbázisra van szükség, amely támogatja a JSON-indexeket, például a SQL Server 2025-öt.
További információ: Kulcsok , indexek és korlátozások.
Entitásörökléssel rendelkező összetett típusok
Az EF Core 11-től kezdve összetett típusok és JSON-oszlopok használhatók TPT -t (tábla/típus) vagy TPC-t (tábla/beton típusú) használó entitástípusokon. Ez lehetővé teszi ezeknek az öröklési stratégiáknak a rugalmasságát az összetett típusok modellezési erejével.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>()
.UseTptMappingStrategy()
.ComplexProperty(a => a.Details);
}
Változások követése
Az EF Core egy összetett típus egyes tulajdonságainak változásait követi nyomon, így híváskor SaveChangescsak az érintett oszlopok frissülnek. Ezt a nyomkövetési állapotot a változáskövetőn keresztül vizsgálhatja meg és módosíthatja.
Összetett tulajdonság elérésére, majd a skaláris tulajdonságok részletezésére használható EntityEntry.ComplexProperty :
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}");
Az ComplexPropertyEntry API tükrözi az entitás EntityEntry API-t: elolvashatja és beállíthatja CurrentValue, ellenőrizheti és beállíthatja IsModified, és további beágyazott összetett tulajdonságokba vagy összetett gyűjteményekbe navigálhat. Az összetett tulajdonságok az entitás tulajdonságértékeivel (API-kkal)CurrentValues/OriginalValues is elérhetők.
Összetett típusok lekérdezése
Az összetett típusú tagok ugyanúgy használhatók a LINQ-lekérdezésekben, mint az entitás tulajdonságai – szűrheti őket, kivetítheti őket, és rendezheti őket:
// 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();
Mivel az összetett típusok értékszemantikával rendelkeznek, egy lekérdezés teljes komplex értékét is összehasonlíthatja, az EF pedig az összes tulajdonságát összehasonlítja:
var ordersToHomeAddress = await context.Orders
.Where(o => o.ShippingAddress == o.BillingAddress)
.ToListAsync();
A JSON-ra leképezett összetett típusok esetében az EF Core 11 hozzáadja EF.Functions.JsonPathExistsa következőt, amely ellenőrzi, hogy létezik-e egy adott JSON-elérési út a dokumentumban:
var withPostCode = await context.Customers
.Where(c => EF.Functions.JsonPathExists(c.Address, "$.PostCode"))
.ToListAsync();
Limitations
Az összetett típusok értékobjektumok modellezésére lettek tervezve, és szándékosan nem támogatják az entitástípusok minden képességét. A fő korlátozások a következők:
- Nincs saját identitása vagy nyomon követése. Összetett típus csak egy entitás részeként létezhet; nem rendelkezhet
DbSet<T>összetett típussal, és nem követheti nyomon vagy kérdezheti le egymástól függetlenül. - Nincsenek navigációk. Az összetett típus nem tartalmazhat entitástípusokra jellemző navigációs tulajdonságokat.
- Nincs külön tábla. A relációs adatbázisokban a rendszer mindig egy összetett típust tárol a tároló táblájában (táblafelosztással) vagy egy JSON-oszlopban – soha nem a saját táblájában.
- A gyűjtemények JSON-t igényelnek. A relációs szolgáltatóknál az összetett gyűjteményeket JSON-ra
ToJsonkell leképezni; a táblák felosztásával nem képezhetők le. - Az értéktípusok gyűjteményei nem támogatottak. Az összetett gyűjteményelemeknek referenciatípusoknak kell lenniük.
- Az opcionális összetett típusokhoz szükség van egy szükséges tulajdonságra. A választható (null értékű) összetett típusnak legalább egy kötelező tulajdonságot meg kell határoznia.
Az összetett típusú támogatás továbbra is szélesedik a kiadások között; tekintse meg a legújabb kiegészítések új lapjait.