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.
Met EF Core kunt u entiteitstypen modelleren die alleen kunnen worden weergegeven in navigatie-eigenschappen van andere entiteitstypen. Dit worden entiteitstypen in eigendom genoemd. De entiteit met een entiteitstype in eigendom is de eigenaar.
Entiteiten in eigendom zijn in wezen een onderdeel van de eigenaar en kunnen niet bestaan zonder deze, ze zijn conceptueel vergelijkbaar met aggregaties. Dit betekent dat de entiteit in eigendom per definitie aan de afhankelijke kant van de relatie met de eigenaar staat.
Typen configureren als eigendom
In de meeste providers worden entiteitstypen nooit geconfigureerd als eigendom door conventie. U moet de OwnsOne-methode expliciet gebruiken in OnModelCreating of het type met OwnedAttribute annoteren om het als eigendom te configureren. De Azure Cosmos DB-provider is een uitzondering hierop. Omdat Azure Cosmos DB een documentdatabase is, configureert de provider alle gerelateerde entiteitstypen standaard als eigendom.
In dit voorbeeld StreetAddress is een type zonder id-eigenschap. Het wordt gebruikt als een eigenschap van het type Order om het verzendadres voor een bepaalde bestelling op te geven.
We kunnen de OwnedAttribute entiteit gebruiken om deze te behandelen als een entiteit die eigendom is wanneer er wordt verwezen naar een ander entiteitstype:
[Owned]
public class StreetAddress
{
public string Street { get; set; }
public string City { get; set; }
}
public class Order
{
public int Id { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
Het is ook mogelijk om de OwnsOne methode OnModelCreating te gebruiken om op te geven dat de ShippingAddress eigenschap een eigendomsentiteit van het Order entiteitstype is en om indien nodig extra facetten te configureren.
modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);
Als de ShippingAddress eigenschap privé is in het Order type, kunt u de tekenreeksversie van de OwnsOne methode gebruiken:
modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");
Het bovenstaande model is toegewezen aan het volgende databaseschema:
Zie het volledige voorbeeldproject voor meer context.
Aanbeveling
Het eigendomstype van de entiteit kan als verplicht worden gemarkeerd. Zie Vereiste een-op-een afhankelijkheden voor meer informatie.
Impliciete sleutels
Eigendomstypen die zijn geconfigureerd met OwnsOne of gedetecteerd via een referentienavigatie, hebben altijd een een-op-een-relatie met de eigenaar. Daarom hebben ze hun eigen sleutelwaarden niet nodig, omdat de vreemde sleutelwaarden uniek zijn. In het vorige voorbeeld hoeft het StreetAddress type geen sleuteleigenschap te definiëren.
Om te begrijpen hoe EF Core deze objecten bijhoudt, is het nuttig om te weten dat een primaire sleutel wordt gemaakt als een schaduweigenschap voor het geleverde type. De waarde van de sleutel van een exemplaar van het eigendomstype zal dezelfde zijn als de waarde van de sleutel van het eigenaarexemplaar.
Verzamelingen van eigendomstypen
Om een verzameling van eigendomstypen te configureren, gebruik OwnsMany in OnModelCreating.
Eigendomstypen hebben een primaire sleutel nodig. Als er geen goede kandidateneigenschappen voor het .NET-type zijn, kan EF Core proberen er een te maken. Wanneer eigendomstypen echter worden gedefinieerd via een verzameling, is het niet voldoende om alleen een schaduweigenschap te creëren om zowel als de referentiesleutel naar de eigenaar als de primaire sleutel van het eigendomsexemplaar te fungeren, zoals we doen voor OwnsOne: er kunnen meerdere exemplaren van het eigendomstype voor elke eigenaar zijn, en daarom is de sleutel van de eigenaar niet voldoende om voor elk eigendomsexemplaar een unieke identiteit te verschaffen.
De twee eenvoudigste oplossingen hiervoor zijn:
- Het definiëren van een surrogaat primaire sleutel op een nieuwe eigenschap, onafhankelijk van de vreemde sleutel die verwijst naar de eigenaar. De opgenomen waarden moeten uniek zijn voor alle eigenaren (bijvoorbeeld als {1} een bovenliggend item met een onderliggend {1} element heeft, dan kan {2} geen bovenliggend item met een onderliggend {1} element hebben), zodat de waarde geen inherente betekenis heeft. Omdat de vreemde sleutel geen deel uitmaakt van de primaire sleutel, kunnen de waarden worden aangepast. Hiermee zou u een kind van de ene ouder naar een andere kunnen verplaatsen, maar dit gaat meestal in tegen de aggregaatsemantiek.
- Gebruik de foreign key en een extra eigenschap als een samengestelde sleutel. De extra eigenschapswaarde hoeft nu alleen uniek te zijn voor een bepaald bovenliggend item (dus als het bovenliggende item {1} het onderliggende element {1,1} heeft, kan het bovenliggende {2} nog steeds het onderliggende element {2,1} hebben). Door van de buitenlandse sleutel een deel van de primaire sleutel te maken, wordt de relatie tussen de eigenaar en de beheerde entiteit onveranderbaar en weerspiegelt het de aggregaatsemantiek beter. Dit is wat EF Core standaard doet.
In dit voorbeeld gebruiken we de Distributor klasse.
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
De primaire sleutel die standaard wordt gebruikt voor het type eigendom waarnaar wordt verwezen via de ShippingCenters navigatie-eigenschap, is ("DistributorId", "Id") waarbij "DistributorId" de FK is en "Id" een unieke int waarde is.
Om een andere primaire sleutel te configureren, belt u HasKey.
modelBuilder.Entity<Distributor>().OwnsMany(
p => p.ShippingCenters, a =>
{
a.WithOwner().HasForeignKey("OwnerId");
a.Property<int>("Id");
a.HasKey("Id");
});
Het bovenstaande model is toegewezen aan het volgende databaseschema:
Toewijzing van eigendomstypen met tabelsplitsing
Wanneer u relationele databases gebruikt, worden standaard referentie-typen toegewezen aan dezelfde tabel als de eigenaar. Hiervoor moet de tabel in tweeën worden gesplitst: sommige kolommen worden gebruikt om de gegevens van de eigenaar op te slaan en sommige kolommen worden gebruikt om gegevens van de entiteit in eigendom op te slaan. Dit is een algemene functie die bekend staat als tabelsplitsing.
Standaard geeft EF Core de databasekolommen een naam voor de eigenschappen van het entiteitstype in eigendom volgens het patroon Navigation_OwnedEntityProperty. Daarom worden de StreetAddress eigenschappen weergegeven in de tabel Orders met de namen 'ShippingAddress_Street' en 'ShippingAddress_City'.
U kunt de methode gebruiken om de HasColumnName naam van deze kolommen te wijzigen.
modelBuilder.Entity<Order>().OwnsOne(
o => o.ShippingAddress,
sa =>
{
sa.Property(p => p.Street).HasColumnName("ShipsToStreet");
sa.Property(p => p.City).HasColumnName("ShipsToCity");
});
Opmerking
De meeste configuratiemethoden voor het normale entiteitstype, zoals Ignore , kunnen op dezelfde manier worden aangeroepen.
Hetzelfde .NET-type delen tussen verschillende eigendomstypen
Een entiteitstype in eigendom kan van hetzelfde .NET-type zijn als een ander entiteitstype in eigendom, waardoor het .NET-type mogelijk niet voldoende is om een eigendomstype te identificeren.
In dergelijke gevallen wordt de eigenschap die van de eigenaar naar de entiteit in eigendom verwijst, de definiërende navigatie van het eigendom entiteitstype. Vanuit het perspectief van EF Core maakt de definiërende navigatie deel uit van de identiteit van het type naast het .NET-type.
Bijvoorbeeld, in de volgende klasse ShippingAddress en BillingAddress beide van hetzelfde .NET-type, StreetAddresszijn.
public class OrderDetails
{
public DetailedOrder Order { get; set; }
public StreetAddress BillingAddress { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
Om te begrijpen hoe EF Core bijgehouden instanties van deze objecten onderscheidt, kan het handig zijn om te denken dat de definiërende navigatie-eigenschap deel uitmaakt van de sleutel van de instantie, naast de waarde van de sleutel van de eigenaar en het .NET-type van het eigendomstype.
Geneste eigendomstypen
In dit voorbeeld bezit OrderDetailsBillingAddress en ShippingAddress, die beide van het type StreetAddress zijn.
OrderDetails is vervolgens eigendom van het type DetailedOrder.
public class DetailedOrder
{
public int Id { get; set; }
public OrderDetails OrderDetails { get; set; }
public OrderStatus Status { get; set; }
}
public enum OrderStatus
{
Pending,
Shipped
}
Elke navigatie naar een eigendomstype definieert een afzonderlijk entiteitstype met een volledig onafhankelijke configuratie.
Naast geneste eigendomstypen kan een eigendomstype verwijzen naar een normale entiteit, die de eigenaar of een andere entiteit kan zijn, zolang de eigendomsentiteit zich aan de afhankelijke kant bevindt. Met deze mogelijkheid worden entiteitstypen in eigendom ingesteld, afgezien van complexe typen in EF6.
public class OrderDetails
{
public DetailedOrder Order { get; set; }
public StreetAddress BillingAddress { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
Eigendomstypen configureren
Het is mogelijk om de OwnsOne methode te koppelen in een fluent-aanroep om dit model te configureren:
modelBuilder.Entity<DetailedOrder>().OwnsOne(
p => p.OrderDetails, od =>
{
od.WithOwner(d => d.Order);
od.Navigation(d => d.Order).UsePropertyAccessMode(PropertyAccessMode.Property);
od.OwnsOne(c => c.BillingAddress);
od.OwnsOne(c => c.ShippingAddress);
});
Let op de WithOwner aanroep die wordt gebruikt om de navigatie-eigenschap te definiëren die naar de eigenaar wijst. Om een navigatie naar het eigenaarsentiteitstype te definiëren dat geen deel uitmaakt van de eigendomsrelatie, moet WithOwner() zonder argumenten worden aangeroepen.
Het is ook mogelijk om dit resultaat te bereiken met zowel OwnedAttributeOrderDetails als StreetAddress.
Let ook op de Navigation oproep. Navigatie-eigenschappen voor eigendomstypen kunnen verder worden geconfigureerd zoals voor niet-eigendom navigatie-eigenschappen.
Het bovenstaande model is toegewezen aan het volgende databaseschema:
Eigendomstypen opslaan in afzonderlijke tabellen
In tegenstelling tot complexe EF6-typen kunnen eigendomstypen worden opgeslagen in een afzonderlijke tabel, los van de eigenaar. Als u de conventie wilt overschrijven waarmee een type eigendom aan dezelfde tabel wordt toegewezen als de eigenaar, kunt u gewoon een andere tabelnaam aanroepen ToTable en opgeven. In het volgende voorbeeld worden OrderDetails en zijn twee adressen toegewezen aan een afzonderlijke tabel, gescheiden van DetailedOrder.
modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od => { od.ToTable("OrderDetails"); });
Het is ook mogelijk om dit TableAttribute te doen, maar houd er rekening mee dat dit mislukt als er meerdere navigaties naar het eigendomstype zijn, omdat in dat geval meerdere entiteitstypen op dezelfde tabel worden gemapt.
Query's uitvoeren op eigendomstypen
Bij het opvragen van de eigenaar worden de eigendomstypen standaard opgenomen. Het is niet nodig om de Include-methode te gebruiken, zelfs als de eigendomstypen in een afzonderlijke tabel worden opgeslagen. Op basis van het eerder beschreven model haalt de volgende query Order, OrderDetails en de twee in StreetAddresses bezitten items uit de database.
var order = await context.DetailedOrders.FirstAsync(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.OrderDetails.ShippingAddress.City}");
Beperkingen
Sommige van deze beperkingen zijn van fundamenteel belang voor de werking van entiteitstypen in eigendom, maar sommige andere beperkingen zijn beperkingen die we mogelijk kunnen verwijderen in toekomstige releases:
Ontworpen beperkingen
- U kunt geen
DbSet<T>voor een eigendomstype aanmaken. - U kunt
Entity<T>()niet aanroepen met een eigendomstype opModelBuilder. - Exemplaren van entiteitstypen in eigendom kunnen niet worden gedeeld door meerdere eigenaren (dit is een bekend scenario voor waardeobjecten die niet kunnen worden geïmplementeerd met behulp van entiteitstypen in eigendom).
Huidige tekortkomingen
- Entiteitstypen in eigendom kunnen geen overnamehiërarchieën hebben
Tekortkomingen in eerdere versies
- In EF Core 2.x kunnen verwijzingsnavigaties naar toegeëigende entiteitstypen niet null zijn, tenzij ze expliciet aan een aparte tabel van de eigenaar zijn toegewezen.
- In EF Core 3.x worden de kolommen van ondergeschikte entiteitstypen die zijn toegewezen aan dezelfde tabel als de eigenaar altijd als nullable gemarkeerd.