Tipos de entidad en propiedad
EF Core permite modelar tipos de entidad que solo pueden aparecer en las propiedades de navegación de otros tipos de entidad. Se denominan tipos de entidad de propiedad. La entidad que contiene un tipo de entidad de propiedad es su propietario.
Las entidades de propiedad son esencialmente una parte del propietario y no pueden existir sin él, son conceptualmente similares a los agregados. Esto significa que la entidad de propiedad está por definición en el lado dependiente de la relación con el propietario.
Configuración de tipos como propiedad
En la mayoría de los proveedores, los tipos de entidad nunca se configuran como propiedad de la convención: debe usar explícitamente el método en o anotar el tipo con para configurar el tipo como OwnsOneOnModelCreatingOwnedAttribute propiedad. El proveedor de azure Cosmos DB es una excepción a esto. Dado Cosmos db es una base de datos de documentos, el proveedor configura todos los tipos de entidad relacionados como propiedad de forma predeterminada.
En este ejemplo, StreetAddress es un tipo sin propiedad de identidad. Se usa como propiedad del tipo Order para especificar la dirección de envío de un pedido en concreto.
Se puede usar para tratarlo como una entidad de propiedad cuando se OwnedAttribute hace referencia desde otro tipo de entidad:
[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; }
}
También es posible usar el método en para especificar que la propiedad es una entidad de propiedad del tipo de entidad y para configurar facetas adicionales OwnsOneOnModelCreating si es ShippingAddressOrder necesario.
modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);
Si la ShippingAddress propiedad es privada en el tipo , puede usar la versión de cadena del método OrderOwnsOne :
modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");
El modelo anterior se asigna al siguiente esquema de base de datos:

Consulte el proyecto de ejemplo completo para obtener más contexto.
Sugerencia
El tipo de entidad de propiedad se puede marcar como obligatorio; consulte Dependencias uno a uno necesarias para obtener más información.
Claves implícitas
Los tipos de propiedad configurados con o detectados a través de una navegación de referencia siempre tienen una relación uno a uno con el propietario, por lo que no necesitan sus propios valores de clave, ya que los valores de clave externa son OwnsOne únicos. En el ejemplo anterior, el StreetAddress tipo no necesita definir una propiedad de clave.
Para entender cómo EF Core realiza un seguimiento de estos objetos, es útil saber que se crea una clave principal como una propiedad de sombra para el tipo de propiedad. El valor de la clave de una instancia del tipo de propiedad será el mismo que el valor de la clave de la instancia de propietario.
Colecciones de tipos de propiedad
Para configurar una colección de tipos de propiedad, use OwnsMany en OnModelCreating .
Los tipos de propiedad necesitan una clave principal. Si no hay propiedades de candidatos buenas en el tipo de .NET, EF Core intentar crear una. Sin embargo, cuando los tipos de propiedad se definen a través de una colección, no basta con crear una propiedad de sombra para que actúe como la clave externa en el propietario y la clave principal de la instancia de propiedad, como en el caso de : puede haber varias instancias de tipo de propiedad para cada propietario y, por tanto, la clave del propietario no es suficiente para proporcionar una identidad única para cada instancia de OwnsOne propiedad.
Las dos soluciones más sencillas a esto son:
- Definir una clave principal suplente en una nueva propiedad independiente de la clave externa que apunta al propietario. Los valores contenidos tendrían que ser únicos en todos los propietarios (por ejemplo, si Parent tiene Child , parent no puede tener Child), por lo que el valor no tiene {1}{1} ningún significado {2}{1} inherente. Dado que la clave externa no forma parte de la clave principal, sus valores se pueden cambiar, por lo que podría mover un elemento secundario de un elemento primario a otro, pero esto suele ir en contra de la semántica de agregado.
- Usar la clave externa y una propiedad adicional como clave compuesta. El valor de propiedad adicional ahora solo debe ser único para un elemento primario determinado (por lo que si parent tiene {1} elemento {1,1} secundario, parent {2} puede tener el elemento {2,1} secundario). Al convertir la clave externa en parte de la clave principal, la relación entre el propietario y la entidad de propiedad se vuelve inmutable y refleja mejor la semántica de agregado. Esto es lo EF Core de forma predeterminada.
En este ejemplo, usaremos la Distributor clase .
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
De forma predeterminada, la clave principal usada para el tipo de propiedad al que se hace referencia a través de la propiedad de navegación será donde es la clave externa y ShippingCenters("DistributorId", "Id") es un valor "DistributorId""Id"int único.
Para configurar una llamada de clave principal diferente, llame a HasKey .
modelBuilder.Entity<Distributor>().OwnsMany(
p => p.ShippingCenters, a =>
{
a.WithOwner().HasForeignKey("OwnerId");
a.Property<int>("Id");
a.HasKey("Id");
});
El modelo anterior se asigna al siguiente esquema de base de datos:

