Buscar..


Introducción

Este tema explica cómo asignar relaciones de tipo uno a uno mediante Entity Framework.

Mapeo de uno a cero o uno

Así que digamos de nuevo que tienes el siguiente modelo:

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }
}

public class Car
{
  public int CarId { get; set; }
  public string LicensePlate { get; set; }
}

public class MyDemoContext : DbContext
{
  public DbSet<Person> People { get; set; }
  public DbSet<Car> Cars { get; set; }
}

Y ahora desea configurarlo para que pueda expresar la siguiente especificación: una persona puede tener uno o cero automóviles, y cada automóvil pertenece exactamente a una persona (las relaciones son bidireccionales, por lo tanto, si CarA pertenece a PersonA, entonces PersonA posee 'CarA).

Así que modifiquemos un poco el modelo: agregue las propiedades de navegación y las propiedades de clave externa:

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }
  public int CarId { get; set; }
  public virtual Car Car { get; set; }
}

public class Car
{
  public int CarId { get; set; }
  public string LicensePlate { get; set; }
  public int PersonId { get; set; }
  public virtual Person Person { get; set; }
}

Y la configuración:

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
     this.HasRequired(c => c.Person).WithOptional(p => p.Car);                        
  }
}    

En este momento esto debería ser autoexplicativo. El auto tiene una persona requerida ( HasRequired () ), y la persona tiene un auto opcional ( WithOptional () ). Nuevamente, no importa de qué lado configures esta relación, solo ten cuidado cuando uses la combinación correcta de Has / With y Required / Optional. Desde el lado de la Person , se vería así:

public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
  public PersonEntityTypeConfiguration()
  {
     this.HasOptional(p => p.Car).WithOptional(c => c.Person);                        
  }
}    

Ahora vamos a ver el esquema de db:

Mire detenidamente: puede ver que no hay FK en People para referirse a Car . Además, el FK en Car no es el PersonId , sino el CarId . Aquí está el script real para el FK:

ALTER TABLE [dbo].[Cars]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Cars_dbo.People_CarId] FOREIGN KEY([CarId])
REFERENCES [dbo].[People] ([PersonId])

Por lo tanto, esto significa que las propiedades clave de CarId y PersonId foregn que tenemos en el modelo se ignoran básicamente. Están en la base de datos, pero no son claves externas, como podría esperarse. Esto se debe a que las asignaciones uno a uno no admiten agregar el FK a su modelo EF. Y eso se debe a que las asignaciones uno a uno son bastante problemáticas en una base de datos relacional.

La idea es que cada persona puede tener exactamente un automóvil, y ese automóvil solo puede pertenecer a esa persona. O puede haber registros personales, que no tienen automóviles asociados con ellos.

Entonces, ¿cómo podría ser representado con claves externas? Obviamente, podría haber un PersonId en el Car y un CarId en las People . Para imponer que cada persona pueda tener solo un auto, PersonId tendría que ser único en Car . Pero si PersonId es único en People , ¿cómo puede agregar dos o más registros donde PersonId es NULL (más de un auto que no tiene dueño)? Respuesta: no puede (bueno, en realidad, puede crear un índice único filtrado en SQL Server 2008 y más reciente, pero olvidemos este tecnicismo por un momento; por no mencionar otros RDBMS). Sin mencionar el caso donde se especifican ambos extremos de la relación ...

La única forma real de hacer cumplir esta regla si las tablas de People y Car tienen la clave principal 'misma' (los mismos valores en los registros conectados). Y para hacer esto, CarId in Car debe ser tanto PK como FK para PK de People. Y esto hace que todo el esquema sea un desastre. Cuando uso esto prefiero nombrar el PK / FK en Car PersonId , y lo configuro en consecuencia:

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }        
  public virtual Car Car { get; set; }
}

public class Car
{        
  public string LicensePlate { get; set; }
  public int PersonId { get; set; }
  public virtual Person Person { get; set; }
}

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
     this.HasRequired(c => c.Person).WithOptional(p => p.Car);
     this.HasKey(c => c.PersonId);
  }
}

No ideal, pero tal vez un poco mejor. Aún así, debes estar alerta cuando utilices esta solución, porque va en contra de las convenciones de nombres habituales, lo que podría desviarte. Aquí está el esquema generado a partir de este modelo:

Entonces, esta relación no es impuesta por el esquema de la base de datos, sino por Entity Framework. Es por eso que debes tener mucho cuidado al usar esto, no permitir que nadie se enoje directamente con la base de datos.

Mapeo uno a uno

Mapear uno a uno (cuando se requieren ambos lados) también es algo complicado.

Imaginemos cómo podría representarse esto con claves foráneas. De nuevo, un CarId in People que se refiere a CarId in Car , y un PersonId in Car que se refiere al PersonId in People .

Ahora, ¿qué pasa si quieres insertar un registro de coche? Para que esto tenga éxito, debe haber un PersonId especificado en este registro de auto, porque es necesario. Y para que este PersonId sea ​​válido, debe existir el registro correspondiente en People . OK, entonces sigamos adelante e insertemos el registro de persona. Pero para que esto CarId éxito, un CarId válido debe estar en el registro de la persona, ¡pero el auto aún no está insertado! No puede ser, porque primero tenemos que insertar el registro de la persona referida. Pero no podemos insertar el registro de la persona referida, porque se refiere al registro del automóvil, por lo que debe insertarse primero (clave-clave extranjera :)).

Entonces, esto tampoco puede ser representado de la manera 'lógica'. De nuevo, tienes que soltar una de las claves foráneas. La que dejes caer depende de ti. El lado que queda con una clave externa se llama "dependiente", el lado que queda sin una clave externa se llama "principal". Y nuevamente, para garantizar la singularidad en el dependiente, el PK debe ser el FK, por lo que no se admite la adición de una columna FK ni la importación a su modelo.

Así que aquí está la configuración:

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
    this.HasRequired(c => c.Person).WithRequiredDependent(p => p.Car);
    this.HasKey(c => c.PersonId);
  }
}

En este momento, realmente debería haber obtenido la lógica de la misma :) Solo recuerde que también puede elegir el otro lado, solo tenga cuidado de usar las versiones Dependiente / Principal de WithRequired (y todavía tiene que configurar la PK en el automóvil).

public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
  public PersonEntityTypeConfiguration()
  {
    this.HasRequired(p => p.Car).WithRequiredPrincipal(c => c.Person);
  }
}

Si verifica el esquema de DB, encontrará que es exactamente igual que en el caso de la solución uno a uno o cero. Eso es porque, de nuevo, esto no se aplica por el esquema, sino por el propio EF. Así que de nuevo, ten cuidado :)

Mapeo de uno o cero a uno o cero

Y para terminar, veamos brevemente el caso cuando ambos lados son opcionales.

A estas alturas, debería estar realmente aburrido de estos ejemplos :), así que no voy a entrar en detalles y jugar con la idea de tener dos FK-s y los posibles problemas y le advierto sobre los peligros de no aplicar estas reglas en el esquema pero solo en EF mismo.

Aquí está la configuración que necesita aplicar:

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
    this.HasOptional(c => c.Person).WithOptionalPrincipal(p => p.Car);
    this.HasKey(c => c.PersonId);
  }
}

De nuevo, también puede configurar desde el otro lado, solo tenga cuidado de usar los métodos correctos :)



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow