Tipi di entità di proprietà
EF Core consente di modellare i tipi di entità che possono essere visualizzati solo nelle proprietà di navigazione di altri tipi di entità. Questi tipi sono denominati tipi di entità di proprietà. L'entità contenente un tipo di entità di proprietà è il proprietario.
Le entità di proprietà sono essenzialmente una parte del proprietario e non possono esistere senza di essa, sono concettualmente simili alle aggregazioni. Ciò significa che l'entità di proprietà è per definizione sul lato dipendente della relazione con il proprietario.
Nella maggior parte dei provider i tipi di entità non vengono mai configurati come di proprietà della convenzione. È necessario usare in modo esplicito il OwnsOne
metodo in OnModelCreating
o annotare il tipo con OwnedAttribute
per configurare il tipo come di proprietà. Il provider Azure Cosmos DB è un'eccezione. Poiché Azure Cosmos DB è un database di documenti, il provider configura tutti i tipi di entità correlati come di proprietà per impostazione predefinita.
In questo esempio, StreetAddress
è un tipo senza proprietà Identity. Viene usato come proprietà del tipo Order per specificare l'indirizzo di spedizione per uno specifico ordine.
È possibile usare OwnedAttribute
per considerarlo come entità di proprietà quando viene fatto riferimento da un altro tipo di entità:
[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 OwnsOne
metodo in OnModelCreating
per specificare che la ShippingAddress
proprietà è un'entità di proprietà del Order
tipo di entità e per configurare facet aggiuntivi, se necessario.
modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);
Se la ShippingAddress
proprietà è privata nel Order
tipo, è possibile usare la versione stringa del OwnsOne
metodo :
modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");
Il modello precedente è mappato allo schema di database seguente:
Per altre informazioni di contesto, vedere il progetto di esempio completo.
Suggerimento
Il tipo di entità di proprietà può essere contrassegnato come richiesto. Per altre informazioni, vedere Dipendenti uno-a-uno necessari.
I tipi di proprietà configurati con OwnsOne
o individuati tramite una navigazione di riferimento hanno sempre una relazione uno-a-uno con il proprietario, pertanto non hanno bisogno dei propri valori di chiave perché i valori di chiave esterna sono univoci. Nell'esempio precedente, il StreetAddress
tipo non deve definire una proprietà chiave.
Per comprendere come EF Core tiene traccia di questi oggetti, è utile sapere che una chiave primaria viene creata come proprietà shadow per il tipo di proprietà. Il valore della chiave di un'istanza del tipo di proprietà sarà uguale al valore della chiave dell'istanza del proprietario.
Per configurare una raccolta di tipi di proprietà da usare OwnsMany
in OnModelCreating
.
I tipi di proprietà necessitano di una chiave primaria. Se non sono presenti proprietà idonee per il tipo .NET, EF Core può provare a crearne uno. Tuttavia, quando i tipi di proprietà vengono definiti tramite una raccolta, non è sufficiente creare solo una proprietà shadow per agire sia come chiave esterna nel proprietario che nella chiave primaria dell'istanza di proprietà, come OwnsOne
per : possono essere presenti più istanze di tipo di proprietà per ogni proprietario e quindi la chiave del proprietario non è sufficiente per fornire un'identità univoca per ogni istanza di proprietà.
Le due soluzioni più semplici sono:
- Definizione di una chiave primaria surrogata in una nuova proprietà indipendente dalla chiave esterna che punta al proprietario. I valori contenuti devono essere univoci in tutti i proprietari ,ad esempio se Padre {1} ha Figlio {1}, l'elemento Padre {2} non può avere Figlio {1}, quindi il valore non ha alcun significato intrinseco. Poiché la chiave esterna non fa parte della chiave primaria, è possibile modificarne i valori, quindi è possibile spostare un elemento figlio da un elemento padre a un altro, ma questo avviene in genere rispetto alla semantica di aggregazione.
- Uso della chiave esterna e di una proprietà aggiuntiva come chiave composita. Il valore della proprietà aggiuntivo deve ora essere univoco solo per un determinato elemento padre( quindi se Padre {1} ha Figlio {1,1} , l'elemento Padre {2} può comunque avere Figlio {2,1}). Rendendo la chiave esterna parte della chiave primaria, la relazione tra il proprietario e l'entità di proprietà diventa non modificabile e riflette meglio la semantica di aggregazione. Questo è ciò che EF Core esegue per impostazione predefinita.
In questo esempio si userà la Distributor
classe .
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
Per impostazione predefinita, la chiave primaria usata per il tipo di proprietà a cui si fa riferimento tramite la ShippingCenters
proprietà di navigazione sarà ("DistributorId", "Id")
dove "DistributorId"
è FK ed "Id"
è un valore univoco int
.
Per configurare una diversa chiamata HasKey
di chiave primaria.
modelBuilder.Entity<Distributor>().OwnsMany(
p => p.ShippingCenters, a =>
{
a.WithOwner().HasForeignKey("OwnerId");
a.Property<int>("Id");
a.HasKey("Id");
});
Il modello precedente è mappato allo schema di database seguente:
Quando si usano database relazionali, per impostazione predefinita viene eseguito il mapping dei tipi di proprietà di riferimento alla stessa tabella del proprietario. Questa operazione richiede la suddivisione della tabella in due: alcune colonne verranno usate per archiviare i dati del proprietario e alcune colonne verranno usate per archiviare i dati dell'entità di proprietà. Si tratta di una funzionalità comune nota come suddivisione delle tabelle.
Per impostazione predefinita, EF Core denomina le colonne di database per le proprietà del tipo di entità di proprietà seguendo il modello Navigation_OwnedEntityProperty. Pertanto, le StreetAddress
proprietà verranno visualizzate nella tabella 'Orders' con i nomi 'ShippingAddress_Street' e 'ShippingAddress_City'.
È possibile usare il HasColumnName
metodo per rinominare tali colonne.
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 dei tipi di entità, ad esempio Ignore , può essere chiamata nello stesso modo.
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à.
In questi casi, la proprietà che punta dal proprietario all'entità di proprietà diventa la struttura di navigazione del tipo di entità di proprietà. Dal punto di vista di EF Core, lo spostamento che definisce fa parte dell'identità del tipo insieme al tipo .NET.
Ad esempio, nella classe ShippingAddress
seguente e BillingAddress
sono entrambi dello stesso tipo .NET, 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 distinguerà le istanze rilevate di questi oggetti, può essere utile pensare che la navigazione che definisce sia diventata parte della chiave dell'istanza insieme al valore della chiave del proprietario e del tipo .NET del tipo di proprietà.
In questo esempio OrderDetails
è proprietario di BillingAddress
e ShippingAddress
, che sono entrambi StreetAddress
tipi. Quindi OrderDetails
è di proprietà del tipo DetailedOrder
.
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 in un tipo di proprietà definisce un tipo di entità separato con una configurazione completamente indipendente.
Oltre ai tipi di proprietà annidati, un tipo di proprietà può fare riferimento a un'entità regolare che può essere il proprietario o un'entità diversa purché l'entità di proprietà si trova sul lato dipendente. Questa funzionalità imposta tipi di entità di proprietà diversi dai tipi complessi in EF6.
public class OrderDetails
{
public DetailedOrder Order { get; set; }
public StreetAddress BillingAddress { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
È possibile concatenare il OwnsOne
metodo in una chiamata Fluent per configurare questo modello:
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);
});
Si noti la WithOwner
chiamata usata per definire la proprietà di navigazione che punta di nuovo al proprietario. Per definire una navigazione al tipo di entità proprietario che non fa parte della relazione WithOwner()
di proprietà, è necessario chiamare senza argomenti.
È anche possibile ottenere questo risultato usando OwnedAttribute
sia OrderDetails
su che StreetAddress
su .
Si noti anche la Navigation
chiamata. Le proprietà di navigazione ai tipi di proprietà possono essere ulteriormente configurate come per le proprietà di navigazione non di proprietà.
Il modello precedente è mappato allo schema di database seguente:
A differenza dei tipi complessi EF6, i tipi di proprietà possono essere archiviati in una tabella separata dal proprietario. Per eseguire l'override della convenzione che esegue il mapping di un tipo di proprietà alla stessa tabella del proprietario, è sufficiente chiamare ToTable
e specificare un nome di tabella diverso. Nell'esempio seguente viene eseguito il mapping OrderDetails
e i relativi due indirizzi a una tabella separata da DetailedOrder
:
modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od => { od.ToTable("OrderDetails"); });
È anche possibile usare per TableAttribute
eseguire questa operazione, ma si noti che l'errore potrebbe verificarsi se sono presenti più spostamenti al tipo di proprietà, poiché in tal caso più tipi di entità verrebbero mappati alla stessa tabella.
Quando si esegue una query sul proprietario, i tipi di proprietà saranno inclusi per impostazione predefinita. Non è necessario utilizzare il Include
metodo , anche se i tipi di proprietà vengono archiviati in una tabella separata. In base al modello descritto in precedenza, la query seguente otterrà Order
OrderDetails
e i due di proprietà StreetAddresses
del database:
var order = context.DetailedOrders.First(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.OrderDetails.ShippingAddress.City}");
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:
- Non è possibile creare un oggetto
DbSet<T>
per un tipo di proprietà. - Non è possibile chiamare
Entity<T>()
con un tipo di proprietà inModelBuilder
. - 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 usando tipi di entità di proprietà).
- I tipi di entità di proprietà non possono avere gerarchie di ereditarietà
- Negli spostamenti di riferimento di EF Core 2.x ai tipi di entità di proprietà non possono essere Null a meno che non vengano mappati in modo esplicito a una tabella separata dal proprietario.
- In EF Core 3.x le colonne per i tipi di entità di proprietà mappate alla stessa tabella del proprietario vengono sempre contrassegnate come nullable.
Feedback su .NET
.NET è un progetto di open source. Selezionare un collegamento per fornire feedback: