RelationsRelationships

Une relation définit la relation entre deux entités.A relationship defines how two entities relate to each other. Dans une base de données relationnelle, il est représenté par une contrainte de clé étrangère.In a relational database, this is represented by a foreign key constraint.

Note

La plupart des exemples de cet article utilisent une relation un-à-plusieurs pour illustrer les concepts.Most of the samples in this article use a one-to-many relationship to demonstrate concepts. Pour obtenir des exemples de relations un-à-un et plusieurs-à-plusieurs, consultez la section autres modèles de relation à la fin de l’article.For examples of one-to-one and many-to-many relationships see the Other Relationship Patterns section at the end of the article.

Définition des termesDefinition of terms

Il existe un certain nombre de termes utilisés pour décrire les relationsThere are a number of terms used to describe relationships

  • Entité dépendante : Il s’agit de l’entité qui contient les propriétés de clé étrangère.Dependent entity: This is the entity that contains the foreign key properties. Parfois appelé « enfant » de la relation.Sometimes referred to as the 'child' of the relationship.

  • Entité principale : Il s’agit de l’entité qui contient les propriétés de clé primaire/secondaire.Principal entity: This is the entity that contains the primary/alternate key properties. Parfois appelé « parent » de la relation.Sometimes referred to as the 'parent' of the relationship.

  • Clé principale : Propriétés qui identifient de façon unique l’entité principale.Principal key: The properties that uniquely identify the principal entity. Il peut s’agir de la clé primaire ou d’une clé secondaire.This may be the primary key or an alternate key.

  • Clé étrangère : Propriétés de l’entité dépendante utilisées pour stocker les valeurs de clé principale de l’entité associée.Foreign key: The properties in the dependent entity that are used to store the principal key values for the related entity.

  • Propriété de navigation : Propriété définie sur le principal et/ou sur l’entité dépendante qui référence l’entité associée.Navigation property: A property defined on the principal and/or dependent entity that references the related entity.

    • Propriété de navigation de la collection : Propriété de navigation qui contient des références à de nombreuses entités associées.Collection navigation property: A navigation property that contains references to many related entities.

    • Propriété de navigation de référence : Propriété de navigation qui contient une référence à une entité associée unique.Reference navigation property: A navigation property that holds a reference to a single related entity.

    • Propriété de navigation inverse : Lorsque vous discutez d’une propriété de navigation particulière, ce terme fait référence à la propriété de navigation à l’autre extrémité de la relation.Inverse navigation property: When discussing a particular navigation property, this term refers to the navigation property on the other end of the relationship.

  • Relation à référence automatique : Relation dans laquelle les types d’entités dependent et principal sont identiques.Self-referencing relationship: A relationship in which the dependent and the principal entity types are the same.

Le code suivant illustre une relation un-à-plusieurs entre Blog etPostThe following code shows a one-to-many relationship between Blog and Post

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}
  • Postest l’entité dépendantePost is the dependent entity

  • Blogest l’entité principaleBlog is the principal entity

  • Blog.BlogIdest la clé principale (dans le cas présent, il s’agit d’une clé primaire plutôt que d’une clé secondaire)Blog.BlogId is the principal key (in this case it is a primary key rather than an alternate key)

  • Post.BlogIdest la clé étrangèrePost.BlogId is the foreign key

  • Post.Blogest une propriété de navigation de référencePost.Blog is a reference navigation property

  • Blog.Postsest une propriété de navigation de collectionBlog.Posts is a collection navigation property

  • Post.Blogest la propriété de navigation inverse de Blog.Posts (et vice versa)Post.Blog is the inverse navigation property of Blog.Posts (and vice versa)

ConventionsConventions

Par défaut, une relation est créée lorsqu’une propriété de navigation est détectée sur un type.By default, a relationship will be created when there is a navigation property discovered on a type. Une propriété est considérée comme une propriété de navigation si le type vers lequel elle pointe ne peut pas être mappé en tant que type scalaire par le fournisseur de base de données actuel.A property is considered a navigation property if the type it points to can not be mapped as a scalar type by the current database provider.

Note

Les relations découvertes par convention cibleront toujours la clé primaire de l’entité principale.Relationships that are discovered by convention will always target the primary key of the principal entity. Pour cibler une clé secondaire, une configuration supplémentaire doit être effectuée à l’aide de l’API Fluent.To target an alternate key, additional configuration must be performed using the Fluent API.

Relations entièrement définiesFully defined relationships

