c# - tutorial - entity framework ventajas y desventajas
Relación opcional de uno a uno utilizando Entity Framework Fluent API (6)
EF Code First admite relaciones 1:1
y 1:0..1
. Este último es lo que está buscando ("uno a cero o uno").
Sus intentos de hablar con fluidez dicen que es necesario en ambos extremos en un caso y opcional en ambos en el otro.
Lo que necesita es opcional en un extremo y requerido en el otro.
Aquí hay un ejemplo del primer libro del Código de programación de EF
modelBuilder.Entity<PersonPhoto>()
.HasRequired(p => p.PhotoOf)
.WithOptional(p => p.Photo);
La entidad PersonPhoto
tiene una propiedad de navegación llamada PhotoOf
que apunta a un tipo de Person
. El tipo Person
tiene una propiedad de navegación llamada Photo
que apunta al tipo PersonPhoto
.
En las dos clases relacionadas, utiliza la clave principal de cada tipo, no las claves externas . es decir, no usará las propiedades LoyaltyUserDetailId
o PIIUserId
. En cambio, la relación depende de los campos Id
de ambos tipos.
Si está utilizando la API con fluidez como se LoyaltyUser.Id
anteriormente, no necesita especificar LoyaltyUser.Id
como clave foránea, EF lo resolverá.
Así que sin tener tu código para probarme (odio hacer esto de mi cabeza) ... traduciría esto en tu código como
public class PIIUser
{
public int Id { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public PIIUser PIIUser { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(lu => lu.PIIUser )
.WithOptional(pi => pi.LoyaltyUserDetail );
}
Es decir, se requiere la propiedad PIIUser de LoyaltyUserDetail
y la propiedad LoyaltyUserDetail de LoyaltyUserDetail
es opcional.
Puedes comenzar desde el otro extremo:
modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);
que ahora dice que la propiedad LoyaltyUserDetail de LoyaltyUserDetail
es opcional y se requiere la propiedad PIIUser de PIIUser
.
Siempre debe usar el patrón HAS / WITH.
HTH y FWIW, las relaciones uno a uno (o uno a cero / uno) son una de las relaciones más confusas para configurar primero en el código, ¡por lo que no estás solo! :)
Queremos utilizar una relación opcional de uno a uno utilizando Entity Framework Code First. Tenemos dos entidades
public class PIIUser
{
public int Id { get; set; }
public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
PIIUser
puede tener un LoyaltyUserDetail
pero LoyaltyUserDetail
debe tener un PIIUser
. Probamos estas técnicas de acercamiento fluido.
modelBuilder.Entity<PIIUser>()
.HasOptional(t => t.LoyaltyUserDetail)
.WithOptionalPrincipal(t => t.PIIUser)
.WillCascadeOnDelete(true);
Este enfoque no creó la clave externa PIIUsers
en la tabla PIIUsers
.
Después de eso probamos el siguiente código.
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(t => t.PIIUser)
.WithRequiredDependent(t => t.LoyaltyUserDetail);
Pero esta vez EF no creó ninguna clave foránea en estas 2 tablas.
¿Tienes alguna idea para este problema? ¿Cómo podemos crear una relación opcional de uno a uno utilizando la API de Entity con fluidez?
Hay varias cosas mal con su código.
Una relación 1: 1 es: PK <-PK , donde un lado PK también es un FK, o PK <-FK + UC , donde el lado FK no es PK y tiene un UC. Su código muestra que tiene FK <-FK , ya que define que ambos lados tienen un FK, pero eso está mal. PIIUser
es el lado PK y LoyaltyUserDetail
es el lado FK. Esto significa que PIIUser
no tiene un campo FK, pero LoyaltyUserDetail
hace LoyaltyUserDetail
.
Si la relación 1: 1 es opcional, el lado FK debe tener al menos 1 campo anulable.
pswg de arriba contestó tu pregunta, pero cometió un error al definir también un FK en PIIUser, lo cual es, por supuesto, incorrecto como describí anteriormente. Así que defina el campo NULL FK en LoyaltyUserDetail
, defina el atributo en LoyaltyUserDetail
para marcarlo como el campo FK, pero no especifique un campo FK en PIIUser
.
Obtiene la excepción que describe arriba debajo de la publicación de pswg, porque ningún lado es el lado PK (extremo principal).
EF no es muy bueno en 1: 1 ya que no es capaz de manejar restricciones únicas. Primero no soy experto en Código, por lo que no sé si puede crear una UC o no.
(edit) btw: A 1: 1 B (FK) significa que hay solo 1 restricción de FK creada, en el objetivo de B apuntando al PK de A, no a 2.
Intente agregar el atributo ForeignKey
a la propiedad LoyaltyUserDetail
:
public class PIIUser
{
...
public int? LoyaltyUserDetailId { get; set; }
[ForeignKey("LoyaltyUserDetailId")]
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
...
}
Y la propiedad PIIUser
:
public class LoyaltyUserDetail
{
...
public int PIIUserId { get; set; }
[ForeignKey("PIIUserId")]
public PIIUser PIIUser { get; set; }
...
}
Lo único que confunde con las soluciones anteriores es que la clave principal se define como " Id" en ambas tablas y si tiene clave principal basada en el nombre de la tabla no funcionaría, he modificado las clases para ilustrar lo mismo, es decir, la tabla opcional no debe definir su propia clave primaria sino que debe usar el mismo nombre de clave de la tabla principal.
public class PIIUser
{
// For illustration purpose I have named the PK as PIIUserId instead of Id
// public int Id { get; set; }
public int PIIUserId { get; set; }
public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
// Note: You cannot define a new Primary key separately as it would create one to many relationship
// public int LoyaltyUserDetailId { get; set; }
// Instead you would reuse the PIIUserId from the primary table, and you can mark this as Primary Key as well as foreign key to PIIUser table
public int PIIUserId { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
Y luego seguido por
modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);
Haría el truco, la solución aceptada no puede explicar esto claramente, y me desanimó durante unas horas para encontrar la causa
Simplemente haga como si tuviera una relación de uno a muchos entre LoyaltyUserDetail
y PIIUser
por lo que su mapeo debería ser
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(m => m.PIIUser )
.WithMany()
.HasForeignKey(c => c.LoyaltyUserDetailId);
EF debe crear todas las claves foráneas que necesites y ¡simplemente no te importa WithMany !
public class User
{
public int Id { get; set; }
public int? LoyaltyUserId { get; set; }
public virtual LoyaltyUser LoyaltyUser { get; set; }
}
public class LoyaltyUser
{
public int Id { get; set; }
public virtual User MainUser { get; set; }
}
modelBuilder.Entity<User>()
.HasOptional(x => x.LoyaltyUser)
.WithOptionalDependent(c => c.MainUser)
.WillCascadeOnDelete(false);
esto resolverá el problema en REFERENCIA y LLAVES EXTRANJERAS
al ACTUALIZAR o BORRAR un registro