Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
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.
Návod
Pokud modelujete objekt hodnoty – objekt bez vlastní identity, například AddressCoordinate nebo – zvažte použití komplexního typu místo typu vlastněné entity. Na rozdíl od vlastněných typů mají komplexní typy sémantiku hodnot a žádný skrytý klíč, který zabraňuje řadě nástrah; viz Komplexní typy vs. vlastněné typy entit pro porovnání.
Konfigurace typů jako vlastněných objektů
Ve většině zprostředkovatelů nejsou typy entit nikdy nakonfigurovány jako vlastněné podle konvence – k tomu musíte explicitně použít metodu OwnsOne v OnModelCreating nebo anotovat typ pomocí OwnedAttribute, aby byl typ nakonfigurován 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 StreetAddress typem bez vlastností identity. Slouží jako vlastnost typu Objednávka k určení dodací adresy pro určitou objednávku.
Můžeme s ní OwnedAttribute zacházet jako s vlastněnou entitou při odkazování z jiného typu entity.
[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; }
}
Je také možné použít metodu OwnsOne v OnModelCreating k určení, že vlastnost ShippingAddress je vlastněné entity typu Order a v případě potřeby nakonfigurovat další aspekty.
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 .
Návod
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ů
K konfiguraci kolekce vlastněných typů použijte OwnsMany v OnModelCreating.
Vlastněné typy potřebují primární klíč. Pokud typ .NET neobsahuje žádné vhodné vlastnosti kandidáta, EF Core se je může pokusit vytvořit. Pokud jsou vlastněné typy definovány prostřednictvím kolekce, nestačí vytvořit stínovou vlastnost, jež slouží jako cizí klíč vůči vlastníku i jako primární klíč pro vlastněnou instanci, jak to činíme OwnsOne: může totiž existovat více instancí vlastněného typu pro každého vlastníka, a klíč vlastníka tudíž není dostačující k zajištění 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ém atributu nezávislém na cizím klíči, který ukazuje na vlastníka. Obsahované hodnoty by musely být jedinečné pro všechny vlastníky (např. pokud nadřazený objekt obsahuje podřízený objekt {1}, pak nadřazený objekt nemůže mít podřízený objekt {1}{2}), 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 podřízený objekt z jednoho nadřazeného objektu do jiného. To však obvykle jde proti sémantice agregátu.
- Použití cizího klíče a další vlastnosti jako složený klíč. 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}). Tím, že se cizí klíč stane součástí primárního klíče, se vztah mezi vlastníkem a vlastněnou entitou stává neměnným a lépe odráží agregátní sémantiku. To je to, co EF Core ve výchozím nastavení dělá.
V tomto příkladu použijeme třídu Distributor.
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
Ve výchozím nastavení se primární klíč používaný pro vlastněný typ odkazuje prostřednictvím ShippingCenters navigační vlastnosti ("DistributorId", "Id"), kde "DistributorId" je FK a "Id" je jedinečná int hodnota.
Chcete-li konfigurovat jiný primární klíč, zavolejte HasKey.
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ů pomocí sdílení tabulky
Při použití relačních databází jsou ve výchozím nastavení typy vlastněné odkazem 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 vlastní BillingAddress a ShippingAddress, které jsou oba typu StreetAddress.
OrderDetails je pak vlastněn typem DetailedOrder.
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 k vlastněnému typu definuje samostatný typ entity se zcela nezávislou konfigurací.
Kromě vnořených vlastněných typů může vlastněný typ odkazovat na běžnou entitu, což může být vlastník nebo jiná entita, pokud je vlastněná entita v roli závislé strany. Tato funkce odlišuje typy vlastněných entit od 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);
});
Všimněte si volání WithOwner, 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 vlastnictví, zavolejte WithOwner() bez argumentů.
Je také možné dosáhnout tohoto výsledku použitím OwnedAttribute na obou OrderDetails i 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 dosažení tohoto cíle, ale mějte na paměti, že by to selhalo, pokud existuje více navigací na vlastněný typ, protože by v tom případě více typů entit bylo namapováno 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 následující dotaz získá Order, OrderDetails a dvě 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
DbSet<T>pro vlastněný 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
- Odkazové navigace v EF Core 2.x na vlastněné typy entit nemohou být null, pokud nejsou explicitně namapovány na samostatnou tabulku než té vlastnické.
- V EF Core 3.x jsou sloupce pro vlastněné typy entit, které jsou mapovány na stejnou tabulku jako vlastník, vždy označeny jako nulovatelné.