Partager via


Relations 1 à N (un-à-plusieurs)

Les relations un-à-plusieurs sont utilisées lorsqu’une entité unique est associée à un nombre quelconque d’autres entités. Par exemple, un Blog peut avoir de nombreux associésPosts, mais chaque Post est associé à un seul Blog.

Ce document est structuré autour de nombreux exemples. Les exemples commencent par des cas courants, qui introduisent également des concepts. Les exemples ultérieurs couvrent des types de configuration moins courants. Une bonne approche ici consiste à comprendre les premiers exemples et concepts, puis à accéder aux exemples ultérieurs en fonction de vos besoins spécifiques. En fonction de cette approche, nous allons commencer par des relations simples « obligatoires » et « facultatives » un-à-plusieurs.

Conseil

Le code de tous les exemples ci-dessous est disponible dans OneToMany.cs.

Obligatoire d’un-à-plusieurs

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Une relation un-à-plusieurs est constituée des éléments suivants :

  • Une ou plusieurs propriétés clé primaire ou alternative sur l’entité principale ; c’est la fin « une » de la relation. Par exemple, Blog.Id
  • Une ou plusieurs propriétés clé étrangère sur l’entité dépendante ; c’est la fin « plusieurs » de la relation. Par exemple, Post.BlogId
  • Si vous le souhaitez, une navigation de collection sur l’entité principale référençant les entités dépendantes. Par exemple, Blog.Posts
  • Si vous le souhaitez, une navigation de référence sur l’entité dépendante référençant l’entité principale. Par exemple, Post.Blog

Par conséquent, pour la relation dans cet exemple :

  • La propriété de clé étrangère Post.BlogId n’est pas nullable. Cela rend la relation « obligatoire », car chaque dépendant (Post) doit être lié à un certain principal (Blog), car sa propriété de clé étrangère doit être définie sur une valeur.
  • Les deux entités ont des navigations pointant vers l’entité ou les entités associées de l’autre côté de la relation.

Remarque

Une relation requise garantit que chaque entité dépendante doit être associée à une entité principale. Toutefois, une entité principale peut toujours exister sans entités dépendantes. Autrement dit, une relation requise n'apas indiqué qu’il y aura toujours au moins une entité dépendante. Il n’existe aucun moyen dans le modèle EF, et pas non plus de façon standard dans une base de données relationnelle, pour s’assurer qu’un principal est associé à un certain nombre de dépendants. Si nécessaire, elle doit être implémentée dans la logique d’application (métier). Pour plus d’informations, consultez Navigations obligatoires.

Conseil

Une relation avec deux navigations, une de dépendante au principal et une inverse du principal aux dépendants, est connue sous le nom de relation bidirectionnelle.

Cette relation est découverte par la convention. Plus précisément :

  • Blog est découvert comme entité principale dans la relation et Post est découvert comme entité dépendante.
  • Post.BlogId est découvert en tant que clé étrangère de la clé dépendante référençant la clé primaire Blog.Id de l’entité principale. La relation est détectée comme nécessaire, car Post.BlogId n’est pas nullable.
  • Blog.Posts est découvert comme navigation dans la collection.
  • Post.Blog est découvert comme navigation de référence.

Important

Lorsque vous utilisez types de référence C# nullables, la navigation de référence doit être nullable si la propriété de clé étrangère est nullable. Si la propriété de clé étrangère n’est pas nullable, la navigation de référence peut être nullable ou non. Dans ce cas, Post.BlogId est non nullable et Post.Blog est également non nullable. La construction = null!; est utilisée pour marquer cela comme intentionnelle pour le compilateur C#, car EF définit généralement l’instance Blog et ne peut pas être null pour une relation entièrement chargée. Pour plus d’informations, consultez Utilisation des types de référence Nullable .

