Relations, propriétés de navigation et clés étrangères

Cet article donne une vue d’ensemble de la façon dont Entity Framework gère les relations entre les entités. Il fournit également des conseils sur la façon de mapper et de manipuler des relations.

Relations dans EF

Dans les bases de données relationnelles, les relations (également appelées « associations ») entre les tables sont définies via des clés étrangères. Une clé étrangère est une colonne ou une combinaison de colonnes utilisée pour établir et imposer une liaison entre les données de deux tables. Il existe généralement trois types de relations : un-à-un, un-à-plusieurs et plusieurs-à-plusieurs. Dans une relation un-à-plusieurs, la clé étrangère est définie sur la table qui représente la fin « plusieurs » de la relation. La relation plusieurs-à-plusieurs implique la définition d’une troisième table (appelée table de jonction ou de jointure), dont la clé primaire est composée des clés étrangères des deux tables associées. Dans une relation un-à-un, la clé primaire agit également en tant que clé étrangère et il n’existe pas de colonne de clé étrangère distincte pour l’une ou l’autre table.

L’image suivante montre deux tables qui participent à une relation un-à-plusieurs. La table Course (Cours) est la table dépendante, car elle contient la colonne DepartmentID (ID département) qui la lie à la table Department (Département).

Department and Course tables

Dans Entity Framework, une entité peut être associée à d’autres entités via une association ou relation. Chaque relation comporte deux terminaisons qui décrivent le type d’entité et la multiplicité du type (un, zéro ou un, ou plusieurs) pour les deux entités dans cette relation. La relation peut être régie par une contrainte référentielle, qui décrit quelle terminaison de la relation est un rôle principal et celle qui est un rôle dépendant.

Les propriétés de navigation permettent de parcourir une association entre deux types d’entité. Chaque objet peut avoir une propriété de navigation pour chaque relation à laquelle il participe. Les propriétés de navigation permettent de parcourir et de gérer les relations dans les deux directions, en retournant un objet de référence (si la multiplicité est un ou zéro-à-un) ou une collection (si la multiplicité est plusieurs). Vous pouvez aussi choisir d’avoir une navigation unidirectionnelle, auquel cas vous définissez la propriété de navigation sur un seul des types qui participe à la relation et non pas sur les deux.

Il est recommandé d’inclure des propriétés dans le modèle qui sont mappés aux clés étrangères dans la base de données. Lorsque les propriétés de clé étrangère sont incluses, vous pouvez créer ou modifier une relation en changeant la valeur de clé étrangère d'un objet dépendant. Ce type d'association s'appelle une association de clé étrangère. L’utilisation de clés étrangères est encore plus essentielle lors de l’utilisation d’entités déconnectées. Notez que lors de l’utilisation de relations 1-à-1 ou 1-à-0..1, il n’existe pas de colonne de clé étrangère distincte, la propriété de clé primaire agit comme clé étrangère et elle est toujours incluse dans le modèle.

Quand des colonnes clés étrangères ne sont pas incluses dans le modèle, les informations d’association sont gérées en tant qu’objet indépendant. Les relations sont suivies via des références d’objets au lieu de propriétés de clé étrangère. Ce type d’association est appelé association indépendante. La façon la plus courante de modifier une association indépendante est de modifier les propriétés de navigation générées pour chaque entité qui participe à l’association.

Vous pouvez choisir d'utiliser un type d'association dans votre modèle ou les deux à la fois. Cependant, si vous avez une relation plusieurs-à-plusieurs pure connectée par une table de jointure qui contient seulement des clés étrangères, EF va utiliser une association indépendante pour gérer une telle relation plusieurs-à-plusieurs.   

L’image suivante montre un modèle conceptuel qui a été créé avec Entity Framework Designer. Le modèle contient deux entités qui participent à une relation un-à-plusieurs. Les deux entités ont des propriétés de navigation. Cours est l’entité dépendante et a la propriété de clé étrangère departmentID (ID département) définie.

Department and Course tables with navigation properties

L’extrait de code suivant montre le même modèle créé avec Code First.

public class Course
{
  public int CourseID { get; set; }
  public string Title { get; set; }
  public int Credits { get; set; }
  public int DepartmentID { get; set; }
  public virtual Department Department { get; set; }
}

