¿Cómo debo declarar relaciones de clave externa usando Code First Entity Framework (4.1) en MVC3?

104

He estado buscando recursos sobre cómo declarar relaciones de clave externa y otras restricciones usando el código primero EF 4.1 sin mucha suerte. Básicamente, estoy construyendo el modelo de datos en código y usando MVC3 para consultar ese modelo. Todo funciona a través de MVC, lo cual es genial (¡felicitaciones a Microsoft!) Pero ahora no quiero que NO funcione porque necesito tener restricciones de modelo de datos.

Por ejemplo, tengo un objeto Order que tiene un montón de propiedades que son objetos externos (tablas). Ahora mismo puedo crear un Pedido sin problema, pero sin poder agregar la clave externa u objetos externos. MVC3 configura esto sin problema.

Me doy cuenta de que podría agregar los objetos yo mismo en la clase de controlador antes de guardar, pero me gustaría que la llamada a DbContext.SaveChanges () fallara si no se cumplen las relaciones de restricción.

NUEVA INFORMACIÓN

Entonces, específicamente, me gustaría que ocurriera una excepción cuando intento guardar un objeto Pedido sin especificar un objeto de cliente. Este no parece ser el comportamiento si solo compongo los objetos como se describe en la mayoría de la documentación de Code First EF.

Último código:

public class Order
{
    public int Id { get; set; }

    [ForeignKey( "Parent" )]
    public Patient Patient { get; set; }

    [ForeignKey("CertificationPeriod")]
    public CertificationPeriod CertificationPeriod { get; set; }

    [ForeignKey("Agency")]
    public Agency Agency { get; set; }

    [ForeignKey("Diagnosis")]
    public Diagnosis PrimaryDiagnosis { get; set; }

    [ForeignKey("OrderApprovalStatus")]
    public OrderApprovalStatus ApprovalStatus { get; set; }

    [ForeignKey("User")]
    public User User { get; set; }

    [ForeignKey("User")]
    public User Submitter { get; set; }

    public DateTime ApprovalDate { get; set; }
    public DateTime SubmittedDate { get; set; }
    public Boolean IsDeprecated { get; set; }
}

Este es el error que obtengo ahora al acceder a la vista generada por VS para el paciente:

MENSAJE DE ERROR

El ForeignKeyAttribute en la propiedad 'Paciente' en el tipo 'PhysicianPortal.Models.Order' no es válido. El nombre de clave externa 'Padre' no se encontró en el tipo dependiente 'PhysicianPortal.Models.Order'. El valor de Nombre debe ser una lista separada por comas de nombres de propiedad de clave externa.

Saludos,

Guido

Guido Anselmi
fuente

Respuestas:

164

Si tiene una Orderclase, agregar una propiedad que haga referencia a otra clase en su modelo, por ejemplo, Customerdebería ser suficiente para que EF sepa que hay una relación allí:

public class Order
{
    public int ID { get; set; }

    // Some other properties

    // Foreign key to customer
    public virtual Customer Customer { get; set; }
}

Siempre puede establecer la FKrelación explícitamente:

public class Order
{
    public int ID { get; set; }

    // Some other properties

    // Foreign key to customer
    [ForeignKey("Customer")]
    public string CustomerID { get; set; }
    public virtual Customer Customer { get; set; }
}

El ForeignKeyAttributeconstructor toma una cadena como parámetro: si la coloca en una propiedad de clave externa, representa el nombre de la propiedad de navegación asociada. Si lo coloca en la propiedad de navegación, representa el nombre de la clave externa asociada.

Lo que esto significa es que, si coloca el ForeignKeyAttributeen la Customerpropiedad, el atributo tomaría CustomerIDen el constructor:

public string CustomerID { get; set; }
[ForeignKey("CustomerID")]
public virtual Customer Customer { get; set; }

EDITAR basado en el último código Obtiene ese error debido a esta línea:

[ForeignKey("Parent")]
public Patient Patient { get; set; }

