Vlastněné typy entit
EF Core umožňuje modelovat typy entit, které se dají zobrazit pouze na navigačních vlastnostech jiných typů entit. Tyto typy entit se nazývají vlastněné typy entit. Entita obsahující typ vlastněné entity je jejím vlastníkem.
Vlastněné entity jsou v podstatě součástí vlastníka a nemohou bez něj existovat, jsou koncepčně podobné agregacím. To znamená, že vlastněná entita je podle definice na závislé straně vztahu s vlastníkem.
Konfigurace typů jako vlastněných
Ve většině zprostředkovatelů nejsou typy entit nikdy nakonfigurovány jako vlastněné konvencí – k konfiguraci typu jako vlastněného musíte explicitně použít OwnsOne
metodu OnModelCreating
nebo k tomu, aby se typ OwnedAttribute
nakonfiguroval jako vlastněný. Zprostředkovatel služby Azure Cosmos DB je výjimkou. Vzhledem k tomu, že Azure Cosmos DB je databáze dokumentů, poskytovatel ve výchozím nastavení nakonfiguruje všechny související typy entit jako vlastněné.
V tomto příkladu je typ bez StreetAddress
vlastnosti identity. Slouží jako vlastnost typu Objednávka k určení dodací adresy pro určitou objednávku.
Při odkazech z jiného typu entity můžeme tuto entitu OwnedAttribute
použít jako vlastněnou entitu:
[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; }
}
Metodu OwnsOne
OnModelCreating
je také možné použít k určení, že ShippingAddress
vlastnost je vlastněnou entitou Order
typu entity a v případě potřeby nakonfigurovat další omezující vlastnosti.
modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);
ShippingAddress
Pokud je vlastnost v Order
typu soukromá, můžete použít řetězcovou verzi OwnsOne
metody:
modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");
Výše uvedený model je mapován na následující schéma databáze:
Další kontext najdete v úplném ukázkovém projektu .
Tip
Typ vlastněné entity lze označit jako povinný. Další informace najdete v tématu Povinné závislé položky 1:1.
Implicitní klíče
Vlastněné typy nakonfigurované OwnsOne
nebo zjištěné prostřednictvím referenční navigace mají vždy vztah 1:1 s vlastníkem, a proto nepotřebují vlastní hodnoty klíče, protože hodnoty cizího klíče jsou jedinečné. V předchozím příkladu StreetAddress
typ nemusí definovat vlastnost klíče.
Abyste pochopili, jak EF Core sleduje tyto objekty, je užitečné vědět, že primární klíč je vytvořen jako stínová vlastnost pro vlastněný typ. Hodnota klíče instance vlastněného typu bude stejná jako hodnota klíče instance vlastníka.
Kolekce vlastněných typů
Konfigurace kolekce vlastněných typů se používá OwnsMany
v OnModelCreating
souboru .
Vlastněné typy potřebují primární klíč. Pokud typ .NET neobsahuje žádné dobré vlastnosti kandidáta, ef Core se ho může pokusit vytvořit. Pokud jsou však vlastní typy definovány prostřednictvím kolekce, nestačí vytvořit stínovou vlastnost, která bude fungovat jako cizí klíč pro vlastníka i primární klíč vlastněné instance, jak to děláme OwnsOne
: pro každého vlastníka může existovat více vlastněných instancí typu, a proto klíč vlastníka nestačí k poskytnutí jedinečné identity pro každou vlastněnou instanci.
Toto jsou dvě nejjednodušší řešení:
- Definování náhradního primárního klíče na nové vlastnosti nezávislé na cizím klíči, který odkazuje na vlastníka. Obsažené hodnoty by musely být jedinečné pro všechny vlastníky (např. pokud nadřazený prvek obsahuje podřízenou {1}položku{1}, pak nadřazený prvek nemůže mít podřízenou {2} {1}hodnotu), takže hodnota nemá žádný vlastní význam. Vzhledem k tomu, že cizí klíč není součástí primárního klíče, je možné změnit jeho hodnoty, takže byste mohli přesunout dítě z jednoho nadřazeného objektu do druhého, ale obvykle jde o agregovanou sémantiku.
- Použití cizího klíče a další vlastnosti jako složeného klíče. Další hodnota vlastnosti teď musí být jedinečná pouze pro danou nadřazenou položku (takže pokud nadřazený prvek obsahuje podřízenou {1} {1,1} položku, nadřazený objekt může stále mít podřízené {2} {2,1}). Díky tomu, že část cizího klíče primárního klíče bude vztah mezi vlastníkem a vlastněnou entitou neměnný a lépe odráží agregovanou sémantiku. To je to, co EF Core ve výchozím nastavení dělá.
V tomto příkladu Distributor
použijeme třídu.
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
Ve výchozím nastavení bude primární klíč používaný pro vlastněný typ odkazovaný prostřednictvím ShippingCenters
navigační vlastnosti ("DistributorId", "Id")
tam, kde "DistributorId"
je FK a "Id"
je jedinečná int
hodnota.
Konfigurace jiného volání HasKey
primárního klíče .
modelBuilder.Entity<Distributor>().OwnsMany(
p => p.ShippingCenters, a =>
{
a.WithOwner().HasForeignKey("OwnerId");
a.Property<int>("Id");
a.HasKey("Id");
});
Výše uvedený model je mapován na následující schéma databáze:
Mapování vlastněných typů s rozdělením tabulky
Při použití relačních databází jsou ve výchozím nastavení typy vlastněné odkazy mapovány na stejnou tabulku jako vlastník. To vyžaduje rozdělení tabulky do dvou sloupců: některé sloupce se použijí k ukládání dat vlastníka a některé sloupce se použijí k ukládání dat vlastněné entity. Jedná se o běžnou funkci, která se označuje jako rozdělení tabulky.
Ef Core ve výchozím nastavení pojmenuje sloupce databáze pro vlastnosti vlastněného typu entity podle vzoru Navigation_OwnedEntityProperty. StreetAddress
Vlastnosti se proto zobrazí v tabulce Orders s názvy "ShippingAddress_Street" a "ShippingAddress_City".
Tuto metodu HasColumnName
můžete použít k přejmenování těchto sloupců.
modelBuilder.Entity<Order>().OwnsOne(
o => o.ShippingAddress,
sa =>
{
sa.Property(p => p.Street).HasColumnName("ShipsToStreet");
sa.Property(p => p.City).HasColumnName("ShipsToCity");
});
Poznámka:
Většina běžných metod konfigurace typu entity, jako je Ignore , se dá volat stejným způsobem.
Sdílení stejného typu .NET mezi více vlastněnými typy
Typ vlastněné entity může být stejného typu .NET jako jiný typ vlastněné entity, takže typ .NET nemusí být dostatečný k identifikaci vlastněného typu.
V těchto případech se vlastnost odkazující od vlastníka na vlastněnou entitu stane definováním navigace typu vlastněné entity. Z pohledu EF Core je definování navigace součástí identity typu spolu s typem .NET.
Například v následující třídě ShippingAddress
a BillingAddress
jsou oba stejného typu .NET, StreetAddress
.
public class OrderDetails
{
public DetailedOrder Order { get; set; }
public StreetAddress BillingAddress { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
Abyste pochopili, jak EF Core rozpozná sledované instance těchto objektů, může být užitečné si myslet, že definice navigace se stala součástí klíče instance spolu s hodnotou klíče vlastníka a typu .NET vlastněného typu.
Vnořené typy vlastněné
V tomto příkladu OrderDetails
BillingAddress
vlastní oba typy a ShippingAddress
které jsou oba StreetAddress
typy. Potom OrderDetails
je vlastníkem DetailedOrder
typu.
public class DetailedOrder
{
public int Id { get; set; }
public OrderDetails OrderDetails { get; set; }
public OrderStatus Status { get; set; }
}
public enum OrderStatus
{
Pending,
Shipped
}
Každá navigace na vlastněný typ definuje samostatný typ entity s zcela nezávislou konfigurací.
Kromě vnořených vlastněných typů může vlastněný typ odkazovat na běžnou entitu, která může být vlastníkem nebo jinou entitou, pokud je vlastněná entita na závislé straně. Tato schopnost nastavuje typy entit kromě složitých typů v EF6.
public class OrderDetails
{
public DetailedOrder Order { get; set; }
public StreetAddress BillingAddress { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
Konfigurace vlastněných typů
Metodu OwnsOne
je možné zřetězením v plynulém volání nakonfigurovat tento model:
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);
});
WithOwner
Všimněte si volání, které se používá k definování navigační vlastnosti odkazující zpět na vlastníka. Chcete-li definovat navigaci na typ entity vlastníka, který není součástí vztahu WithOwner()
vlastnictví, by se měl volat bez argumentů.
Je také možné dosáhnout tohoto výsledku pomocí OwnedAttribute
obou OrderDetails
a StreetAddress
.
Kromě toho si všimněte Navigation
hovoru. Navigační vlastnosti pro vlastněné typy lze dále nakonfigurovat jako pro nevlastní navigační vlastnosti.
Výše uvedený model je mapován na následující schéma databáze:
Ukládání vlastněných typů v samostatných tabulkách
Na rozdíl od komplexních typů EF6 je také možné vlastní typy ukládat do samostatné tabulky od vlastníka. Pokud chcete přepsat konvenci, která mapuje vlastněný typ na stejnou tabulku jako vlastník, můžete jednoduše zavolat ToTable
a zadat jiný název tabulky. Následující příklad namapuje OrderDetails
a jeho dvě adresy na samostatnou tabulku od DetailedOrder
:
modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od => { od.ToTable("OrderDetails"); });
Je také možné použít TableAttribute
k tomu, aby to bylo možné, ale mějte na paměti, že pokud existuje více navigace na vlastněný typ, protože v takovém případě by se více typů entit namapovalo na stejnou tabulku.
Dotazování vlastněných typů
Při dotazování na vlastníka budou ve výchozím nastavení zahrnuty vlastněné typy. Není nutné použít metodu Include
, i když jsou vlastněné typy uloženy v samostatné tabulce. Na základě modelu popsaného dříve se získá Order
OrderDetails
následující dotaz a dva vlastněné StreetAddresses
z databáze:
var order = await context.DetailedOrders.FirstAsync(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.OrderDetails.ShippingAddress.City}");
Omezení
Některá z těchto omezení jsou zásadní pro fungování typů entit ve vlastnictví, ale některé jiné jsou omezení, která můžeme v budoucích verzích odebrat:
Omezení podle návrhu
- Nelze vytvořit vlastní
DbSet<T>
typ. - Nelze volat
Entity<T>()
s vlastním typem .ModelBuilder
- Instance vlastněných typů entit nemohou být sdíleny více vlastníky (jedná se o dobře známý scénář pro objekty hodnot, které nelze implementovat pomocí vlastněných typů entit).
Aktuální nedostatky
- Typy vlastněných entit nemohou mít hierarchie dědičnosti.
Nedostatky v předchozích verzích
- V odkazových navigaci EF Core 2.x na vlastněné typy entit nesmí být null, pokud nejsou explicitně namapovány na samostatnou tabulku od vlastníka.
- V EF Core 3.x sloupce pro vlastněné typy entit mapované na stejnou tabulku jako vlastník jsou vždy označeny jako nullable.