public class Department
{
   public Department()
   {
     this.Courses = new HashSet<Course>();
   }  
   public int DepartmentID { get; set; }
   public string Name { get; set; }
   public decimal Budget { get; set; }
   public DateTime StartDate { get; set; }
   public int? Administrator {get ; set; }
   public virtual ICollection<Course> Courses { get; set; }
}

Configuration ou mappage des relations

Le reste de cette page explique comment accéder aux données et comment les manipuler en utilisant les relations. Pour plus d’informations sur la configuration des relations dans votre modèle, consultez les pages suivantes.

Création et modification de relations

Dans une association de clé étrangère, quand vous modifiez la relation, l’état d’un objet dépendant avec un état EntityState.Unchanged passe à l’état EntityState.Modified. Dans une relation indépendante, la modification de la relation ne met pas à jour l’état de l’objet dépendant.

Les exemples suivants montrent comment utiliser les propriétés de clé étrangère et la propriété de navigation pour associer les objets connexes. Avec des associations de clé étrangère, vous pouvez utiliser l’une ou l’autre des méthodes pour créer ou pour modifier des relations. Avec les associations indépendantes, vous ne pouvez pas utiliser la propriété de clé étrangère.

  • En affectant une nouvelle valeur à une propriété de clé étrangère, comme dans l’exemple suivant.

    course.DepartmentID = newCourse.DepartmentID;
    
  • Le code suivant supprime une relation en définissant la clé étrangère sur null. Notez que la propriété de clé étrangère doit être nullable.

    course.DepartmentID = null;
    

    Remarque

    Si la référence est dans l’état ajouté (dans cet exemple, l’objet « course ») la propriété de navigation de référence n’est pas synchronisée avec les valeurs de clé d’un nouvel objet tant que SaveChanges n’est pas appelé. La synchronisation n'a pas lieu, car le contexte de l'objet ne contient pas de clés permanentes pour les objets ajoutés tant qu'ils ne sont pas enregistrés. Si les nouveaux objets doivent être entièrement synchronisés dès que vous définissez la relation, utilisez une des méthodes suivantes.*

  • En affectant un nouvel objet à une propriété de navigation. Le code suivant crée une relation entre un cours et un department. Si les objets sont attachés au contexte, le course est ajouté à la collection department.Courses, et la propriété de clé étrangère correspondante de l’objet course est définie sur la valeur de propriété de clé du département.

    course.Department = department;
    
  • Pour supprimer la relation, définissez la propriété de navigation sur null. Si vous utilisez Entity Framework basé sur .NET 4.0, la terminaison associée doit être chargée avant de pouvoir être définie sur null. Par exemple :

    context.Entry(course).Reference(c => c.Department).Load();
    course.Department = null;
    

    À compter d’Entity Framework 5.0, qui est basé sur .NET 4.5, vous pouvez définir la relation sur null sans charger la terminaison associée. Vous pouvez aussi définir la valeur actuelle sur null en utilisant la méthode suivante.

    context.Entry(course).Reference(c => c.Department).CurrentValue = null;
    
  • En supprimant ou en ajoutant un objet dans une collection d'entités. Par exemple, vous pouvez ajouter un objet de type Course à la collection department.Courses. Cette opération crée une relation entre un cours particulier et un department particulier. Si les objets sont attachés au contexte de l’objet, la référence de département et la propriété de clé étrangère sur l’objet course (cours) seront définies sur le department approprié.

    department.Courses.Add(newCourse);
    
  • En utilisant la méthode ChangeRelationshipState pour changer l’état de la relation spécifiée entre deux objets d’entité. Cette méthode est couramment utilisée avec des applications multiniveau et avec une association indépendante (elle ne peut pas être utilisée avec une association de clé étrangère). En outre, pour utiliser cette méthode, vous devez descendre au niveau de ObjectContext, comme illustré dans l’exemple ci-dessous.
    Dans l’exemple suivant, il existe une relation plusieurs-à-plusieurs entre Instructors (Instructeurs) et Courses (Cours). L’appel de la méthode ChangeRelationshipState et le passage du paramètre EntityState.Added permet au SchoolContext de savoir qu’une relation a été ajoutée entre les deux objets :

    
    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, instructor, c => c.Instructor, EntityState.Added);
    

    Notez que si vous mettez à jour (et pas seulement si vous ajoutez) une relation, vous devez supprimer l’ancienne relation après l’ajout de la nouvelle :

    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, oldInstructor, c => c.Instructor, EntityState.Deleted);
    