Dans les cas où les navigations, la clé étrangère ou la nature obligatoire/facultative de la relation ne sont pas découvertes par convention, ces éléments peuvent être configurés explicitement. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Dans l’exemple ci-dessus, la configuration des relations commence par HasMany sur le type d’entité principal (Blog), puis suit ceci avec WithOne. Comme pour toutes les relations, il est exactement équivalent à commencer par le type d’entité dépendant (Post) et à utiliser HasOne suivi de WithMany. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(e => e.Blog)
        .WithMany(e => e.Posts)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Aucune de ces options n’est meilleure que l’autre, elles entraînent toutes deux exactement la même configuration.

Conseil

Il n’est jamais nécessaire de configurer une relation deux fois, une fois à partir de l’entité principale, puis à nouveau à partir de l’entité dépendante. En outre, la tentative de configurer l’entité principale et les moitiés dépendantes d’une relation séparément ne fonctionnent généralement pas. Choisissez de configurer chaque relation d’une extrémité ou de l’autre, puis d’écrire le code de configuration une seule fois.

Facultatif un-à-plusieurs

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int? BlogId { get; set; } // Optional foreign key property
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

Il s’agit de la même chose que l’exemple précédent, sauf que la propriété de clé étrangère et la navigation vers le principal sont désormais nullables. Cela rend la relation « facultative », car un dépendant (Post) peut exister sans être lié à n’importe quel principal (Blog).

Important

Lorsque vous utilisez types de référence C# nullables, la navigation de référence doit être nullable si la propriété de clé étrangère est nullable. Dans ce cas, Post.BlogId est nullable. Par conséquent, Post.Blog doit également être nullable. Pour plus d’informations, consultez Utilisation des types de référence Nullable .

Comme précédemment, cette relation est découverte par convention. Dans les cas où les navigations, la clé étrangère ou la nature obligatoire/facultative de la relation ne sont pas découvertes par convention, ces éléments peuvent être configurés explicitement. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired(false);
}

Obligatoire d’une à plusieurs avec une clé étrangère d’ombre

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Dans certains cas, vous ne souhaiterez peut-être pas une propriété de clé étrangère dans votre modèle, car les clés étrangères sont un détail de la façon dont la relation est représentée dans la base de données, ce qui n’est pas nécessaire lors de l’utilisation de la relation de manière purement orientée objet. Toutefois, si les entités doivent être sérialisées, par exemple pour être envoyées sur un câble, les valeurs de clé étrangère peuvent être un moyen utile de conserver les informations de relation intactes lorsque les entités ne sont pas sous forme d’objets. Il est donc souvent pragmatique de conserver les propriétés de clé étrangère dans le type .NET à cet effet. Les propriétés de clé étrangère peuvent être privées, ce qui est souvent un bon compromis pour éviter d’exposer la clé étrangère tout en autorisant sa valeur à voyager avec l’entité.

À partir des deux exemples précédents, cet exemple supprime la propriété de clé étrangère du type d’entité dépendant. EF crée donc une propriété de clé étrangère d’ombre appelée BlogId de type int.

Un point important à noter ici est que types de référence nullables C# sont utilisés, de sorte que la possibilité null de la navigation de référence est utilisée pour déterminer si la propriété de clé étrangère est nullable ou non, et par conséquent, si la relation est facultative ou requise. Si les types de référence nullables ne sont pas utilisés, la propriété de clé étrangère d’ombre sera nullable par défaut, ce qui rend la relation facultative par défaut. Dans ce cas, utilisez IsRequired pour forcer la propriété de clé étrangère d’ombre à être non nullable et rendre la relation requise.

Comme précédemment, cette relation est découverte par la convention. Dans les cas où les navigations, la clé étrangère ou la nature obligatoire/facultative de la relation ne sont pas découvertes par convention, ces éléments peuvent être configurés explicitement. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("BlogId")
        .IsRequired();
}

Facultatif un-à-plusieurs avec une clé étrangère d’ombre

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

Comme dans l’exemple précédent, la propriété de clé étrangère a été supprimée du type d’entité dépendant. EF crée donc une propriété de clé étrangère d’ombre appelée BlogId de type int?. Contrairement à l’exemple précédent, cette fois,la propriété de clé étrangère est créée comme nullable, car types de référence nullables C# sont utilisés et la navigation sur le type d’entité dépendant est nullable. Cela rend la relation facultative.

