Posiadane typy jednostek

Program EF Core umożliwia modelowanie typów jednostek, które mogą być wyświetlane tylko we właściwościach nawigacji innych typów jednostek. Są to nazywane typami jednostek należących do firmy. Jednostka zawierająca typ jednostki będącej własnością jest jego właścicielem.

Jednostki należące są zasadniczo częścią właściciela i nie mogą istnieć bez niego, są one koncepcyjnie podobne do agregacji. Oznacza to, że jednostka będąca własnością jest zdefiniowana po stronie zależnej relacji z właścicielem.

Konfigurowanie typów jako należących do

W większości dostawców typy jednostek nigdy nie są konfigurowane jako należące do konwencji — należy jawnie użyć OwnsOne metody w OnModelCreating programie lub dodać adnotację do typu w OwnedAttribute celu skonfigurowania typu jako należącego do użytkownika. Dostawca usługi Azure Cosmos DB jest wyjątkiem od tego. Ponieważ usługa Azure Cosmos DB jest bazą danych dokumentów, dostawca domyślnie konfiguruje wszystkie powiązane typy jednostek jako należące do użytkownika.

W tym przykładzie StreetAddress jest to typ bez właściwości tożsamości. Jest on używany jako właściwość typu Zamówienie, aby określić adres wysyłki dla określonego zamówienia.

Możemy użyć elementu OwnedAttribute , aby traktować go jako jednostkę będącą własnością w przypadku przywołowania z innego typu jednostki:

[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; }
}

Można również użyć OwnsOne metody w , OnModelCreating aby określić, że ShippingAddress właściwość jest własnością jednostki Order typu jednostki i skonfigurować dodatkowe aspekty w razie potrzeby.

modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);

ShippingAddress Jeśli właściwość jest prywatna w typieOrder, możesz użyć wersji OwnsOne ciągu metody:

modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");

Powyższy model jest mapowany na następujący schemat bazy danych:

Screenshot of the database model for entity containing owned reference

Zobacz pełny przykładowy projekt, aby uzyskać więcej kontekstu.

Napiwek

Typ jednostki należącej do użytkownika można oznaczyć jako wymagany. Aby uzyskać więcej informacji, zobacz Wymagane zależności jeden do jednego.

Klucze niejawne

Typy należące do właścicieli skonfigurowane za OwnsOne pomocą nawigacji referencyjnej lub odnajdywane za pomocą nawigacji referencyjnej zawsze mają relację jeden do jednego z właścicielami, dlatego nie potrzebują własnych wartości klucza, ponieważ wartości klucza obcego są unikatowe. W poprzednim przykładzie StreetAddress typ nie musi definiować właściwości klucza.

Aby zrozumieć, jak program EF Core śledzi te obiekty, warto wiedzieć, że klucz podstawowy jest tworzony jako właściwość w tle dla typu należącego do użytkownika. Wartość klucza wystąpienia typu należącego do właściciela będzie taka sama jak wartość klucza wystąpienia właściciela.

Kolekcje typów należących do

Aby skonfigurować kolekcję typów należących do użytkownika, użyj OwnsMany polecenia w programie OnModelCreating.

Typy należące do użytkownika wymagają klucza podstawowego. Jeśli w typie platformy .NET nie ma dobrych właściwości kandydatów, program EF Core może spróbować go utworzyć. Jednak jeśli typy własności są definiowane za pośrednictwem kolekcji, nie wystarczy tylko utworzyć właściwość w tle, aby działać zarówno jako klucz obcy w właścicielu, jak i klucz podstawowy wystąpienia należącego do użytkownika, tak jak w OwnsOneprzypadku : może istnieć wiele wystąpień typu dla każdego właściciela, a tym samym klucz właściciela nie wystarczy, aby zapewnić unikatową tożsamość dla każdego wystąpienia należącego do właściciela.

