Tipi di entità di proprietàOwned Entity Types

EF Core consente di modellare i tipi di entità che possono essere visualizzati solo nelle proprietà di navigazione di altri tipi di entità.EF Core allows you to model entity types that can only ever appear on navigation properties of other entity types. Questi sono denominati tipi di entità di proprietà.These are called owned entity types. L'entità che contiene un tipo di entità di proprietà è il proprietario.The entity containing an owned entity type is its owner.

Le entità di proprietà sono essenzialmente parte del proprietario e non possono esistere senza di essa, sono concettualmente simili a quelle delle aggregazioni.Owned entities are essentially a part of the owner and cannot exist without it, they are conceptually similar to aggregates. Questo significa che il tipo di proprietà è per definizione sul lato dipendente della relazione con il proprietario.This means that the owned type is by definition on the dependent side of the relationship with the owner.

Configurazione esplicitaExplicit configuration

I tipi di entità di proprietà non vengono mai inclusi da EF Core nel modello per convenzione.Owned entity types are never included by EF Core in the model by convention. È possibile usare il metodo OwnsOne in OnModelCreating o annotare il tipo con OwnedAttribute (nuovo in EF Core 2,1) per configurare il tipo come tipo di proprietà.You can use the OwnsOne method in OnModelCreating or annotate the type with OwnedAttribute (new in EF Core 2.1) to configure the type as an owned type.

In questo esempio StreetAddress è un tipo senza proprietà Identity.In this example, StreetAddress is a type with no identity property. Viene usato come proprietà del tipo Order per specificare l'indirizzo di spedizione per uno specifico ordine.It is used as a property of the Order type to specify the shipping address for a particular order.

È possibile utilizzare il OwnedAttribute per considerarlo come un'entità di proprietà quando viene fatto riferimento da un altro tipo di entità:We can use the OwnedAttribute to treat it as an owned entity when referenced from another entity type:

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

È anche possibile usare il metodo OwnsOne in OnModelCreating per specificare che la proprietà ShippingAddress è un'entità di proprietà del tipo di entità Order e per configurare i facet aggiuntivi, se necessario.It is also possible to use the OwnsOne method in OnModelCreating to specify that the ShippingAddress property is an Owned Entity of the Order entity type and to configure additional facets if needed.

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

Se la proprietà ShippingAddress è privata nel tipo di Order, è possibile usare la versione in formato stringa del metodo OwnsOne:If the ShippingAddress property is private in the Order type, you can use the string version of the OwnsOne method:

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

Per ulteriori informazioni sul contesto, vedere il progetto di esempio completo .See the full sample project for more context.

Chiavi impliciteImplicit keys

I tipi di proprietà configurati con OwnsOne o individuati tramite un'esplorazione dei riferimenti hanno sempre una relazione uno-a-uno con il proprietario, pertanto non necessitano di valori di chiave personalizzati perché i valori di chiave esterna sono univoci.Owned types configured with OwnsOne or discovered through a reference navigation always have a one-to-one relationship with the owner, therefore they don't need their own key values as the foreign key values are unique. Nell'esempio precedente, non è necessario che il tipo di StreetAddress definisca una proprietà chiave.In the previous example, the StreetAddress type does not need to define a key property.

Per comprendere il modo in cui EF Core tiene traccia di questi oggetti, è utile sapere che una chiave primaria viene creata come proprietà shadow per il tipo di proprietà.In order to understand how EF Core tracks these objects, it is useful to know that a primary key is created as a shadow property for the owned type. Il valore della chiave di un'istanza del tipo di proprietà sarà uguale al valore della chiave dell'istanza del proprietario.The value of the key of an instance of the owned type will be the same as the value of the key of the owner instance.

Raccolte di tipi di proprietàCollections of owned types

Nota

Questa funzionalità è stata introdotta in EF Core 2.2.This feature is new in EF Core 2.2.

Per configurare una raccolta di tipi di proprietà, utilizzare OwnsMany in OnModelCreating.To configure a collection of owned types use OwnsMany in OnModelCreating.