Lorsque les types de référence nullables C# ne sont pas utilisés, la propriété de clé étrangère est également créée comme nullable par défaut. Cela signifie que les relations avec les propriétés d’ombre créées automatiquement sont facultatives par défaut.

Comme précédemment, cette relation est découverte par la convention. Dans les cas où les navigations, la clé étrangère ou la nature obligatoire/facultative de la relation ne sont pas découvertes par convention, ces éléments peuvent être configurés explicitement. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("BlogId")
        .IsRequired(false);
}

Un-à-plusieurs sans navigation vers le principal

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

Pour cet exemple, la propriété de clé étrangère a été ré-introduite, mais la navigation sur la dépendance a été supprimée.

Conseil

Une relation avec une seule navigation, une de dépendant au principal ou d’un principal à dépendant(s), mais pas les deux, est connue sous le nom de relation unidirectionnelle.

Comme précédemment, cette relation est découverte par la convention. Dans les cas où les navigations, la clé étrangère ou la nature obligatoire/facultative de la relation ne sont pas découvertes par convention, ces éléments peuvent être configurés explicitement. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Notez que l’appel à WithOne n’a aucun argument. C’est la façon de dire à EF qu’il n’y a pas de navigation de Post à Blog.

Si la configuration commence à partir de l’entité sans navigation, le type de l’entité à l’autre extrémité de la relation doit être spécifié explicitement à l’aide de l’appel générique HasOne<>(). Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne<Blog>()
        .WithMany(e => e.Posts)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Un-à-plusieurs sans navigation vers principal et avec une touche étrangère d’ombre

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
}

Cet exemple combine deux des exemples précédents en supprimant à la fois la propriété de clé étrangère et la navigation sur la dépendance.

Cette relation est découverte par la convention comme relation facultative. Étant donné qu’il n’existe rien dans le code qui peut être utilisé pour indiquer qu’il doit être nécessaire, une configuration minimale à l’aide de IsRequired est nécessaire pour créer une relation requise. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .IsRequired();
}

Une configuration plus complète peut être utilisée pour configurer explicitement le nom de la navigation et de la clé étrangère, avec un appel approprié à IsRequired() ou IsRequired(false) si nécessaire. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .HasForeignKey("BlogId")
        .IsRequired();
}

Un-à-plusieurs sans navigation vers les dépendants

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Les deux exemples précédents comportaient des navigations entre le principal et les dépendants, mais aucune navigation entre le principal dépendant et le principal. Pour les deux exemples suivants, la navigation sur la dépendance est réinitulation, tandis que la navigation sur le principal est supprimée à la place.

Comme précédemment, cette relation est découverte par la convention. Dans les cas où les navigations, la clé étrangère ou la nature obligatoire/facultative de la relation ne sont pas découvertes par convention, ces éléments peuvent être configurés explicitement. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(e => e.Blog)
        .WithMany()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Notez à nouveau que WithMany() est appelé sans arguments pour indiquer qu’il n’y a pas de navigation dans cette direction.

Si la configuration commence à partir de l’entité sans navigation, le type de l’entité à l’autre extrémité de la relation doit être spécifié explicitement à l’aide de l’appel générique HasMany<>(). Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Un-à-plusieurs sans navigation

Parfois, il peut être utile de configurer une relation sans navigation. Une telle relation ne peut être manipulée qu’en modifiant directement la valeur de clé étrangère.

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

Cette relation n’est pas découverte par convention, car il n’existe aucune navigation indiquant que les deux types sont liés. Elle peut être configurée explicitement dans OnModelCreating. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne();
}

Avec cette configuration, la propriété Post.BlogId est toujours détectée comme clé étrangère par convention et la relation est requise, car la propriété de clé étrangère n’est pas nullable. La relation peut être rendue « facultative » en rendant la propriété de clé étrangère nullable.

Une configuration explicite plus complète de cette relation est la suivante :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Un-à-plusieurs avec une autre clé