Dwa najprostsze rozwiązania tego problemu to:

  • Definiowanie zastępczego klucza podstawowego na nowej właściwości niezależnie od klucza obcego wskazującego właściciela. Zawarte wartości muszą być unikatowe dla wszystkich właścicieli (np. jeśli element nadrzędny ma element podrzędny {1}, element nadrzędny {2}{1} nie może mieć elementu podrzędnego {1}), więc wartość nie ma żadnego znaczenia. Ponieważ klucz obcy nie jest częścią klucza podstawowego, można zmienić jego wartości, więc można przenieść element podrzędny z jednego elementu nadrzędnego do innego, jednak zwykle jest to związane z semantykami agregacji.
  • Używanie klucza obcego i dodatkowej właściwości jako klucza złożonego. Dodatkowa wartość właściwości musi teraz być unikatowa tylko dla danego elementu nadrzędnego (więc jeśli element nadrzędny ma element podrzędny {1}{1,1} , element nadrzędny {2} może nadal mieć element podrzędny {2,1}). Dzięki dokonaniu części klucza obcego klucza podstawowego relacja między właścicielem a jednostką będącą własnością staje się niezmienna i odzwierciedla zagregowaną semantykę lepiej. To jest to, co program EF Core robi domyślnie.

W tym przykładzie użyjemy Distributor klasy .

public class Distributor
{
    public int Id { get; set; }
    public ICollection<StreetAddress> ShippingCenters { get; set; }
}

Domyślnie klucz podstawowy używany dla typu należącego do elementu, do którego odwołuje się ShippingCenters właściwość nawigacji, to ("DistributorId", "Id") miejsce, w którym "DistributorId" znajduje się klucz FK i "Id" jest unikatową int wartością.

Aby skonfigurować inne wywołanie HasKeyklucza podstawowego.

modelBuilder.Entity<Distributor>().OwnsMany(
    p => p.ShippingCenters, a =>
    {
        a.WithOwner().HasForeignKey("OwnerId");
        a.Property<int>("Id");
        a.HasKey("Id");
    });

Powyższy model jest mapowany na następujący schemat bazy danych:

Sceenshot of the database model for entity containing owned collection

Mapowanie typów należących do tabeli z podziałem tabel

W przypadku korzystania z relacyjnych baz danych domyślnie typy należące do odwołań są mapowane na tę samą tabelę co właściciel. Wymaga to podzielenia tabeli w dwóch kolumnach: niektóre kolumny będą używane do przechowywania danych właściciela, a niektóre kolumny będą używane do przechowywania danych jednostki należącej do właściciela. Jest to typowa funkcja znana jako dzielenie tabel.

Domyślnie program EF Core będzie nazywać kolumny bazy danych właściwości typu jednostki należącej do użytkownika zgodnie ze wzorcem Navigation_OwnedEntityProperty. StreetAddress W związku z tym właściwości będą wyświetlane w tabeli "Orders" z nazwami "ShippingAddress_Street" i "ShippingAddress_City".

Możesz użyć HasColumnName metody , aby zmienić nazwy tych kolumn.

modelBuilder.Entity<Order>().OwnsOne(
    o => o.ShippingAddress,
    sa =>
    {
        sa.Property(p => p.Street).HasColumnName("ShipsToStreet");
        sa.Property(p => p.City).HasColumnName("ShipsToCity");
    });

Uwaga

Większość normalnych metod konfiguracji typu jednostki, takich jak Ignoruj , może być wywoływana w taki sam sposób.

Udostępnianie tego samego typu platformy .NET między wieloma typami należącymi do firmy

Typ jednostki należącej może być tego samego typu platformy .NET co inny typ jednostki należącej do użytkownika, dlatego typ platformy .NET może nie wystarczyć do zidentyfikowania typu należącego do użytkownika.

W takich przypadkach właściwość wskazująca od właściciela do jednostki będącej własnością staje się definiowaną nawigacją typu jednostki należącej do użytkownika. Z punktu widzenia platformy EF Core definiowana nawigacja jest częścią tożsamości typu obok typu .NET.

Na przykład w poniższej klasie ShippingAddress i BillingAddress są tymi samymi typami platformy .NET. StreetAddress

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Aby zrozumieć, w jaki sposób program EF Core będzie rozróżniał śledzone wystąpienia tych obiektów, warto pomyśleć, że zdefiniowana nawigacja stała się częścią klucza wystąpienia wraz z wartością klucza właściciela i typu platformy .NET typu własności.

