35

Below is my model:

public class TMUrl
{
    //many other properties

    //only property with type Keyword
    public List<Keyword> Keywords{get;set;} 
}

public class Keyword
{
   //many other properties

   //only property with type TMUrl
   public List<TMUrl> Urls{get;set;}
}

So clearly, both the entities have many-to-many relationship. I chose fluent api to tell the entity-framework about this relationship i.e.

modelBuilder.Entity<TMUrl>
               .HasMany(s => s.Keywords)
               .WithMany(s => s.URLs).Map(s =>
                {
                    s.MapLeftKey("KeywordId");
                    s.MapRightKey("UrlId");
                    s.ToTable("KeywordUrlMapping");
                });

but when I do

url.Keywords.Add(dbKey); //where url is object of TMUrl, 
                         //dbKey is an existing/new object of Keyword
db.SaveChanges();

I get exception

An error occurred while saving entities that do not expose foreign key 
properties for their relationships....

InnerException:

The INSERT statement conflicted with the FOREIGN KEY constraint   
"KeywordMaster_Keyword". The conflict occurred in database "DbName", 
table "dbo.KeywordMaster", column 'Id'.The statement has been terminated.

but when I add Configuration from the otherside aswell, everything works fine. i.e.

modelBuilder.Entity<KeyWord>
         .HasMany(s => s.URLs)
         .WithMany(s => s.Keywords)
         .Map(s =>
               {
                  s.MapLeftKey("KeywordId");
                  s.MapRightKey("UrlId");
                  s.ToTable("KeywordUrlMapping");
               });

Why?. Why I've to add configuration from both the entities, where I've read here and many other places, configuration for one of the entities should do.

What is the case, when I should add configuration for both of the entities involved in the relationship?

I need to understand this. Why. Please help.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Manish Mishra
  • 12,163
  • 5
  • 35
  • 59
  • "*See the InnerException for details*". Did you? For this rarely useful exception the inner exception is really important to know. – Slauma May 10 '13 at 21:16
  • @Slauma, I've updated the question with the inner exception, but do notice that adding relationship from both side makes it work. – Manish Mishra May 10 '13 at 21:43
  • Interesting question would be if *only* the second mapping (without the first mapping) would make it work. If yes, I'd suspect that the column `KeywordId` in the mapping table is actually the foreign key to table `URLs` and not to `Keywords`. (Should only be possible if this is an existing DB with manually created relationships, not if you created the DB with Code-First.) – Slauma May 10 '13 at 21:50
  • No @Slauma, I checked the fk_relation and KeywordId is mapped to Id of KeywordMaster. Am not sure why is it happening; Sometimes it just works; Sometimes it just won't. One more thing, I've a static single context. I hope that is not causing all this – Manish Mishra May 10 '13 at 22:13
  • Then, I think, the first mapping is incorrect and you only need the second. Keep in mind they are not the same: With the first mapping you tell EF that the FK from mapping table to configured enity (=`TMUrl`) is called `KeywordId`. Based on this EF will write the key value of `url` (not `dbKey`) into that column which is wrong if the FK constraint requires a keywordid in that column. With the second mapping it is the other way around - probably the correct way. It might sometimes apparently work (as you say) if a `url` id is stored that accidentally has the same value as an existing keywordid. – Slauma May 10 '13 at 22:40
  • ok @Slauma, so you're saying, left will be the key of first class I pointed to which is `TMUrl` in the first mapping. Okay. That makes sense. Actually I created many of the tables manually and thought `Left` simply meant left column of the table so I created correct FK but EF links it otherwise. Wow, it has to be this. I will test it a bit further. Thanks for your help, built my entire product based on the clear answers you provided on ef overhere :) – Manish Mishra May 10 '13 at 23:02
  • 3
    Yes, the terms `Left` and `Right` are confusing here because they seem to indicate that they would have to do with the column order in the table. Perhaps `MapFirstEntityKey` and `MapSecondEntityKey` would have been clearer to indicate they actually refer to the entity order in the mapping. Of course if EF creates the DB itself `First` and `Left` matches. But if you do it manually, the order can be just reverse. – Slauma May 10 '13 at 23:13
  • Hey @Slauma, put this into an answer! I like the names you coined. Maybe the EF team will adopt them! – Gert Arnold May 11 '13 at 14:52
  • @GertArnold: I've done it now (but it was getting a bit too lengthy...) – Slauma May 11 '13 at 20:22

1 Answers1

116

