61

From examples that I have seen online and in a Programming Entity Framework CodeFirst book, when you have a collection on both classes EF would create a mapping table such as MembersRecipes and the primary key from each class would link to this table.

However when I do the below, I instead get a new field in the Recipes table called Member_Id and a Recipe_Id in the Members table.

Which only creates two one-to-many relationships, but not a many-to-many so I could have Member 3 linked to Recipes (4,5,6) and Recipe 4 linked to Members (1,2,3) etc.

Is there a way to create this mapping table? and if so how do you name it something else such as "cookbooks" ?

Thanks

    public abstract class Entity {
        [Required]
        public int Id { get; set; }
    }   

    public class Member : Entity {
        [Required]
        public string Name { get; set; }

        public virtual IList<Recipe> Recipes { get; set; }
    }

    public class Recipe : Entity {  
        [Required]
        public string Name { get; set; }

        [ForeignKey("Author")]
        public int AuthorId { get; set; }
        public virtual Member Author { get; set; }

            ....

        public virtual IList<Member> Members { get; set; }
    }

UPDATE: Below is another approach I have tried which doesn't use the Fluent API and replaces the AuthorId & Author on Recipe with an owner flag, I have also renamed the below example from Cookbooks to MembersRecipes, this also fixes my issue similar to the answer but as mentioned has further implications.

public class MembersRecipes {

    [Key, Column(Order = 0)]
    [ForeignKey("Recipe")]
    public int RecipeId { get; set; }
    public virtual Recipe Recipe { get; set; }

    [Key, Column(Order = 1)]
    [ForeignKey("Member")]
    public int MemberId { get; set; }
    public virtual Member Member { get; set; }

    public bool Owner { get; set; }
}

and in Recipe & Member classes I changed the collections to

public virtual IList<MembersRecipes> MembersRecipes { get; set; }
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Pricey
  • 5,799
  • 12
  • 60
  • 84
  • 2
    Regarding your update, in Entity Framework's parlance that many-to-many approach is called *many-to-many mapping with payload* I don't know why they come up with a very fancy term(payload); or it's just me, my native language is not English :-) I usually associate the word payload with computer virus. In EF, payload is not something sinister, you can future-proof your design by making your table a payload-ready entity(e.g. By adding extra ID as primary key, consequently you need to convert your existing many-to-many composite pk to unique composite columns) from the get-go – Michael Buen Jul 08 '12 at 23:59
  • 1
    In your case, you didn't add an extra ID, you maintained the composite primary key. The Owner flag on the table is the payload – Michael Buen Jul 09 '12 at 00:02
  • @MichaelBuen The payload refers to the mapping table (relationship) where you can put extra data (e.g. creation date of the relationship) in . If you should use EF many-to-many with a hidden table, you cannot add a payload to the relationship – RHAD Jul 18 '14 at 08:37
  • For me when I had this problem, it was because one of my navigation properties was a `List` and the other was an `IEnumerable`. I changed them to both be lists and then `Add-Migration` started generating a XR table instead of just the FK properties. – levininja Jan 14 '15 at 22:35

1 Answers1

98

Do this on your DbContext OnModelCreating:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{    
    modelBuilder.Entity<Recipe>()
        .HasMany(x => x.Members)
        .WithMany(x => x.Recipes)
    .Map(x =>
    {
        x.ToTable("Cookbooks"); // third table is named Cookbooks
        x.MapLeftKey("RecipeId");
        x.MapRightKey("MemberId");
    });
}

You can do it the other way around too, it's the same, just another side of the same coin:

modelBuilder.Entity<Member>()
    .HasMany(x => x.Recipes)
    .WithMany(x => x.Members)
.Map(x =>
{
  x.ToTable("Cookbooks"); // third table is named Cookbooks
  x.MapLeftKey("MemberId");
  x.MapRightKey("RecipeId");
});

Further examples:

http://www.ienablemuch.com/2011/07/using-checkbox-list-on-aspnet-mvc-with_16.html

http://www.ienablemuch.com/2011/07/nhibernate-equivalent-of-entity.html


UPDATE

To prevent cyclical reference on your Author property, aside from above, you need to add this:

modelBuilder.Entity<Recipe>()
    .HasRequired(x => x.Author)
    .WithMany()
    .WillCascadeOnDelete(false);

Idea sourced here: EF Code First with many to many self referencing relationship

