2012-08-30 28 views
13

我有一些代碼可以保存代碼中的多對多關係。它與Entity Framework 4.1一起工作良好,但在更新到Entity Framework 5之後,它失敗了。實體框架5升級後,多對多關係左右鍵翻轉

,我發現了以下錯誤:

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_WebUserFavouriteEvent_Event". The conflict occurred in database "MainEvents", table "dbo.Event", column 'Id'.

我使用POCO實體與自定義映射。標準字段和多對一關係映射似乎工作正常。

UPDATE

好了,我已經有了SQL事件探查器安裝,情節變稠...

exec sp_executesql N'insert [dbo].[WebUserFavouriteEvent]([WebUserId], [EventId]) 
values (@0, @1) 
',N'@0 int,@1 int',@0=1820,@1=14 

這意味着:

WebUserId = @0 = 1820 
EventId = @1 = 14 

有趣的是是EF5似乎已經翻轉了外鍵 ... WebUserId應該是14EventId應該是1820,而不是像現在這樣。

我審查了映射代碼,我99%我已經正確地設置了它。有關更多信息,請參閱Entity Framework Fluent API - Relationships MSDN article

注意:我也發現這不僅限於保存,SELECT也被打破。

這裏的所有相關代碼:

服務層

public void AddFavEvent(WebUser webUser, Event @event) 
{ 
    webUser.FavouriteEvents.Add(@event); 

    _webUserRepo.Update(webUser); 
} 

public void Update<T>(params T[] entities) 
    where T : DbTable 
{ 
    foreach (var entity in entities) 
    { 
     entity.UpdatedOn = DateTime.UtcNow; 
    } 

    _dbContext.SaveChanges(); 
} 

注:我使用的每個請求的方法1周的DataContext,所以webUser@event將從與_webUserRepo中相同的上下文中加載。

實體(不用擔心DBTABLE東西)

public class Event : DbTable 
{ 
    //BLAH 
    public virtual ICollection<WebUser> FavouriteOf { get; set; } 
    //BLAH 
} 

public class WebUser : DbTable 
{ 
    //BLAH 
    public virtual ICollection<Event> FavouriteEvents { get; set; } 
    //BLAH 
} 

映射

public class EventMapping : DbTableMapping<Event> 
{ 
    public EventMapping() 
    { 
     ToTable("Event"); 
     //BLAH 
     HasMany(x => x.FavouriteOf) 
      .WithMany(x => x.FavouriteEvents) 
      .Map(x => 
        { 
         x.MapLeftKey("EventId"); 
         x.MapRightKey("WebUserId"); 
         x.ToTable("WebUserFavouriteEvent"); 
        }); 
    } 
} 

public class WebUserMapping : DbTableMapping<WebUser> 
{ 
    public WebUserMapping() 
    { 
     HasMany(x => x.FavouriteEvents) 
      .WithMany(x => x.FavouriteOf) 
      .Map(m => 
        { 
         m.MapLeftKey("WebUserId"); 
         m.MapRightKey("EventId"); 
         m.ToTable("WebUserFavouriteEvent"); 
        }); 
    } 
} 
+0

我已經證實,回滾到EF 4.3.1修復了上述問題。因此,它肯定是在EF 5 – Charlino

+0

重大更改不「LeftKey」指的是在連接表的左/第一列,並且不「RightKey」指的是在連接表右/第二列?從這個角度來看,你的映射是矛盾的:你映射一個和相同的關係,但是在第一個映射中,你的左列被稱爲「EventId」,而第二個左列被稱爲「WebUserId」。我會責怪EF不會在矛盾的映射中首先拋出異常,但顯然它只是其中之一,EF版本之間的差異是不同的。只要擺脫這兩種映射之一,你只需要一個。 – Slauma

+0

@Slauma它如何矛盾?當我從**事件的角度映射,LeftKey應該是EVENTID ** ...然後當我從** WEBUSER的角度映射LeftKey應該是WebUserId **。我會考慮從Event和WebUser的角度對LeftKey(和RightKey)使用相同的值是一個矛盾......然後我期待一個例外。 – Charlino

