How to create a many-to-many mapping in Entity Framework?

C#Entity FrameworkMany to-Many

C# Problem Overview


Here is the case, I have 2 entities, such as Contract、Media。

public class Media : Entity
{
    public string Name {get; set;}
    public bool Enabled
    *//other properties can be ignored..*
}

public class Contract : Entity
{
    public string Code {get; set;}
    *//other properties can be ignored..*
}

Contract has many Medias, it seems that they are many to many.

But!! at ef code first, i need 3 more fields in the ContractMedia table(ef auto generated). such as StartDate,EndDate and Price. these could not be added in Media entity.

How to map at this case??

C# Solutions


Solution 1 - C#

If you want to create many to many relationship with additional data in association table, you have to make the association table as entity. The pure many to many relationship is only in pure table with entity id's.

In you case it will be:

public class Media // One entity table
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool Enabled { get; set; }

    public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}

public class Contract // Second entity table
{
    public int Id { get; set; }
    public string Code { get; set }

    public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}

public class ContractMedia // Association table implemented as entity
{
    public int MediaId { get; set; }
    public int ContractId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public double Price { get; set; }

    public virtual Media Media { get; set; }
    public virtual Contract Contract { get; set; }
}

And after you created models/entities, you need to define relationships in context:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
   modelBuilder.Entity<ContractMedia>()
       .HasKey(c => new { c.MediaId, c.ContractId });

   modelBuilder.Entity<Contract>()
       .HasMany(c => c.ContractMedias)
       .WithRequired()
       .HasForeignKey(c => c.ContractId);

   modelBuilder.Entity<Media>()
       .HasMany(c => c.ContractMedias)
       .WithRequired()
       .HasForeignKey(c => c.MediaId);  
}

Also you can refer to these links:
<https://stackoverflow.com/questions/15153390/many-to-many-mapping-with-extra-fields-in-fluent-api><br /> <https://stackoverflow.com/questions/5434125/entity-framework-codefirst-many-to-many-relationship-with-additional-information><br /> <https://stackoverflow.com/questions/7050404/create-code-first-many-to-many-with-additional-fields-in-association-table>

Solution 2 - C#

Adding to @Tomas answer without having to use Fluent API.

public class Media // One entity table
{
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}

public class Contract // Second entity table
{
    public int Id { get; set; }

    public string Code { get; set }

    public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}

public class ContractMedia // Association table implemented as entity
{
    [Key]
    [Column(Order = 0)]
    [ForeignKey("Media")]
    public int MediaId { get; set; }

    [Key]
    [Column(Order = 1)]
    [ForeignKey("Contract")]
    public int ContractId { get; set; }

    public DateTime StartDate { get; set; }

    public DateTime EndDate { get; set; }

    public double Price { get; set; }

    public virtual Media Media { get; set; }

    public virtual Contract Contract { get; set; }
}

EF Core needs to use Fluent API but it would look like this:

internal class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
    }

    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasMany(p => p.Tags)
            .WithMany(p => p.Posts)
            .UsingEntity<PostTag>(
                j => j
                    .HasOne(pt => pt.Tag)
                    .WithMany(t => t.PostTags)
                    .HasForeignKey(pt => pt.TagId),
                j => j
                    .HasOne(pt => pt.Post)
                    .WithMany(p => p.PostTags)
                    .HasForeignKey(pt => pt.PostId),
                j =>
                {
                    j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
                    j.HasKey(t => new { t.PostId, t.TagId });
                });
    }
}

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

    public ICollection<Tag> Tags { get; set; }
    public List<PostTag> PostTags { get; set; }
}

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

    public ICollection<Post> Posts { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

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

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

Source:

https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKratosView Question on Stackoverflow
Solution 1 - C#TomasView Answer on Stackoverflow
Solution 2 - C#OgglasView Answer on Stackoverflow