Search code examples
c#entity-frameworkef-code-firstef-code-first-mapping

How to correctly initialize many to many relationship with code-first


I'm trying to use code-first to model my db.

What I'm trying to map here is a 0 -> * relationship for Users and Challenges.

E.g A user can have 0 or more challenges associated with them. A challenge can have 0 or more users associated with them.

This is the code I'm using, the problem is that the challenges table is generated with a UserAccount_ID property.

public class UserAccount
{
    public int Id { get; set; }
    public string Username { get; set; }

    public virtual List<Challenge> Challenges { get; set; }
}

public class Challenge
{
    public int Id { get; set; }
    public string ChallengeId {get; set;}
    public string Name { get; set; }
    public string Description { get; set; }

    public virtual List<Competitor> Competitors { get; set; }
}

public class Competitor
{
    public int Id { get; set; }
    public string Username { get; set; }
    public int Rank { get; set; }
}

And the incorrect property:

enter image description here

I've tried adding this to my context class but it doesn't have any effect:

protected override void OnModelCreating(DbModelBuilder mb)
{
    mb.Entity<UserAccount>()
        .HasMany(o1 => o1.Challenges)
        .WithOptional();

    base.OnModelCreating(mb);
}

Edit

Updated title and question - relationship is not zero to many but many to many.


Solution

  • That column comes from your relationship between Challenge and UserAccount. You could declare the FK property in your model:

    public class Challenge
    {
        public int Id { get; set; }
        public string ChallengeId {get; set;}
        public string Name { get; set; }
        public string Description { get; set; }
    
        //Add these properties
        public int UserAccountId{get;set;}
        public UserAccount UserAccount{get;set;}
    
        public virtual List<Competitor> Competitors { get; set; }
    }
    

    By default EF use some name conventions to identify the FK properties in your model, so it should be fine with that name. Now, if you are using Fluent Api, then I suggest to configure the relationship as you are intending to do:

    protected override void OnModelCreating(DbModelBuilder mb)
    {
        mb.Entity<UserAccount>()
            .HasMany(o1 => o1.Challenges)
            .WithOptional(e=>e.UserAccount);
            .HasForeignKey(e=>e.UserAccountId);
    
        base.OnModelCreating(mb);
    }
    

    Now if you don't want to include the FK property and the navigation property in your Challenge entity but you want to rename the FK property you can do this:

    protected override void OnModelCreating(DbModelBuilder mb)
    {
        mb.Entity<UserAccount>()
            .HasMany(o1 => o1.Challenges)
            .WithOptional();
            .Map(m => m.MapKey("UserAccountId"));
    
        base.OnModelCreating(mb);
    }
    

    Thinking a little more about your model it is true that maybe what you are really pursuing is a many to many relationship, if that if the case you can configure this way:

      mb.Entity<UserAccount>()
        .HasMany(r => r.Challenges)
        .WithMany() // No navigation property here
        .Map(m =>
            {
                m.MapLeftKey("UserAccountId");
                m.MapRightKey("ChallengeId");
                m.ToTable("UserAccountChallenge");
            });