Search code examples
entity-frameworkef-code-firstmany-to-manyentitycode-first

Entity Framework code-first, Many-to-Many relationship on the same table


I'm building a social network with Friend function. My idea is that I've already had the default ApplicationUser class, so I create a new table called Friend

public class Friend
{
    [Key]
    [Column(Order = 1)]
    public string SenderId { get; set; }

    [Key]
    [Column(Order = 2)]
    public string ReceiverId { get; set; }

    //Status == true : Friend request accepted
    //Status == false : Friend request not accepted
    public bool Status { get; set; }
}

In the ApplicationUser, I define 2 navigation properties Senders and Receivers (to link to Friend table)

public class ApplicationUser : IdentityUser
{
    [Required]
    [StringLength(50)]
    public string Name { get; set; }

    [Required]
    public bool Gender { get; set; }

    [StringLength(255)]
    public string Address { get; set; }

    [StringLength(255)]
    public string Job { get; set; }

    [StringLength(255)]
    public string Image { get; set; }

    public DateTime Birthday { get; set; }

    public ICollection<ApplicationUser> Senders { get; set; }
    public ICollection<ApplicationUser> Receivers { get; set; }
}

And finally in ApplicationDbContext, I declare relationships between 2 tables using Fluent Api

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{

    public DbSet<Friend> Friends { get; set; }

    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ApplicationUser>()
            .HasMany(a => a.Senders)
            .WithMany(a => a.Receivers)
            .Map(m =>
            {
                m.MapLeftKey("ReceiverId");
                m.MapRightKey("SenderId");
                m.ToTable("Friends");
            });
        base.OnModelCreating(modelBuilder);

    }
}

But when I add-migration, it creates 2 tables like this, and neither of them is what I need (one doesn't have foreign keys, one doesn't have Status properties)

public override void Up()
    {
        CreateTable(
            "dbo.Friends1",
            c => new
                {
                    SenderId = c.String(nullable: false, maxLength: 128),
                    ReceiverId = c.String(nullable: false, maxLength: 128),
                    Status = c.Boolean(nullable: false),
                })
            .PrimaryKey(t => new { t.SenderId, t.ReceiverId });

        CreateTable(
            "dbo.Friends",
            c => new
                {
                    SenderId = c.String(nullable: false, maxLength: 128),
                    ReceiverId = c.String(nullable: false, maxLength: 128),
                })
            .PrimaryKey(t => new { t.SenderId, t.ReceiverId })
            .ForeignKey("dbo.AspNetUsers", t => t.SenderId)
            .ForeignKey("dbo.AspNetUsers", t => t.ReceiverId)
            .Index(t => t.SenderId)
            .Index(t => t.ReceiverId);

    }

What am I supposed to do :( I've searched on internet and this seems legit but it doesn't work


Solution

  • But when I add-migration, it creates 2 tables like this, and neither of them is what I need (one doesn't have foreign keys, one doesn't have Status properties)

    This is because you mixed the two possible many-to-many associations supported by EF - one using implicit junction table (from the Fluent API configuration) and the other using explicit junction table (Friend entity) and two one-to-many associations.

    Since the first approach can be used only if you don't have additional data associated with the association, and you have one (Status property), you need to use the second approach.

    In order to do that, change the type of the navigation properties as follows:

    public class ApplicationUser : IdentityUser
    {
        // ...
        public ICollection<Friend> Senders { get; set; }
        public ICollection<Friend> Receivers { get; set; }
    }
    

    and the configuration:

    modelBuilder.Entity<ApplicationUser>()
        .HasMany(e => e.Senders)
        .WithRequired()
        .HasForeignKey(e => e.ReceiverId);
    
    modelBuilder.Entity<ApplicationUser>()
        .HasMany(e => e.Receivers)
        .WithRequired()
        .HasForeignKey(e => e.SenderId);