Clés étrangères et principales dans les relations

Toutes les relations un-à-un et un-à-plusieurs sont définies par une clé étrangère du côté dépendant qui fait référence à une clé primaire ou alternative du côté principal. Pour des raisons pratiques, cette clé primaire ou alternative est appelée « clé principale » pour la relation. Les relations plusieurs-à-plusieurs sont composées de deux relations un-à-plusieurs, chacune d’elles étant définie par une clé étrangère référençant une clé principale.

Conseil

Le code ci-dessous se trouve dans ForeignAndPrincipalKeys.cs.

Clés étrangères

La propriété ou les propriétés qui composent une clé étrangère sont souvent découvertes par convention. Les propriétés peuvent également être configurées explicitement à l’aide d’attributs de mappage ou avec HasForeignKey dans l’API de génération de modèles. HasForeignKey peut être utilisé avec une expression lambda. Par exemple, pour une clé étrangère composée d’une propriété unique :

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

Ou, pour une clé étrangère composite composée de plusieurs propriétés :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => new { e.ContainingBlogId1, e.ContainingBlogId2 });
}

Conseil

L’utilisation d’expressions lambda dans l’API de génération de modèles garantit que l’utilisation de la propriété est disponible pour l’analyse du code et la refactorisation. Cela fournit également le type de propriété à l’API pour une utilisation dans d’autres méthodes chaînées.

HasForeignKey peut également recevoir le nom de la propriété de clé étrangère en tant que chaîne. Par exemple, pour une propriété unique :

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

Ou, pour une clé étrangère composite :

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

L’utilisation d’une chaîne est utile lorsque :

  • La propriété ou les propriétés sont privées.
  • La propriété ou les propriétés n’existent pas sur le type d’entité et doivent être créées en tant que propriétés cachées.
  • Le nom de la propriété est calculé ou construit en fonction d’une entrée au processus de génération de modèle.

Colonnes de clé étrangère non-nullable

Comme décrit dans les relations facultatives et requises, la possibilité de valeur NULL de la propriété de clé étrangère détermine si une relation est facultative ou obligatoire. Toutefois, une propriété de clé étrangère pouvant accéder à la valeur Null peut être utilisée pour une relation requise à l’aide de l’attribut [Required]ou en appelant IsRequired dans l’API de génération de modèle. Par exemple :

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

Ou, si la clé étrangère est découverte par convention, IsRequired peut être utilisé sans appel à HasForeignKey:

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

Le résultat final est que la colonne de clé étrangère de la base de données est rendue non-nullable même si la propriété de clé étrangère a la possibilité de valeur NULL. La même chose peut être obtenue en configurant explicitement la propriété de clé étrangère elle-même en fonction des besoins. Par exemple :

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

Clés étrangères cachées

Les propriétés de clé étrangère peuvent être créées en tant que propriétés cachées. Une propriété cachée existe dans le modèle EF, mais n’existe pas sur le type .NET. EF effectue le suivi de la valeur de propriété et de l’état en interne.

Les clés étrangères cachées sont généralement utilisées lorsqu’il existe une volonté de masquer le concept relationnel d’une clé étrangère du modèle de domaine utilisé par le code d’application/la logique métier. Ce code d’application manipule ensuite entièrement la relation par le biais de navigations.

Conseil

Si les entités vont être sérialisées, par exemple pour envoyer 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 dans un formulaire d’objet/graphique. 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é.

Les propriétés cachées de clé étrangère sont souvent créées par convention. Une clé étrangère cachée est également créée si l’argument de HasForeignKey ne correspond à aucune propriété .NET. Par exemple :

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

Par convention, une clé étrangère cachée obtient son type de la clé principale dans la relation. Ce type est rendu pouvant accepter la valeur Null, sauf si la relation est détectée ou configurée comme nécessaire.

La propriété de clé étrangère cachée peut également être créée explicitement, ce qui est utile pour configurer des facettes de la propriété. Par exemple, pour rendre la propriété non-nullable :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .Property<string>("MyBlogId")
        .IsRequired();

    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("MyBlogId");
}

Conseil

Par convention, les propriétés de clé étrangère héritent des facettes telles que la longueur maximale et la prise en charge Unicode de la clé principale dans la relation. Il est donc rarement nécessaire de configurer explicitement des facettes sur une propriété de clé étrangère.

La création d’une propriété cachée, si le nom donné ne correspond à aucune propriété du type d’entité peut être désactivée à l’aide de ConfigureWarnings. Par exemple :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Throw(CoreEventId.ShadowPropertyCreated));