Zagnieżdżone typy należące do

W tym przykładzie OrderDetails należą do BillingAddress nich typy i ShippingAddress, które są oba StreetAddress typy. Następnie OrderDetails jest własnością 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żda nawigacja do typu należącego definiuje oddzielny typ jednostki z całkowicie niezależną konfiguracją.

Oprócz zagnieżdżonych typów należących do użytkownika typ może odwoływać się do jednostki regularnej, która może być właścicielem lub inną jednostką, o ile jednostka będąca własnością znajduje się po stronie zależnej. Ta funkcja ustawia typy jednostek będących własnością oprócz typów złożonych w programie EF6.

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Konfigurowanie typów własności

Istnieje możliwość utworzenia OwnsOne łańcucha metody w płynnym wywołaniu w celu skonfigurowania tego modelu:

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);
    });

Zwróć uwagę na WithOwner wywołanie użyte do zdefiniowania właściwości nawigacji wskazującej na właściciela. Aby zdefiniować nawigację do typu jednostki właściciela, który nie jest częścią relacji WithOwner() własności, powinien być wywoływany bez żadnych argumentów.

Istnieje również możliwość osiągnięcia tego wyniku przy użyciu OwnedAttribute metod i OrderDetailsStreetAddress.

Ponadto zwróć uwagę na wywołanie Navigation . Właściwości nawigacji do typów należących można dodatkowo skonfigurować jako właściwości nawigacji nienależących do użytkownika.

Powyższy model jest mapowany na następujący schemat bazy danych:

Screenshot of the database model for entity containing nested owned references

Przechowywanie należących typów w oddzielnych tabelach

W przeciwieństwie do typów złożonych EF6, typy należące do firmy mogą być przechowywane w oddzielnej tabeli od właściciela. Aby zastąpić konwencję mapowania typu należącego do tej samej tabeli co właściciel, można po prostu wywołać ToTable metodę i podać inną nazwę tabeli. Poniższy przykład mapuje OrderDetails i jego dwa adresy na oddzielną tabelę od DetailedOrder:

modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od => { od.ToTable("OrderDetails"); });

Można również użyć TableAttribute elementu , aby to osiągnąć, ale należy pamiętać, że nie powiedzie się, jeśli istnieje wiele nawigacji do typu należącego, ponieważ w takim przypadku wiele typów jednostek zostanie zamapowanych na tę samą tabelę.

Wykonywanie zapytań dotyczących typów należących do

Podczas wykonywania zapytań względem właściciela domyślnie będą dołączane typy własnościowe. Nie jest konieczne użycie Include metody, nawet jeśli należące do nich typy są przechowywane w oddzielnej tabeli. Na podstawie opisanego wcześniej modelu następujące zapytanie pobierze Orderelement , OrderDetails a dwa należące do StreetAddresses bazy danych:

var order = context.DetailedOrders.First(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.OrderDetails.ShippingAddress.City}");

Ograniczenia

Niektóre z tych ograniczeń mają podstawowe znaczenie dla działania typów jednostek należących do użytkownika, ale niektóre z nich są ograniczeniami, które możemy usunąć w przyszłych wersjach:

Ograniczenia projektowe

  • Nie można utworzyć DbSet<T> elementu dla typu należącego do użytkownika.
  • Nie można wywołać Entity<T>() metody z typem należącym do elementu ModelBuilder.
  • Wystąpienia typów jednostek będących własnością nie mogą być współużytkowane przez wielu właścicieli (jest to dobrze znany scenariusz dla obiektów wartości, których nie można zaimplementować przy użyciu należących typów jednostek).

Bieżące braki

  • Należące typy jednostek nie mogą mieć hierarchii dziedziczenia

Braki w poprzednich wersjach

  • W programie EF Core 2.x nawigacje referencyjne do należących typów jednostek nie mogą mieć wartości null, chyba że są jawnie mapowane na oddzielną tabelę od właściciela.
  • W programie EF Core 3.x kolumny dla należących typów jednostek są mapowane na tę samą tabelę, co właściciel, są zawsze oznaczone jako dopuszczane do wartości null.