3

I have Tag and Post classes (many-to-many relation, code-first)

class Post {
    ICollection<Tag> Tags {get; set;} = new List<Tag>();
}

class Tag {
    ICollection<Post> Posts {get; set;} = new List<Post>();
}

Tag and Post classes has corresponding Tag and Post tables with TagId and PostId primary keys (note the singular naming convention)

When configuring my Post's builder I say

builder
    .HasMany(p => p.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity(j => j.HasData(new { PostId = 1, TagId = 1 }));

When I add try to add-migration Initial a blocking error interrupts the migration creation:

The seed entity for entity type 'PostTag (Dictionary<string, object>)' cannot be added because no value was provided for the required property 'PostsId'.

Could someone explain what it hidden on behind this message?

I see it passes with the plurals (PostsId vs PostId), but I don't want to use the plural on the foreignkeys, is there a way to use PostId instead of PostsId?

.UsingEntity(j => j.HasData(new { PostsId = 1, TagsId = 1 }));

Also, it creates the following migration:

migrationBuilder.CreateTable(
    name: "PostTag (Dictionary<string, object>)",
    columns: table => new
    {
        PostsId = table.Column<int>(type: "int", nullable: false),
        TagsId = table.Column<int>(type: "int", nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_PostTag (Dictionary<string, object>)", x => new { x.PostsId, x.TagsId });
        table.ForeignKey(
            name: "FK_PostTag (Dictionary<string, object>)_Post_PostsId",
            column: x => x.PostsId,
            principalTable: "Post",
            principalColumn: "PostId",
            onDelete: ReferentialAction.Cascade);

The table name "PostTag (Dictionary<string, object>)" (what a horror) and also PrimaryKey("PK_PostTag (Dictionary<string, object>)" ...

What is the logic to use such conventions ? What to do to have the normal PostTag table name?

serge
  • 13,940
  • 35
  • 121
  • 205
  • wondering from the voter to close the question what "Stack Overflow guideline" doesn’t meet this question... – serge Feb 06 '21 at 16:51
  • I've not used UsingEntity before. I normally create the join table in the model explicity. But, are you sure you want to use HasData in that configuration and not HasKey? – Neil W Feb 06 '21 at 23:07
  • https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#joining-relationships-configuration – Gert Arnold Feb 06 '21 at 23:32

2 Answers2

1

When EF generates something for you, it follows it's specific naming convention. PostTag is the joining table generated by EF to maintain the many-to-many relationship and you are not supposed to be aware of it's existence and how EF manages it.

If you do want to be aware of that table and want it's columns named your way, then you have to create the joining entity, name the properties as you want them to be and configure it explicitly.

The joining entity -

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

    public Post Post { get; set; }
    public Tag Tag { get; set; }
}

The configuration, along with seed -

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>(e =>
    {
        e.ToTable("Post").HasData(new Post { Id = 1, Title = "EF Deep Dive" });               
        e.HasMany(p => p.Tags)
        .WithMany(p => p.Posts)
        .UsingEntity<PostTag>(
            link => link.HasOne(p => p.Tag).WithMany().HasForeignKey(p => p.TagId),
            link => link.HasOne(p => p.Post).WithMany().HasForeignKey(p => p.PostId))
        .HasKey(p => new { p.PostId, p.TagId });
    });

    modelBuilder.Entity<Tag>(e =>
    {
        e.ToTable("Tag").HasData(new Tag { Id = 1, Name = "EF" }, new Tag { Id = 2, Name = ".NET" });
    });

    modelBuilder.Entity<PostTag>().HasData(new { PostId = 1, TagId = 1 });
}
atiyar
  • 7,762
  • 6
  • 34
  • 75
0

As it's writen in error message you should have the primary keys in both tables if you want to use many-to-many:

public class Post
{
     [Key]
    public int PostId { get; set; }
    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    [Key]
    public int TagId { get; set; }
    public ICollection<Post> Posts { get; set; }
}

and db context:

 protected override void OnModelCreating(ModelBuilder modelBuilder)
{

    var posts = new[]
     {
     new Post{PostId=1},
     new Post{PostId=2},
     new Post{PostId=3}
    };

 var tags = new[]
    {
     new Tag{TagId=1},
     new Tag{TagId=2}
    };

 modelBuilder.Entity<Tag>().HasData(tags);

   modelBuilder.Entity<Post>().HasData(posts);

    modelBuilder.Entity<Post>()
    .HasMany(p => p.Tags)
     .WithMany(p => p.Posts)
    .UsingEntity(j => j.HasData(new { PostsPostId = 1, TagsTagId = 1 }));
    
}
Serge
  • 40,935
  • 4
  • 18
  • 45