Comment dois-je déclarer les relations de clé étrangère à l'aide de Code First Entity Framework (4.1) dans MVC3?

104

J'ai cherché des ressources sur la façon de déclarer les relations de clé étrangère et d'autres contraintes en utilisant d'abord le code EF 4.1 sans beaucoup de chance. Fondamentalement, je construis le modèle de données dans le code et j'utilise MVC3 pour interroger ce modèle. Tout fonctionne via MVC, ce qui est génial (bravo à Microsoft!) Mais maintenant je ne veux PAS que cela fonctionne car j'ai besoin de contraintes de modèle de données.

Par exemple, j'ai un objet Order qui a une tonne de propriétés qui sont des objets externes (tables). Pour le moment, je peux créer une commande sans problème, mais sans pouvoir ajouter la clé étrangère ou des objets externes. MVC3 met cela en place sans problème.

Je me rends compte que je pourrais simplement ajouter les objets moi-même dans la classe de contrôleur avant d'enregistrer, mais je voudrais que l'appel à DbContext.SaveChanges () échoue si les relations de contrainte n'ont pas été respectées.

NOUVELLE INFORMATION

Donc, plus précisément, j'aimerais qu'une exception se produise lorsque je tente de sauvegarder un objet Order sans spécifier d'objet client. Cela ne semble pas être le comportement si je compose simplement les objets comme décrit dans la plupart de la documentation Code First EF.

Dernier code:

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; }
}

C'est l'erreur que j'obtiens maintenant lors de l'accès à la vue générée par VS pour le patient:

MESSAGE D'ERREUR

Le ForeignKeyAttribute sur la propriété 'Patient' sur le type 'PhysicianPortal.Models.Order' n'est pas valide. Le nom de clé étrangère «Parent» est introuvable sur le type dépendant «PhysicianPortal.Models.Order». La valeur Name doit être une liste de noms de propriété de clé étrangère séparés par des virgules.

Cordialement,

Guido

Guido Anselmi
la source

Réponses:

164

Si vous avez une Orderclasse, l'ajout d'une propriété qui fait référence à une autre classe dans votre modèle, par exemple, Customerdevrait être suffisant pour indiquer à EF qu'il y a une relation là-dedans:

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

    // Some other properties

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

Vous pouvez toujours définir la FKrelation explicitement:

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; }
}

Le ForeignKeyAttributeconstructeur prend une chaîne comme paramètre: si vous la placez sur une propriété de clé étrangère, il représente le nom de la propriété de navigation associée. Si vous le placez sur la propriété de navigation, il représente le nom de la clé étrangère associée.

Cela signifie que si vous placez le ForeignKeyAttributesur la Customerpropriété, l'attribut prendrait CustomerIDle constructeur:

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

EDIT basé sur le dernier code Vous obtenez cette erreur à cause de cette ligne:

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

EF recherchera une propriété appelée Parentpour l'utiliser comme exécuteur de clé étrangère. Vous pouvez faire 2 choses:

1) Retirez le ForeignKeyAttributeet remplacez-le par RequiredAttributepour marquer la relation comme requis:

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

Décorer une propriété avec le a RequiredAttributeégalement un effet secondaire intéressant: la relation dans la base de données est créée avec ON DELETE CASCADE.

Je recommanderais également de créer la propriété virtualpour activer le chargement différé.

2) Créez une propriété appelée Parentqui servira de clé étrangère. Dans ce cas, il est probablement plus logique de l'appeler par exemple ParentID(vous devrez également changer le nom dans le ForeignKeyAttribute):

public int ParentID { get; set; }

D'après mon expérience dans ce cas, il est préférable de l'avoir dans l'autre sens:

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

public virtual Patient Patient { get; set; }
Sergi Papaseit
la source
Merci Sergi - J'ai ajouté des informations supplémentaires dans la citation de bloc.
Guido Anselmi le
@Guido - J'ai mis à jour ma réponse en fonction de votre dernière modification de code, j'espère que cela vous aidera.
Sergi Papaseit le
30

Vous pouvez définir une clé étrangère par:

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; }
}

Maintenant ParentId est la propriété de clé étrangère et définit la relation requise entre l'enfant et le parent existant. Enregistrer l'enfant sans quitter le parent lèvera une exception.

Si le nom de votre propriété FK ne comprend pas le nom de la propriété de navigation et le nom PK parent, vous devez utiliser l'annotation de données ForeignKeyAttribute ou l'API fluide pour mapper la relation

Annotation des données:

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

API Fluent:

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

D'autres types de contraintes peuvent être appliqués par des annotations de données et la validation du modèle .

Éditer:

Vous obtiendrez une exception si vous ne définissez pas ParentId. Il s'agit d'une propriété obligatoire (non nullable). Si vous ne le définissez pas, il essaiera probablement d'envoyer la valeur par défaut à la base de données. La valeur par défaut est 0, donc si vous n'avez pas de client avec Id = 0, vous obtiendrez une exception.

Ladislav Mrnka
la source
Merci Ladislav - J'ai ajouté des informations supplémentaires dans le devis de bloc.
Guido Anselmi le
@Ladislav. Donc, pour appliquer cette contrainte, JE DOIS avoir à la fois la référence à Parent et une référence à ParentId. Est-ce exact? J'ajouterai la classe réelle ci-dessus pour référence.
Guido Anselmi le
@Guido: Ce sont les nouvelles informations. Vous n'utilisez pas de propriétés de clé étrangère. Toutes vos propriétés de navigation sont gérées comme facultatives par défaut. Utilisez une cartographie fluide pour les mapper au besoin.
Ladislav Mrnka
@Ladislav: Merci encore. Je cherche autour de moi pour comprendre les différences entre l'utilisation des annotations de données et l'API Fluent. J'ai apporté les modifications au code ci-dessus conformément à ce que je pense que vous dites. Est-ce que ce que j'ai à faire ci-dessus? Cordialement.
Guido Anselmi le
Aucun attribut ForeignKey ne définit la propriété de navigation liée à la propriété de clé étrangère ou vice versa. Vous n'avez pas de propriétés de clé étrangère, vous ne pouvez donc pas utiliser cet attribut. Essayez d'utiliser l'attribut requis sur vos propriétés de navigation (je ne l'ai pas testé).
Ladislav Mrnka le