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 OwnsOneOnModelCreating 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:

Screenshot of the database model for entity containing owned reference

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 OnModelCreatingsouboru .

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í HasKeyprimá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:

Sceenshot of the database model for entity containing owned collection

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 OrderDetailsBillingAddress vlastní oba typy a ShippingAddresskteré 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:

Screenshot of the database model for entity containing nested owned references

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á OrderOrderDetails následující dotaz a dva vlastněné StreetAddresses z databáze:

var order = context.DetailedOrders.First(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.