EF buscará una propiedad llamada Parentpara usarla como ejecutor de clave externa. Puedes hacer 2 cosas:

1) Quite el ForeignKeyAttributey reemplácelo con el RequiredAttributepara marcar la relación según sea necesario:

[Required]
public virtual Patient Patient { get; set; }

Decorar una propiedad con el RequiredAttributetambién tiene un efecto secundario agradable: la relación en la base de datos se crea con ON DELETE CASCADE.

También recomendaría hacer que la propiedad virtualhabilite la carga diferida.

2) Cree una propiedad llamada Parentque servirá como clave externa. En ese caso, probablemente tenga más sentido llamarlo, por ejemplo ParentID(también deberá cambiar el nombre en el ForeignKeyAttribute):

public int ParentID { get; set; }

En mi experiencia, en este caso, aunque funciona mejor hacerlo al revés:

[ForeignKey("Patient")]
public int ParentID { get; set; }

public virtual Patient Patient { get; set; }
Sergi Papaseit
fuente
Gracias Sergi: agregué información adicional en la cita del bloque.
Guido Anselmi
@Guido: he actualizado mi respuesta en función de su última edición de código, espero que esto ayude.
Sergi Papaseit
30

Puede definir una clave externa mediante:

public class Parent
{
   public int Id { get; set; }
   public virtual ICollection<Child> Childs { get; set; }
}

public class Child
{
   public int Id { get; set; }
   // This will be recognized as FK by NavigationPropertyNameForeignKeyDiscoveryConvention
   public int ParentId { get; set; } 
   public virtual Parent Parent { get; set; }
}

Ahora ParentId es una propiedad de clave externa y define la relación requerida entre el niño y el padre existente. Salvar al niño sin el padre existente arrojará una excepción.

Si el nombre de su propiedad FK no consta del nombre de la propiedad de navegación y el nombre PK principal, debe usar la anotación de datos ForeignKeyAttribute o una API fluida para mapear la relación

Anotación de datos:

// The name of related navigation property
[ForeignKey("Parent")]
public int ParentId { get; set; }

API fluida:

modelBuilder.Entity<Child>()
            .HasRequired(c => c.Parent)
            .WithMany(p => p.Childs)
            .HasForeignKey(c => c.ParentId);

Se pueden aplicar otros tipos de restricciones mediante anotaciones de datos y validación de modelos .

Editar:

Obtendrá una excepción si no la establece ParentId. Es una propiedad requerida (no anulable). Si simplemente no lo configura, lo más probable es que intente enviar el valor predeterminado a la base de datos. El valor predeterminado es 0, por lo que si no tiene un cliente con Id = 0, obtendrá una excepción.

Ladislav Mrnka
fuente
Gracias Ladislav: agregué información adicional en la cita del bloque.
Guido Anselmi
@Ladislav. Entonces, para hacer cumplir esta restricción, DEBO tener tanto la referencia a Parent como una referencia a ParentId. ¿Es eso correcto? Agregaré la clase real anterior como referencia.
Guido Anselmi
@Guido: Esa es la nueva información. No está utilizando propiedades de clave externa. Todas sus propiedades de navegación se manejan como opcionales por defecto. Utilice un mapeo fluido para mapearlos según sea necesario.
Ladislav Mrnka
@Ladislav: Gracias de nuevo. Estoy mirando a mi alrededor para comprender las diferencias entre el uso de anotaciones de datos y la API Fluent. Hice los cambios en el código anterior de acuerdo con lo que creo que estás diciendo. ¿Lo anterior es todo lo que tengo que hacer? Saludos.
Guido Anselmi
Ningún atributo ForeignKey define la propiedad de navegación relacionada con la propiedad de la clave externa o viceversa. No tiene propiedades de clave externa, por lo que no puede usar ese atributo. Intente usar el atributo Requerido en sus propiedades de navegación (no lo probé).
Ladislav Mrnka