回答

13

看着這個我懷疑問題可能是由你所映射的事實引起的兩次相同的關係。而且你以不同的順序映射它。

我做了一個簡單的測試,我首先映射關係的一次:

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"); 
      }); 

    } 
} 

然後,我改變了OnModelCreating是:

 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"); 
      }); 
    } 

我發現和懷疑的是,在第一次運行生成此sql:

exec sp_executesql N'insert [dbo].[ChildToParentMapping]([ChildId], [ParentId]) 
values (@0, @1) 
',N'@0 int,@1 int',@0=1,@1=2 

與第二個生成的對照相比:

exec sp_executesql N'insert [dbo].[ChildToParentMapping]([ParentId], [ChildId]) 
values (@0, @1) 
',N'@0 int,@1 int',@0=1,@1=2 

您看到翻轉的值?這裏它實際上將ChildId列作爲ParentId進行計數。現在這不會讓我崩潰,但我讓EF創建數據庫,這意味着它可能只是切換列名,如果我看看外鍵,它們也會被切換。如果您手動創建數據庫,可能不會是這種情況。

因此,在短期:你映射是不相等的,我希望使用其中一個,這一個也許是錯誤的。在較早的版本中,我猜EF是按照不同的順序挑選出來的。

更新: 我對外鍵有點好奇,並檢查了SQL。

從第一碼:

ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Children_ChildId] FOREIGN KEY ([ChildId]) REFERENCES [dbo].[Children] ([ChildId]) ON DELETE CASCADE 

從第二碼:

ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Children_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [dbo].[Children] ([ChildId]) ON DELETE CASCADE 

現在是不是很好。 ParentId映射對兒童肯定不是我們想要的。

所以是第二映射錯了嗎?不是因爲看到當我刪除第一個時發生了什麼:

ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Parents_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [dbo].[Parents] ([ParentId]) ON DELETE CASCADE 

不知怎的,有兩個映射似乎弄亂了事情。錯誤與否我不知道。

+0

有趣的是,我可能不得不更多地嘗試這個想法。我仍然不明白爲什麼映射它兩次會產生這種效果,以不同的順序映射它們兩次是完全有意的,做不同的事情是沒有意義的。簡而言之,如果你找到的是原因,我會將其視爲EF5中的一個錯誤。 – Charlino

+1

這對我來說似乎是一個錯誤。不確定是否有時間查看更新。但是,似乎有兩個映射EF被列名弄糊塗了。如果您(不與EF或EF沃爾德)手動創建數據庫,這將可能意味着它的映射正確 –

+0

很有意思,謝謝你這麼多投入的時間和做研究,我應該做的......肯定看起來像你已經找到了這個奇怪的行爲/錯誤的觸發器。 – Charlino

6

我可以證實這是一個EF5錯誤,但我仍然不知道它在4.3.1如何工作的。

的問題是,我們沒有正確的關聯LeftKey/RightKey與它們對應的導航性能要求。

我將在提起EF6錯誤在我們的CodePlex項目網站。

要解決,我想你會需要:

  1. 配置只從一側的關聯。或者,
  2. 將兩個配置中的LeftKey/RightKey列名稱切換爲相同。

很抱歉給您帶來不便。

UPDATE:這裏是bug

+0

謝謝安德魯,欣賞來自某個如此接近該項目的人的更新!:-)我擔心切換LeftKey/RightKey列在兩種配置中都是一樣的,因爲我/我們不太清楚這個bug是如何表現出來的。即如果因爲EF發現第一個映射而將關係存儲在內存中時發生錯誤,那麼當它找到第二個映射時,它就會改變該關係的鍵。如果我們要翻轉鍵以使它們相同,從技術上講,其中一個映射將是不正確的 - 如果EF首先遇到錯誤的映射,該錯誤可能會再次出現。 – Charlino

+0

然後配置關聯只有一次 - 實際上是使用Code First的首選方式,因爲它更幹,更高效。 –