Noms des contraintes de clés étrangères

Par convention, les contraintes de clé étrangère sont nommées FK_<dependent type name>_<principal type name>_<foreign key property name>. Pour les clés étrangères composites, <foreign key property name> devient une liste séparée par un trait de soulignement des noms de propriétés de clé étrangère.

Cela peut être modifié dans l’API de génération de modèles à l’aide de HasConstraintName. Par exemple :

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

Conseil

Le nom de contrainte n’est pas utilisé par le runtime EF. Il est utilisé uniquement lors de la création d’un schéma de base de données à l’aide d’EF Core Migrations.

Index des clés étrangères

Par convention, EF crée un index de base de données pour la propriété ou les propriétés d’une clé étrangère. Pour plus d’informations sur les types d’index créés par convention, consultez les conventions de génération de modèles.

Conseil

Les relations sont définies dans le modèle EF entre les types d’entités inclus dans ce modèle. Certaines relations peuvent avoir besoin de référencer un type d’entité dans le modèle d’un autre contexte, par exemple lors de l’utilisation du modèle BoundedContext. Dans ce cas, la ou les colonnes de clé étrangère doivent être mappées aux propriétés normales, et ces propriétés peuvent ensuite être manipulées manuellement pour gérer les modifications apportées à la relation.

Clés de principal

Par convention, les clés étrangères sont limitées à la clé primaire du côté principal de la relation. Toutefois, une autre clé peut être utilisée à la place. Cette opération est obtenue à l’aide HasPrincipalKey de l’API de génération de modèles. Par exemple, pour une clé étrangère de propriété unique :

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

Ou pour une clé étrangère composite avec plusieurs propriétés :

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

HasPrincipalKey peut également recevoir le nom de la propriété de clé secondaire en tant que chaîne. Par exemple, pour une clé de propriété unique :

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

Ou, pour une clé composite :

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

Remarque

L’ordre des propriétés dans la clé principale et étrangère doit correspondre. Il s’agit également de l’ordre dans lequel la clé est définie dans le schéma de base de données. Il ne doit pas être identique à l’ordre des propriétés dans le type d’entité ou les colonnes de la table.

Il n’est pas nécessaire d’appeler HasAlternateKey pour définir la clé secondaire sur l’entité principale. Cette opération est effectuée automatiquement lorsque HasPrincipalKey est utilisé avec des propriétés qui ne sont pas les propriétés de clé primaire. Toutefois, HasAlternateKey peut être utilisé pour configurer davantage la clé secondaire, par exemple pour définir son nom de contrainte de base de données. Pour plus d’informations, consultez la rubrique Clés.

Relations avec les entités sans clé

Chaque relation doit avoir une clé étrangère qui fait référence à une clé principale (primaire ou alternative). Cela signifie qu’un type d’entité sans clé ne peut pas agir comme côté principal d’une relation, car il n’existe aucune clé principale pour les clés étrangères à référencer.

Conseil

Un type d’entité ne peut pas avoir une autre clé, mais aucune clé primaire. Dans ce cas, la clé secondaire (ou l’une des autres clés, s’il existe plusieurs) doit être promue vers la clé primaire.

Toutefois, les types d’entités sans clé peuvent toujours avoir des clés étrangères définies et peuvent donc agir comme côté dépendant d’une relation. Par exemple, considérons ces types, où Tag n’a pas de clé :

public class Tag
{
    public string Text { get; set; } = null!;
    public int PostId { get; set; }
    public Post Post { get; set; } = null!;
}

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

Tag peut être configuré comme côté dépendant de la relation :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Tag>()
        .HasNoKey();

    modelBuilder.Entity<Post>()
        .HasMany<Tag>()
        .WithOne(e => e.Post);
}

Remarque

EF ne prend pas en charge les navigations pointant vers des types d’entités sans clé. Consultez GitHub, problème #30331.

Clés étrangères dans les relations plusieurs-à-plusieurs

Dans les relations plusieurs-à-plusieurs, les clés étrangères sont définies sur le type d’entité de jointure et mappées aux contraintes de clé étrangère dans la table de jointure. Tout ce qui est décrit ci-dessus peut également être appliqué à ces clés étrangères d’entité de jointure. Par exemple, la définition des noms de contrainte de base de données :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            l => l.HasOne(typeof(Tag)).WithMany().HasConstraintName("TagForeignKey_Constraint"),
            r => r.HasOne(typeof(Post)).WithMany().HasConstraintName("PostForeignKey_Constraint"));
}