Le modèle le plus courant pour les relations est d’avoir des propriétés de navigation définies aux deux extrémités de la relation et une propriété de clé étrangère définie dans la classe d’entité dépendante.The most common pattern for relationships is to have navigation properties defined on both ends of the relationship and a foreign key property defined in the dependent entity class.

  • Si une paire de propriétés de navigation est trouvée entre deux types, ils sont configurés en tant que propriétés de navigation inverse de la même relation.If a pair of navigation properties is found between two types, then they will be configured as inverse navigation properties of the same relationship.

  • Si l’entité dépendante contient une propriété dont le nom correspond à l’un de ces modèles, elle est configurée en tant que clé étrangère :If the dependent entity contains a property with a name matching one of these patterns then it will be configured as the foreign key:

    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity name><principal key property name>
    • <principal entity name>Id
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Dans cet exemple, les propriétés mises en surbrillance sont utilisées pour configurer la relation.In this example the highlighted properties will be used to configure the relationship.

Note

Si la propriété est la clé primaire ou si elle est d’un type qui n’est pas compatible avec la clé principale, elle ne sera pas configurée en tant que clé étrangère.If the property is the primary key or is of a type not compatible with the principal key then it won't be configured as the foreign key.

Note

Avant le EF Core 3,0, la propriété nommée exactement comme la propriété de clé principale était également mise en correspondance comme clé étrangèreBefore EF Core 3.0 the property named exactly the same as the principal key property was also matched as the foreign key

Aucune propriété de clé étrangèreNo foreign key property

Bien qu’il soit recommandé d’avoir une propriété de clé étrangère définie dans la classe d’entité dépendante, elle n’est pas obligatoire.While it is recommended to have a foreign key property defined in the dependent entity class, it is not required. Si aucune propriété de clé étrangère n’est trouvée, une propriété de clé étrangère Shadow est introduite avec le nom <navigation property name><principal key property name> ou <principal entity name><principal key property name> si aucune navigation n’est présente sur le type dépendant.If no foreign key property is found, a shadow foreign key property will be introduced with the name <navigation property name><principal key property name> or <principal entity name><principal key property name> if no navigation is present on the dependent type.

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

Dans cet exemple, la clé étrangère Shadow est en BlogId raison du fait que le nom de navigation est redondant.In this example the shadow foreign key is BlogId because prepending the navigation name would be redundant.

Note

Si une propriété du même nom existe déjà, le nom de la propriété Shadow sera suivi d’un nombre.If a property with the same name already exists then the shadow property name will be suffixed with a number.

Propriété de navigation uniqueSingle navigation property

L’inclusion d’une seule propriété de navigation (aucune navigation inverse et aucune propriété de clé étrangère) suffit à avoir une relation définie par Convention.Including just one navigation property (no inverse navigation, and no foreign key property) is enough to have a relationship defined by convention. Vous pouvez également avoir une propriété de navigation unique et une propriété de clé étrangère.You can also have a single navigation property and a foreign key property.

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

LimitesLimitations

Lorsqu’il existe plusieurs propriétés de navigation définies entre deux types (autrement dit, plusieurs paires de navigation qui pointent les unes vers les autres), les relations représentées par les propriétés de navigation sont ambiguës.When there are multiple navigation properties defined between two types (that is, more than just one pair of navigations that point to each other) the relationships represented by the navigation properties are ambiguous. Vous devrez les configurer manuellement pour résoudre l’ambiguïté.You will need to manually configure them to resolve the ambiguity.

Suppression en cascadeCascade delete

Par Convention, la suppression en cascade sera définie sur cascade pour les relations requises et ClientSetNull pour les relations facultatives.By convention, cascade delete will be set to Cascade for required relationships and ClientSetNull for optional relationships. Cascade signifie que les entités dépendantes sont également supprimées.Cascade means dependent entities are also deleted. ClientSetNull signifie que les entités dépendantes qui ne sont pas chargées en mémoire restent inchangées et doivent être supprimées manuellement ou mises à jour pour pointer vers une entité principale valide.ClientSetNull means that dependent entities that are not loaded into memory will remain unchanged and must be manually deleted, or updated to point to a valid principal entity. Pour les entités chargées en mémoire, EF Core tente de définir les propriétés de clé étrangère sur la valeur null.For entities that are loaded into memory, EF Core will attempt to set the foreign key properties to null.

Consultez la section relations obligatoires et facultatives pour connaître la différence entre les relations obligatoires et facultatives.See the Required and Optional Relationships section for the difference between required and optional relationships.

Pour plus d’informations sur les différents comportements de suppression et les valeurs par défaut utilisées par Convention, consultez suppression en cascade .See Cascade Delete for more details about the different delete behaviors and the defaults used by convention.

Configuration manuelleManual configuration

