I have some code that saves a many to many relationship in code. It was working fine with Entity Framework 4.1 but after updating to Entity Framework 5, it\'s failing.
I can confirm this is a bug in EF5, although I'm still not sure how it worked in 4.3.1.
The problem is that we are not correctly associating the LeftKey/RightKey calls with their corresponding navigation properties.
I will file an EF6 bug over on our CodePlex project site.
To workaround, I think you will need to either:
Sorry for the inconvenience.
UPDATE: Here is the bug.
Looking at this I suspect that the problem might be caused by the fact that you map the same relationship twice. And you map it in different order.
I made a simple test where I first mapped the relationship once:
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
var p = new Parent();
var c = new Child();
using (var db = new Context())
{
db.Parents.Add(new Parent());
db.Parents.Add(p);
db.Children.Add(c);
db.SaveChanges();
}
using (var db = new Context())
{
var reloadedP = db.Parents.Find(p.ParentId);
var reloadedC = db.Children.Find(c.ChildId);
reloadedP.Children = new List<Child>();
reloadedP.Children.Add(reloadedC);
db.SaveChanges();
}
using (var db = new Context())
{
Console.WriteLine(db.Children.Count());
Console.WriteLine(db.Children.Where(ch => ch.ChildId == c.ChildId).Select(ch => ch.Parents.Count).First());
Console.WriteLine(db.Parents.Where(pa => pa.ParentId == p.ParentId).Select(pa => pa.Children.Count).First());
}
}
}
public class Parent
{
public int ParentId { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int ChildId { get; set; }
public ICollection<Parent> Parents { get; set; }
}
public class Context : DbContext
{
public Context() : base("data source=Mikael-PC;Integrated Security=SSPI;Initial Catalog=EFTest")
{
}
public IDbSet<Child> Children { get; set; }
public IDbSet<Parent> Parents { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Child>()
.HasMany(x => x.Parents)
.WithMany(x => x.Children)
.Map(c =>
{
c.MapLeftKey("ChildId");
c.MapRightKey("ParentId");
c.ToTable("ChildToParentMapping");
});
}
}
And then I changed the OnModelCreating to be:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Child>()
.HasMany(x => x.Parents)
.WithMany(x => x.Children)
.Map(c =>
{
c.MapLeftKey("ChildId");
c.MapRightKey("ParentId");
c.ToTable("ChildToParentMapping");
});
modelBuilder.Entity<Parent>()
.HasMany(x => x.Children)
.WithMany(x => x.Parents)
.Map(c =>
{
c.MapLeftKey("ParentId");
c.MapRightKey("ChildId");
c.ToTable("ChildToParentMapping");
});
}
What I found and suspected is that the first run generates this sql:
exec sp_executesql N'insert [dbo].[ChildToParentMapping]([ChildId], [ParentId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2
In constrast to the second which generates:
exec sp_executesql N'insert [dbo].[ChildToParentMapping]([ParentId], [ChildId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2
You see the values flipped? Here it actually count the ChildId column as ParentId. Now this doesn't crash for me but I let EF create the database which means it probably just switch the column names and if I would look at the foreign keys they would be switched too. If you created the database manually that probably won't be the case.
So in short: You mappings aren't equal and I expect one of them to be used and that one is probably wrong. In earlier verions I guess EF picked them up in different order.
UPDATE: I got a bit curious about the foreign keys and checked the sql.
From the first code:
ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Children_ChildId] FOREIGN KEY ([ChildId]) REFERENCES [dbo].[Children] ([ChildId]) ON DELETE CASCADE
And from the second code:
ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Children_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [dbo].[Children] ([ChildId]) ON DELETE CASCADE
Now that is not nice. ParentId mapped against Children is certainly not what we want.
So is the second mapping wrong? Not really because see what happend when I removed the first one:
ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Parents_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [dbo].[Parents] ([ParentId]) ON DELETE CASCADE
Somehow having two mappings seems to mess things up. Bug or not I don't know.