Synchronisation des modifications entre les clés étrangères et les propriétés de navigation

Quand vous modifiez la relation des objets attachés au contexte en utilisant une des méthodes décrites ci-dessus, Entity Framework doit faire en sorte que les clés étrangères, les références et les collections restent synchronisées. Entity Framework gère automatiquement cette synchronisation (également appelée correctif de relation) pour les entités POCO avec des proxys. Pour plus d’informations, consultez Utilisation des proxys.

Si vous utilisez des entités POCO sans proxys, vous devez veiller à ce que la méthode DetectChanges soit appelée pour synchroniser les objets connexes dans le contexte. Notez que les API suivantes déclenchent automatiquement un appel de DetectChanges.

  • DbSet.Add
  • DbSet.AddRange
  • DbSet.Remove
  • DbSet.RemoveRange
  • DbSet.Find
  • DbSet.Local
  • DbContext.SaveChanges
  • DbSet.Attach
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries
  • Exécution d’une requête LINQ sur un DbSet

Dans Entity Framework, vous utilisez généralement des propriétés de navigation pour charger des entités liées à l’entité retournée par l’association définie. Pour plus d’informations, consultez Chargement d’objets connexes.

Remarque

Dans une association de clé étrangère, lorsque vous chargez une terminaison connexe d'un objet dépendant, l'objet connexe est chargé en fonction de la valeur de clé étrangère du dépendant actuellement en mémoire :

    // Get the course where currently DepartmentID = 2.
    Course course = context.Courses.First(c => c.DepartmentID == 2);

    // Use DepartmentID foreign key property
    // to change the association.
    course.DepartmentID = 3;

    // Load the related Department where DepartmentID = 3
    context.Entry(course).Reference(c => c.Department).Load();

Dans une association indépendante, la terminaison connexe d'un objet dépendant est interrogée en fonction de la valeur de clé étrangère actuellement présente dans la base de données. Cependant, si la relation a été modifiée et que la propriété de référence de l’objet dépendant pointe vers un objet principal différent de celui qui est chargé dans le contexte de l’objet, Entity Framework essaie de créer une relation telle qu’elle est définie sur le client.

Gérer l'accès concurrentiel

Dans les associations de clé étrangère comme dans les associations indépendantes, les contrôles d’accès concurrentiel sont basés sur les clés d’entité et d’autres propriétés d’entité qui sont définies dans le modèle. Quand vous utilisez EF Designer pour créer un modèle, définissez l’attribut ConcurrencyMode sur fixe pour spécifier que l’accès concurrentiel doit être vérifié pour la propriété. Quand vous utilisez Code First pour définir un modèle, utilisez l’annotation ConcurrencyCheck sur les propriétés pour lesquelles l’accès concurrentiel doit être vérifié. Quand vous utilisez Code First, vous pouvez aussi utiliser l’annotation TimeStamp pour spécifier que l’accès concurrentiel doit être vérifié pour la propriété. Vous ne pouvez avoir qu’une seule propriété timestamp dans une classe donnée. Code First mappe cette propriété à un champ non nullable dans la base de données.

Nous vous recommandons de toujours utiliser l’association de clé étrangère lors de l’utilisation d’entités qui participent à la vérification et à la résolution de l’accès concurrentiel.

Pour plus d’informations, consultez Gestion des conflits d’accès concurrentiel.

Utilisation de clés qui se chevauchent

Des clés qui se chevauchent sont des clés composites qui ont certaines propriétés en commun dans l'entité qu'elles constituent. Une association indépendante ne peut pas contenir de clés qui se chevauchent. Pour modifier une association de clé étrangère qui comporte des clés qui se chevauchent, nous vous conseillons de modifier les valeurs de clé étrangère plutôt que d'utiliser les références d'objet.