The terms Left and Right in MapLeftKey and MapRightKey in the many-to-many mapping with Fluent API can be misunderstood and I guess your problem is caused by this misunderstanding.

One might think that it means they describe the columns that are "left" and "right" in the many-to-many join table. That's actually the case if you let EF Code-First create the database and join table based on your Fluent mapping.

But it's not necessarily the case when you create a mapping to an existing database.

To illustrate this with the prototypic many-to-many example of a User-Role model assume you have an existing database with a Users, Roles and RoleUsers table:

Many-to-many database tables

Now, you want to map this table schema to a simple model:

public class User
{
    public User()
    {
        Roles = new List<Role>();
    }

    public int UserId { get; set; }
    public string UserName { get; set; }
    public ICollection<Role> Roles { get; set; }
}

public class Role
{
    public int RoleId { get; set; }
    public string RoleName { get; set; }
}

And you add the Fluent mapping for the Users entity (you must do it this way, because by convention the model above would be one-to-many and you can't start from the Role entity side because it has no Users collection):

modelBuilder.Entity<User>()
    .HasMany(u => u.Roles)
    .WithMany()
    .Map(m =>
    {
        m.MapLeftKey("RoleId");  // because it is the "left" column, isn't it?
        m.MapRightKey("UserId"); // because it is the "right" column, isn't it?
        m.ToTable("RoleUsers");
    });

This mapping is wrong and if you try to put "Anna" into role "Marketing"...

var anna = ctx.Users.Find(1);
var marketing = ctx.Roles.Find(2);

anna.Roles.Add(marketing);

ctx.SaveChanges();

...SaveChanges will throw exactly the exception you are having. The reason becomes clear when you capture the SQL command that is sent with SaveChanges:

exec sp_executesql N'insert [dbo].[RoleUsers]([RoleId], [UserId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2

So, EF wants to insert here a row into the join table RoleUsers with a RoleId of 1 and a UserId of 2 which is causing the foreign key constraint violation because there is no user with UserId 2 in the Users table.

In other words, the mapping above has configured the column RoleId as the foreign key to table Users and the column UserId as the foreign key to table Roles. In order to correct the mapping we have to use the "left" column name in the join table in MapRightKey and the "right" column in MapLeftKey:

        m.MapLeftKey("UserId");
        m.MapRightKey("RoleId");

Actually looking at Intellisense the description makes it clearer what "Left" and "Right" really mean:

MapLeftKey

Configures the name of the column(s) for the left foreign key. The left foreign key represents the navigation property specified in the HasMany call.

MapRightKey

Configures the name of the column(s) for the right foreign key. The right foreign key represents the navigation property specified in the WithMany call.

So, "Left" and "Right" refer to the order in which the entities appear in the Fluent mapping, not to the column order in the join table. The order in the table actually doesn't matter, you can change it without breaking anything because the INSERT sent by EF is an "extended" INSERT that also contains the column names and not only the values.

Perhaps MapFirstEntityKey and MapSecondEntityKey would have been a less misleading choice of those method names - or maybe MapSourceEntityKey and MapTargetEntityKey.

This was a long post about two words.

If my guess is right that it has anything to do with your problem at all then I would say that your first mapping is incorrect and that you only need the second and correct mapping.

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • 4
    +1 for the deep discussion and explanation. Took me about 20 or so Debug stack trace views to figure out what you explained in a long post. – GoldBishop Dec 28 '13 at 03:08
  • as in my expirience, it is just the oposit, the LeftKey is for the WithMany cell, and the RightKey is for the HasMany cell. also mentiond here: https://msdn.microsoft.com/en-us/data/hh134698.aspx – IFink Mar 08 '16 at 12:12
  • 1
    Naming the method names MapSourceEntityKey and MapTargetEntityKey for the MapLeftKey and MapRightKey explained everything! you could only write those two words to explain everything – Tinaira Sep 24 '16 at 12:33
  • I'm sorry to say but this just is not true. Its more like being random: I have a model where you can't find a rule whether MapLeftKey relates to HasMany() or WithMany(), both variants occur (and N-O-N-E of the many-many relations are defined from both sides) and the only way to find out seems by proper unit tests (testing with fresh data where each side has the primary key "1" is NOT proper). – springy76 Dec 20 '16 at 12:03
  • 1
    The description of MapLeftKey should be "The left foreign key points to the parent entity of the navigation property specified in the HasMany call.", and MapRightKey should be "The right foreign key points to the parent entity of the the navigation property specified in the WithMany call." – Carlos Liu Apr 06 '17 at 05:36