Dans tous les exemples jusqu’à présent, la propriété de clé étrangère sur la dépendance est limitée à la propriété de clé primaire sur le principal. La clé étrangère peut à la place être contrainte à une propriété différente, qui devient ensuite une autre clé pour le type d’entité principal. Par exemple :

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public int AlternateId { get; set; } // Alternate key as target of the Post.BlogId foreign key
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Cette relation n’est pas découverte par convention, car EF crée toujours, par convention, une relation avec la clé primaire. Elle peut être configurée explicitement dans OnModelCreating à l’aide d’un appel à HasPrincipalKey. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId);
}

HasPrincipalKey peut être combiné avec d’autres appels pour configurer explicitement les navigations, les propriétés de clé étrangère et la nature obligatoire/facultative. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Clé étrangère un-à-plusieurs avec clé étrangère composite

Dans tous les exemples jusqu’à présent, la propriété de clé primaire ou alternative du principal était constituée d’une propriété unique. Les clés primaire ou alternatives peuvent également être formées de plusieurs propriétés : elles sont appelées « clés composites ». Lorsque le principal d’une relation a une clé composite, la clé étrangère du dépendant doit également être une clé composite avec le même nombre de propriétés. Par exemple :

// Principal (parent)
public class Blog
{
    public int Id1 { get; set; } // Composite key part 1
    public int Id2 { get; set; } // Composite key part 2
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId1 { get; set; } // Required foreign key property part 1
    public int BlogId2 { get; set; } // Required foreign key property part 2
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Cette relation est découverte par convention. Toutefois, la clé composite elle-même doit être configurée explicitement ::

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(e => new { e.Id1, e.Id2 });
}

Important

Une valeur de clé étrangère composite est considérée comme null si l’une de ses valeurs de propriété est null. Une clé étrangère composite avec une propriété null et une autre valeur non null ne sera pas considérée comme une correspondance pour une clé primaire ou alternative avec les mêmes valeurs. Les deux seront considérées comme null.

HasForeignKey et HasPrincipalKey peuvent être utilisés pour spécifier explicitement des clés avec plusieurs propriétés. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        nestedBuilder =>
        {
            nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });

            nestedBuilder.HasMany(e => e.Posts)
                .WithOne(e => e.Blog)
                .HasPrincipalKey(e => new { e.Id1, e.Id2 })
                .HasForeignKey(e => new { e.BlogId1, e.BlogId2 })
                .IsRequired();
        });
}

Conseil

Dans le code ci-dessus, les appels à HasKey et HasMany ont été regroupés dans un générateur imbriqué. Les générateurs imbriqués suppriment la nécessité d’appeler Entity<>() plusieurs fois pour le même type d’entité, mais sont fonctionnellement équivalents à l’appel Entity<>() plusieurs fois.

Obligatoire d’un-à-plusieurs sans suppression en cascade

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Par convention, les relations requises sont configurées pour suppression en cascade; cela signifie que lorsque le principal est supprimé, tous ses dépendants sont également supprimés, car les dépendants ne peuvent pas exister dans la base de données sans principal. Il est possible de configurer EF pour lever une exception au lieu de supprimer automatiquement des lignes dépendantes qui ne peuvent plus exister :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .OnDelete(DeleteBehavior.Restrict);
}

Référencement automatique d’un-à-plusieurs

Dans tous les exemples précédents, le type d’entité principal était différent du type d’entité dépendant. Cela ne doit pas être le cas. Par exemple, dans les types ci-dessous, chaque Employee est liée à d’autres Employees.

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

    public int? ManagerId { get; set; } // Optional foreign key property
    public Employee? Manager { get; set; } // Optional reference navigation to principal
    public ICollection<Employee> Reports { get; } = new List<Employee>(); // Collection navigation containing dependents
}

Cette relation est découverte par la convention. Dans les cas où les navigations, la clé étrangère ou la nature obligatoire/facultative de la relation ne sont pas découvertes par convention, ces éléments peuvent être configurés explicitement. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Employee>()
        .HasOne(e => e.Manager)
        .WithMany(e => e.Reports)
        .HasForeignKey(e => e.ManagerId)
        .IsRequired(false);
}