The core thing is, you need to inform EF that the Author property(which is a Member instance) has no Recipe collections(denoted by WithMany()); that way, cyclical reference could be stopped on Author property.

These are the created tables from the Code First mappings above:

CREATE TABLE Members(
    Id int IDENTITY(1,1) NOT NULL primary key,
    Name nvarchar(128) NOT NULL
);


CREATE TABLE Recipes(
    Id int IDENTITY(1,1) NOT NULL primary key,
    Name nvarchar(128) NOT NULL,
    AuthorId int NOT NULL references Members(Id)
);


CREATE TABLE Cookbooks(
    RecipeId int NOT NULL,
    MemberId int NOT NULL,
    constraint pk_Cookbooks primary key(RecipeId,MemberId)
);
Community
  • 1
  • 1
Michael Buen
  • 38,643
  • 9
  • 94
  • 118
  • Thanks for the quick reply, looks good I'm just getting 1 problem. I get this error: "The referential relationship will result in a cyclical reference that is not allowed. [ Constraint name = FK_Cookbooks_Members_MemberId ]" I am assuming its related to my AuthorId on the Recipe entity but I'm not sure. – Pricey Jul 08 '12 at 14:03
  • 1
    Thanks thats great, I found similar posts but didnt grasp the idea of using WithMany() without the lambda expression to configure the relationship to be required:many without a navigation property on the other side of the relationship. Which makes more sense when I look at it as having the same author linked to multiple recipes. Thanks! wish I could + 1 again. – Pricey Jul 08 '12 at 16:01
  • This also makes me think that I should restructure things slightly and use an optional Owner flag in the "Cookbooks" relationship mapping table and do away with the Author property, but im not sure if you can add properties to a product of the Map() function. – Pricey Jul 08 '12 at 16:10
  • 1
    Your many-to-many table Cookbooks will not be payload-free anymore, you shall need different mapping then. For guidelines on determining whether you need a payload on many-to-many relationship or not, you can start from this answer: http://stackoverflow.com/questions/3168016/entity-framework-many-to-many-question/3168126#3168126 – Michael Buen Jul 08 '12 at 16:38
  • Thanks for the link to that question, I'm still not entirely sure what that means but looking at the best practice note, the point is made that an extra ID field should be used, I presume because Recipe & Member would reference the many-to-many in a different way. I have not fully tried out my update above yet, one thing I did notice is I would need a way to make sure each Recipe had at least 1 mandatory owner flag in the MembersRecipes table. Thinking I might stick with what I have got for now, even if the Author property feels redundant. – Pricey Jul 08 '12 at 17:26
  • Could you tell me if there is any way to have both approaches (from the question body) together. I.e., I need to have MembersRecipes class (with some additional properties) and also I would want also to have public virtual IList Members { get; set; } inside of Recipe (and vise versa)? Thank you! – Agat Jul 06 '13 at 13:27
  • This is an awesome answer! – Kevin Babcock Oct 22 '13 at 20:49
  • As regards UPDATE description about the cyclical reference I found a good clear answer that helps me understanding it a lot (in the context of relation of many to many). It is at the following link: http://social.msdn.microsoft.com/Forums/en-US/5ad31503-8336-4494-965d-28875bc37c1d/many-to-many-relationships-in-entity-framework-41-code-first?forum=adodotnetentityframework – Bronek Nov 10 '13 at 23:50
  • Is it possible to point the mapped table to a third model containing additional properties? – Shimmy Weitzhandler Jun 15 '14 at 03:12
  • If you copy pasted the OnModelCreating method like me, don't forget to add ``base.OnModelCreating(modelBuilder);`` – pomber Jun 01 '15 at 19:14
  • @pomber the base's OnModelCreating virtual method is empty https://social.msdn.microsoft.com/Forums/en-US/d233c6c0-cec9-4feb-82f5-20fbada776d4/code-first-should-we-always-call-the-base-after-overriding-seed-and-onmodelcreating?forum=adodotnetentityframework – Michael Buen Jun 02 '15 at 10:47
  • @MichaelBuen In my case I'm working with ``IdentityDbContext`` as the base class, but I think it is a good practice anyway – pomber Jun 02 '15 at 14:48
  • @MichaelBuen What happens if you have more columns in your associative table ? I know it doesn't make any sense, but its from DBA requirements – RPDeshaies Jun 12 '16 at 17:24