I tipi di proprietà necessitano di una chiave primaria.Owned types need a primary key. Se non sono presenti proprietà valide dei candidati sul tipo .NET, EF Core possibile provare a crearne una.If there are no good candidates properties on the .NET type, EF Core can try to create one. Tuttavia, quando i tipi di proprietà vengono definiti tramite una raccolta, non è sufficiente creare una proprietà shadow da utilizzare come chiave esterna nel proprietario e la chiave primaria dell'istanza di proprietà, come per OwnsOne: possono essere presenti più istanze di tipo di proprietà per ogni proprietario e, di conseguenza, la chiave del proprietario non è sufficiente per fornire un'identità univoca per ogni istanza di proprietà.However, when owned types are defined through a collection, it isn't enough to just create a shadow property to act as both the foreign key into the owner and the primary key of the owned instance, as we do for OwnsOne: there can be multiple owned type instances for each owner, and hence the key of the owner isn't enough to provide a unique identity for each owned instance.

Le due soluzioni più semplici sono:The two most straightforward solutions to this are:

  • Definizione di una chiave primaria surrogata in una nuova proprietà indipendente dalla chiave esterna che punta al proprietario.Defining a surrogate primary key on a new property independent of the foreign key that points to the owner. I valori contenuti devono essere univoci in tutti i proprietari, ad esempio se la {1} padre ha {1}figlio, quindi il {2} padre non può avere {1}figlio, quindi il valore non ha alcun significato intrinseco.The contained values would need to be unique across all owners (e.g. if Parent {1} has Child {1}, then Parent {2} cannot have Child {1}), so the value doesn't have any inherent meaning. Poiché la chiave esterna non fa parte della chiave primaria, i relativi valori possono essere modificati, quindi è possibile spostare un elemento figlio da un elemento padre a un altro, ma in genere viene eseguita la semantica di aggregazione.Since the foreign key is not part of the primary key its values can be changed, so you could move a child from one parent to another one, however this usually goes against aggregate semantics.
  • Uso della chiave esterna e di una proprietà aggiuntiva come chiave composta.Using the foreign key and an additional property as a composite key. Il valore aggiuntivo della proprietà deve ora essere univoco solo per un determinato elemento padre (pertanto, se {1} padre ha {1,1} figlio, il {2} padre può ancora avere {2,1}figlio).The additional property value now only needs to be unique for a given parent (so if Parent {1} has Child {1,1} then Parent {2} can still have Child {2,1}). Rendendo la parte chiave esterna della chiave primaria, la relazione tra il proprietario e l'entità di proprietà diventa non modificabile e riflette meglio la semantica di aggregazione.By making the foreign key part of the primary key the relationship between the owner and the owned entity becomes immutable and reflects aggregate semantics better. Per impostazione predefinita, EF Core esegue questa operazione.This is what EF Core does by default.

In questo esempio verrà usata la classe Distributor:In this example we'll use the Distributor class:

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

Per impostazione predefinita, la chiave primaria utilizzata per il tipo di proprietà a cui si fa riferimento tramite la proprietà di navigazione ShippingCenters sarà ("DistributorId", "Id") dove "DistributorId" è l'FK e "Id" è un valore di int univoco.By default the primary key used for the owned type referenced through the ShippingCenters navigation property will be ("DistributorId", "Id") where "DistributorId" is the FK and "Id" is a unique int value.

Per configurare un'altra chiamata a PK HasKey:To configure a different PK call HasKey:

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

Nota

Prima di EF Core 3,0 metodo WithOwner() non esisteva, quindi la chiamata deve essere rimossa.Before EF Core 3.0 WithOwner() method didn't exist so this call should be removed. Inoltre, la chiave primaria non è stata individuata automaticamente, quindi è sempre stata specificata.Also the primary key was not discovered automatically so it always had be specified.

Mapping dei tipi di proprietà con suddivisione della tabellaMapping owned types with table splitting

Quando si usano database relazionali, per impostazione predefinita i tipi di proprietà di riferimento vengono mappati alla stessa tabella del proprietario.When using relational databases, by default reference owned types are mapped to the same table as the owner. Questa operazione richiede la suddivisione della tabella in due: alcune colonne verranno utilizzate per archiviare i dati del proprietario e alcune colonne verranno utilizzate per archiviare i dati dell'entità di proprietà.This requires splitting the table in two: some columns will be used to store the data of the owner, and some columns will be used to store data of the owned entity. Si tratta di una funzionalità comune nota come suddivisione della tabella.This is a common feature known as table splitting.