Pour configurer une relation dans l’API Fluent, commencez par identifier les propriétés de navigation qui composent la relation.To configure a relationship in the Fluent API, you start by identifying the navigation properties that make up the relationship. HasOneou HasMany identifie la propriété de navigation sur le type d’entité sur lequel vous commencez la configuration.HasOne or HasMany identifies the navigation property on the entity type you are beginning the configuration on. Vous enchaînez ensuite un appel à WithOne ou WithMany pour identifier la navigation inverse.You then chain a call to WithOne or WithMany to identify the inverse navigation. HasOne/WithOnesont utilisés pour les propriétés de navigation de référence et HasMany / WithMany sont utilisés pour les propriétés de navigation de collection.HasOne/WithOne are used for reference navigation properties and HasMany/WithMany are used for collection navigation properties.

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

Propriété de navigation uniqueSingle navigation property

Si vous n’avez qu’une seule propriété de navigation, il y a des surcharges sans paramètre de WithOne et WithMany .If you only have one navigation property then there are parameterless overloads of WithOne and WithMany. Cela indique qu’il existe conceptuellement une référence ou une collection à l’autre extrémité de la relation, mais qu’il n’y a aucune propriété de navigation incluse dans la classe d’entité.This indicates that there is conceptually a reference or collection on the other end of the relationship, but there is no navigation property included in the entity class.

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

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

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

Configuration des propriétés de navigationConfiguring navigation properties

Une fois la propriété de navigation créée, vous devrez peut-être la configurer ultérieurement.After the navigation property has been created, you may need to further configure it. Dans EFCore 5,0, une nouvelle API Fluent est ajoutée pour vous permettre d’effectuer cette configuration.In EFCore 5.0 new Fluent API is added to allow you perform that configuration.

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

    modelBuilder.Entity<Blog>()
        .Navigation(b => b.Posts)
            .UsePropertyAccessMode(PropertyAccessMode.Property);
}

Note

Cet appel ne peut pas être utilisé pour créer une propriété de navigation.This call cannot be used to create a navigation property. Elle est utilisée uniquement pour configurer une propriété de navigation qui a été créée précédemment en définissant une relation ou à partir d’une convention.It is only used to configure a navigation property which has been previously created by defining a relationship or from a convention.

Clé étrangèreForeign key

Vous pouvez utiliser l’API Fluent pour configurer la propriété à utiliser comme propriété de clé étrangère pour une relation donnée :You can use the Fluent API to configure which property should be used as the foreign key property for a given relationship:

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogForeignKey);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogForeignKey { get; set; }
    public Blog Blog { get; set; }
}

Masquer la clé étrangèreShadow foreign key

Vous pouvez utiliser la surcharge de chaîne de HasForeignKey(...) pour configurer une propriété Shadow comme clé étrangère (pour plus d’informations, consultez Propriétés Shadow ).You can use the string overload of HasForeignKey(...) to configure a shadow property as a foreign key (see Shadow Properties for more information). Nous vous recommandons d’ajouter explicitement la propriété Shadow au modèle avant de l’utiliser comme clé étrangère (comme indiqué ci-dessous).We recommend explicitly adding the shadow property to the model before using it as a foreign key (as shown below).

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Add the shadow property to the model
        modelBuilder.Entity<Post>()
            .Property<int>("BlogForeignKey");

        // Use the shadow property as a foreign key
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey("BlogForeignKey");
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

Nom de la contrainte de clé étrangèreForeign key constraint name

Par Convention, lorsque vous ciblez une base de données relationnelle, les contraintes de clé étrangère sont nommées FK_ .By convention, when targeting a relational database, foreign key constraints are named FK_. Pour les clés étrangères composites devient une liste de noms de propriétés de clé étrangère séparés par un trait de soulignement.For composite foreign keys becomes an underscore separated list of foreign key property names.

Vous pouvez également configurer le nom de la contrainte comme suit :You can also configure the constraint name as follows:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .HasForeignKey(p => p.BlogId)
        .HasConstraintName("ForeignKey_Post_Blog");
}

Sans propriété de navigationWithout navigation property

Vous n’avez pas nécessairement besoin de fournir une propriété de navigation.You don't necessarily need to provide a navigation property. Vous pouvez simplement fournir une clé étrangère d’un côté de la relation.You can simply provide a foreign key on one side of the relationship.

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

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

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
}

Clé principalePrincipal key

Si vous souhaitez que la clé étrangère fasse référence à une propriété autre que la clé primaire, vous pouvez utiliser l’API Fluent pour configurer la propriété de clé principale de la relation.If you want the foreign key to reference a property other than the primary key, you can use the Fluent API to configure the principal key property for the relationship. La propriété que vous configurez en tant que clé principale est automatiquement configurée en tant que clé secondaire.The property that you configure as the principal key will automatically be setup as an alternate key.

class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<RecordOfSale>()
            .HasOne(s => s.Car)
            .WithMany(c => c.SaleHistory)
            .HasForeignKey(s => s.CarLicensePlate)
            .HasPrincipalKey(c => c.LicensePlate);
    }
}

public class Car
{
    public int CarId { get; set; }
    public string LicensePlate { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }

