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:
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 OwnsOne
przypadku : 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 HasKey
klucza 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:
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 OrderDetails
StreetAddress
.
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:
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 Order
element , 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 elementuModelBuilder
. - 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.