Per impostazione predefinita, EF Core assegna un nome alle colonne del database per le proprietà del tipo di entità di proprietà che segue il modello Navigation_OwnedEntityProperty.By default, EF Core will name the database columns for the properties of the owned entity type following the pattern Navigation_OwnedEntityProperty. Le proprietà StreetAddress verranno pertanto visualizzate nella tabella ' Orders ' con i nomi ' ShippingAddress_Street ' è ShippingAddress_City '.Therefore the StreetAddress properties will appear in the 'Orders' table with the names 'ShippingAddress_Street' and 'ShippingAddress_City'.

È possibile usare il metodo HasColumnName per rinominare le colonne:You can use the HasColumnName method to rename those columns:

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

Nota

La maggior parte dei normali metodi di configurazione del tipo di entità come Ignore può essere chiamata nello stesso modo.Most of the normal entity type configuration methods like Ignore can be called in the same way.

Condivisione dello stesso tipo .NET tra più tipi di proprietàSharing the same .NET type among multiple owned types

Un tipo di entità di proprietà può essere dello stesso tipo .NET di un altro tipo di entità di proprietà, pertanto il tipo .NET potrebbe non essere sufficiente per identificare un tipo di proprietà.An owned entity type can be of the same .NET type as another owned entity type, therefore the .NET type may not be enough to identify an owned type.

In questi casi, la proprietà che punta dal proprietario all'entità di proprietà diventa la navigazione che definisce il tipo di entità di proprietà.In those cases, the property pointing from the owner to the owned entity becomes the defining navigation of the owned entity type. Dal punto di vista del EF Core, la navigazione di definizione fa parte dell'identità del tipo insieme al tipo .NET.From the perspective of EF Core, the defining navigation is part of the type's identity alongside the .NET type.

Ad esempio, nella classe seguente ShippingAddress e BillingAddress sono entrambi dello stesso tipo .NET, StreetAddress:For example, in the following class ShippingAddress and BillingAddress are both of the same .NET type, StreetAddress:

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

Per comprendere in che modo EF Core distinguere le istanze rilevate di questi oggetti, può essere utile pensare che la navigazione di definizione sia diventata parte della chiave dell'istanza insieme al valore della chiave del proprietario e al tipo .NET del tipo di proprietà.In order to understand how EF Core will distinguish tracked instances of these objects, it may be useful to think that the defining navigation has become part of the key of the instance alongside the value of the key of the owner and the .NET type of the owned type.

Tipi di proprietà annidatiNested owned types

In questo esempio OrderDetails possiede BillingAddress e ShippingAddress, che sono entrambi StreetAddress tipi.In this example OrderDetails owns BillingAddress and ShippingAddress, which are both StreetAddress types. Quindi OrderDetails è di proprietà del tipo DetailedOrder.Then OrderDetails is owned by the DetailedOrder type.

public class DetailedOrder
{
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }
    public OrderStatus Status { get; set; }
}
public enum OrderStatus
{
    Pending,
    Shipped
}

Ogni navigazione a un tipo di proprietà definisce un tipo di entità separato con configurazione completamente indipendente.Each navigation to an owned type defines a separate entity type with completely independent configuration.

Oltre ai tipi di proprietà annidati, un tipo di proprietà può fare riferimento a un'entità regolare, può essere il proprietario o un'altra entità purché l'entità di proprietà si trovi sul lato dipendente.In addition to nested owned types, an owned type can reference a regular entity, it can be either the owner or a different entity as long as the owned entity is on the dependent side. Questa funzionalità imposta i tipi di entità di proprietà oltre ai tipi complessi in EF6.This capability sets owned entity types apart from complex types in EF6.

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

È possibile concatenare il metodo OwnsOne in una chiamata Fluent per configurare questo modello:It is possible to chain the OwnsOne method in a fluent call to configure this model:

modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od =>
{
    od.WithOwner(d => d.Order);
    od.OwnsOne(c => c.BillingAddress);
    od.OwnsOne(c => c.ShippingAddress);
});

Si noti la chiamata WithOwner utilizzata per configurare la proprietà di navigazione che punta al proprietario.Notice the WithOwner call used to configure the navigation property pointing back at the owner. Per configurare una navigazione al tipo di entità Owner che non fa parte della relazione di proprietà WithOwner() necessario chiamare senza argomenti.To configure a navigation to the owner entity type that's not part of the ownership relationship WithOwner() should be called without any arguments.