    public List<RecordOfSale> SaleHistory { get; set; }
}

public class RecordOfSale
{
    public int RecordOfSaleId { get; set; }
    public DateTime DateSold { get; set; }
    public decimal Price { get; set; }

    public string CarLicensePlate { get; set; }
    public Car Car { get; set; }
}

Relations obligatoires et facultativesRequired and optional relationships

Vous pouvez utiliser l’API Fluent pour déterminer si la relation est obligatoire ou facultative.You can use the Fluent API to configure whether the relationship is required or optional. Au final, cela contrôle si la propriété de clé étrangère est obligatoire ou facultative.Ultimately this controls whether the foreign key property is required or optional. Cela est particulièrement utile lorsque vous utilisez une clé étrangère d’État Shadow.This is most useful when you are using a shadow state foreign key. Si vous avez une propriété de clé étrangère dans votre classe d’entité, le caractère requis de la relation est déterminé selon que la propriété de clé étrangère est obligatoire ou facultative (pour plus d’informations, consultez propriétés requises et facultatives ).If you have a foreign key property in your entity class then the requiredness of the relationship is determined based on whether the foreign key property is required or optional (see Required and Optional properties for more information).

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

Note

IsRequired(false)L’appel rend également la propriété de clé étrangère facultative, sauf si elle est configurée dans le cas contraire.Calling IsRequired(false) also makes the foreign key property optional unless it's configured otherwise.

Suppression en cascadeCascade delete

Vous pouvez utiliser l’API Fluent pour configurer explicitement le comportement de suppression en cascade pour une relation donnée.You can use the Fluent API to configure the cascade delete behavior for a given relationship explicitly.

Pour une présentation détaillée de chaque option, consultez suppression en cascade .See Cascade Delete for a detailed discussion of each option.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .OnDelete(DeleteBehavior.Cascade);
}

Autres modèles de relationOther relationship patterns

Un à unOne-to-one

Les relations un-à-un ont une propriété de navigation de référence des deux côtés.One to one relationships have a reference navigation property on both sides. Ils suivent les mêmes conventions que les relations un-à-plusieurs, mais un index unique est introduit sur la propriété de clé étrangère pour s’assurer qu’un seul dépendant est lié à chaque principal.They follow the same conventions as one-to-many relationships, but a unique index is introduced on the foreign key property to ensure only one dependent is related to each principal.

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Note

EF choisit l’une des entités à dépendre en fonction de sa capacité à détecter une propriété de clé étrangère.EF will choose one of the entities to be the dependent based on its ability to detect a foreign key property. Si l’entité incorrecte est choisie comme dépendant, vous pouvez utiliser l’API Fluent pour corriger ce problème.If the wrong entity is chosen as the dependent, you can use the Fluent API to correct this.

Quand vous configurez la relation avec l’API Fluent, vous utilisez les HasOne WithOne méthodes et.When configuring the relationship with the Fluent API, you use the HasOne and WithOne methods.

Quand vous configurez la clé étrangère, vous devez spécifier le type d’entité dépendant. Notez le paramètre générique fourni à HasForeignKey dans la liste ci-dessous.When configuring the foreign key you need to specify the dependent entity type - notice the generic parameter provided to HasForeignKey in the listing below. Dans une relation un-à-plusieurs, il est clair que l’entité avec la navigation de référence est la dépendance et celle avec la collection est le principal.In a one-to-many relationship it is clear that the entity with the reference navigation is the dependent and the one with the collection is the principal. Mais ce n’est pas le cas dans une relation un-à-un, c’est pourquoi il est nécessaire de la définir explicitement.But this is not so in a one-to-one relationship - hence the need to explicitly define it.

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<BlogImage> BlogImages { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasOne(b => b.BlogImage)
            .WithOne(i => i.Blog)
            .HasForeignKey<BlogImage>(b => b.BlogForeignKey);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogForeignKey { get; set; }
    public Blog Blog { get; set; }
}

Plusieurs à plusieursMany-to-many

Les relations plusieurs-à-plusieurs sans classe d’entité pour représenter la table de jointure ne sont pas encore prises en charge.Many-to-many relationships without an entity class to represent the join table are not yet supported. Toutefois, vous pouvez représenter une relation plusieurs-à-plusieurs en incluant une classe d’entité pour la table de jointure et en mappant deux relations un-à-plusieurs distinctes.However, you can represent a many-to-many relationship by including an entity class for the join table and mapping two separate one-to-many relationships.

class MyContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PostTag>()
            .HasKey(t => new { t.PostId, t.TagId });

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Post)
            .WithMany(p => p.PostTags)
            .HasForeignKey(pt => pt.PostId);

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Tag)
            .WithMany(t => t.PostTags)
            .HasForeignKey(pt => pt.TagId);
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}