Asignación de tipos de propiedad con división de tablas
Cuando se usan bases de datos relacionales, los tipos de propiedad de referencia predeterminados se asignan a la misma tabla que el propietario. Esto requiere dividir la tabla en dos: algunas columnas se usarán para almacenar los datos del propietario y algunas columnas se usarán para almacenar los datos de la entidad en propiedad. Se trata de una característica común conocida como división de tablas.
De forma predeterminada, EF Core las columnas de base de datos para las propiedades del tipo de entidad de propiedad siguiendo el patrón Navigation_OwnedEntityProperty. Por lo StreetAddress tanto, las propiedades aparecerán en la tabla "Orders" con los nombres "ShippingAddress_Street" y "ShippingAddress_City".
Puede usar el método HasColumnName para cambiar el nombre de esas columnas.
modelBuilder.Entity<Order>().OwnsOne(
o => o.ShippingAddress,
sa =>
{
sa.Property(p => p.Street).HasColumnName("ShipsToStreet");
sa.Property(p => p.City).HasColumnName("ShipsToCity");
});
Nota
La mayoría de los métodos de configuración de tipo de entidad normales, como Ignore, se pueden llamar de la misma manera.
Uso compartido del mismo tipo de .NET entre varios tipos de propiedad
Un tipo de entidad de propiedad puede ser del mismo tipo de .NET que otro tipo de entidad de propiedad, por lo que es posible que el tipo de .NET no sea suficiente para identificar un tipo de propiedad.
En esos casos, la propiedad que apunta desde el propietario a la entidad de propiedad se convierte en la navegación de definición del tipo de entidad propiedad. Desde la perspectiva de EF Core, la navegación de definición forma parte de la identidad del tipo junto con el tipo .NET.
Por ejemplo, en la siguiente clase ShippingAddress y son del mismo tipo de BillingAddress .NET, StreetAddress .
public class OrderDetails
{
public DetailedOrder Order { get; set; }
public StreetAddress BillingAddress { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
Para comprender cómo EF Core distinguirá las instancias con seguimiento de estos objetos, puede ser útil pensar que la navegación de definición ha pasado a formar parte de la clave de la instancia junto con el valor de la clave del propietario y el tipo .NET del tipo de propiedad.
Tipos de propiedad anidados
En este ejemplo OrderDetails posee y , que son ambos BillingAddressShippingAddressStreetAddress tipos. Luego, OrderDetails es propiedad 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
}
Cada navegación a un tipo de propiedad define un tipo de entidad independiente con una configuración completamente independiente.
Además de los tipos de propiedad anidados, un tipo de propiedad puede hacer referencia a una entidad normal que puede ser el propietario o una entidad diferente, siempre que la entidad en propiedad esté en el lado dependiente. Esta funcionalidad establece los tipos de entidad de propiedad aparte de los tipos complejos en EF6.
public class OrderDetails
{
public DetailedOrder Order { get; set; }
public StreetAddress BillingAddress { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
Configuración de tipos de propiedad
Es posible encadenar el OwnsOne método en una llamada fluida para configurar este modelo:
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);
});
Observe la WithOwner llamada que se usa para definir la propiedad de navegación que apunta al propietario. Para definir una navegación al tipo de entidad de propietario que no forma parte de la relación de propiedad, se debe llamar WithOwner() a sin argumentos.
También es posible lograr este resultado mediante OwnedAttribute en OrderDetails y StreetAddress .
Además, observe la Navigation llamada. En EFCore 5.0, las propiedades de navegación a los tipos de propiedad se pueden configurar aún más como para las propiedades de navegación que no son propiedadde .
El modelo anterior se asigna al siguiente esquema de base de datos:

Almacenamiento de tipos de propiedad en tablas independientes
A diferencia de los tipos complejos de EF6, los tipos de propiedad se pueden almacenar en una tabla independiente del propietario. Para invalidar la convención que asigna un tipo de propiedad a la misma tabla que el propietario, simplemente puede llamar a y ToTable proporcionar un nombre de tabla diferente. En el ejemplo siguiente se OrderDetails asignarán y sus dos direcciones a una tabla independiente de DetailedOrder :
modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od => { od.ToTable("OrderDetails"); });
También es posible usar para lograr esto, pero tenga en cuenta que esto produciría un error si hay varias navegaciones al tipo de propiedad, ya que en ese caso se asignarían varios tipos de entidad a la misma TableAttribute tabla.
Consulta de tipos de propiedad
Al consultar al propietario, los tipos de propiedad se incluyen de forma predeterminada. No es necesario usar el método , incluso si los tipos de Include propiedad se almacenan en una tabla independiente. En función del modelo descrito anteriormente, la consulta siguiente obtiene y los Order dos que pertenecen a la base de OrderDetailsStreetAddresses datos:
var order = context.DetailedOrders.First(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.OrderDetails.ShippingAddress.City}");
Limitaciones
Algunas de estas limitaciones son fundamentales para el modo en que funcionan los tipos de entidad de propiedad, pero otras son restricciones que es posible que podamos quitar en futuras versiones:
Restricciones por diseño
- No se puede crear
DbSet<T>para un tipo de propiedad. - No se puede llamar
Entity<T>()a con un tipo de propiedad enModelBuilder. - Varios propietarios no pueden compartir instancias de tipos de entidad de propiedad (se trata de un escenario conocido para objetos de valor que no se pueden implementar mediante tipos de entidad de propiedad).
Deficiencias actuales
- Los tipos de entidad de propiedad no pueden tener jerarquías de herencia
Deficiencias en versiones anteriores
- En EF Core navegación de referencia 2.x a tipos de entidad de propiedad no pueden ser NULL a menos que se asignen explícitamente a una tabla independiente del propietario.
- En EF Core 3.x, las columnas para los tipos de entidad de propiedad asignados a la misma tabla que el propietario siempre se marcan como que aceptan valores NULL.