È possibile ottenere il risultato utilizzando OwnedAttribute sia OrderDetails che StreetAddress.It is possible to achieve the result using OwnedAttribute on both OrderDetails and StreetAddress.

Archiviazione di tipi di proprietà in tabelle separateStoring owned types in separate tables

A differenza dei tipi complessi EF6, inoltre, i tipi di proprietà possono essere archiviati in una tabella separata dal proprietario.Also unlike EF6 complex types, owned types can be stored in a separate table from the owner. Per eseguire l'override della convenzione che esegue il mapping di un tipo di proprietà alla stessa tabella del proprietario, è possibile chiamare semplicemente ToTable e fornire un nome di tabella diverso.In order to override the convention that maps an owned type to the same table as the owner, you can simply call ToTable and provide a different table name. Nell'esempio seguente viene mappato OrderDetails e i relativi due indirizzi a una tabella separata da DetailedOrder:The following example will map OrderDetails and its two addresses to a separate table from DetailedOrder:

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

Per eseguire questa operazione, è anche possibile usare la TableAttribute, ma si noti che questa operazione avrà esito negativo se sono presenti più spostamenti al tipo di proprietà, poiché in questo caso più tipi di entità verrebbero mappati alla stessa tabella.It is also possible to use the TableAttribute to accomplish this, but note that this would fail if there are multiple navigations to the owned type since in that case multiple entity types would be mapped to the same table.

Esecuzione di query sui tipi di proprietàQuerying owned types

Quando si esegue una query sul proprietario, i tipi di proprietà saranno inclusi per impostazione predefinita.When querying the owner the owned types will be included by default. Non è necessario utilizzare il metodo Include, anche se i tipi di proprietà vengono archiviati in una tabella separata.It is not necessary to use the Include method, even if the owned types are stored in a separate table. In base al modello descritto in precedenza, la query seguente otterrà Order, OrderDetails e le due StreetAddresses di proprietà del database:Based on the model described before, the following query will get Order, OrderDetails and the two owned StreetAddresses from the database:

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

LimitazioniLimitations

Alcune di queste limitazioni sono fondamentali per il funzionamento dei tipi di entità di proprietà, ma altre sono restrizioni che potrebbero essere rimosse nelle versioni future:Some of these limitations are fundamental to how owned entity types work, but some others are restrictions that we may be able to remove in future releases:

Limitazioni di progettazioneBy-design restrictions

  • Non è possibile creare un DbSet<T> per un tipo di proprietàYou cannot create a DbSet<T> for an owned type
  • Non è possibile chiamare Entity<T>() con un tipo di proprietà in ModelBuilderYou cannot call Entity<T>() with an owned type on ModelBuilder

Carenze correntiCurrent shortcomings

  • I tipi di entità di proprietà non possono avere gerarchie di ereditarietàOwned entity types cannot have inheritance hierarchies
  • Le esplorazioni dei riferimenti ai tipi di entità di proprietà non possono essere null a meno che non ne venga eseguito il mapping esplicito a una tabella separata dal proprietarioReference navigations to owned entity types cannot be null unless they are explicitly mapped to a separate table from the owner
  • Le istanze dei tipi di entità di proprietà non possono essere condivise da più proprietari (si tratta di uno scenario noto per gli oggetti valore che non possono essere implementati con i tipi di entità di proprietà)Instances of owned entity types cannot be shared by multiple owners (this is a well-known scenario for value objects that cannot be implemented using owned entity types)

Difetti nelle versioni precedentiShortcomings in previous versions

  • In EF Core 2,0, le navigazioni nei tipi di entità di proprietà non possono essere dichiarate in tipi di entità derivate, a meno che non sia stato eseguito il mapping esplicito delle entità di proprietà a una tabella separata dalla gerarchiaIn EF Core 2.0, navigations to owned entity types cannot be declared in derived entity types unless the owned entities are explicitly mapped to a separate table from the owner hierarchy. Questa limitazione è stata rimossa in EF Core 2,1This limitation has been removed in EF Core 2.1
  • In EF Core 2,0 e 2,1 sono supportati solo gli spostamenti di riferimento ai tipi di proprietà.In EF Core 2.0 and 2.1 only reference navigations to owned types were supported. Questa limitazione è stata rimossa in EF Core 2,2This limitation has been removed in EF Core 2.2