2012-08-30 32 views
13

Tôi có một số mã tiết kiệm nhiều mối quan hệ trong mã. Nó hoạt động tốt với Entity Framework 4.1 nhưng sau khi cập nhật lên Entity Framework 5, nó không thành công.Mối quan hệ nhiều và nhiều bên trái và phím phải lộn ngược sau khi nâng cấp Entity Framework 5

Tôi nhận được lỗi sau:

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

Tôi đang sử dụng các đối tượng POCO với ánh xạ tùy chỉnh. Trường tiêu chuẩn và ánh xạ mối quan hệ nhiều-một có vẻ hoạt động tốt.

CẬP NHẬT

Ok, vì vậy tôi đã có SQL Profiler cài đặt và cốt truyện đã dày ...

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

Có nghĩa là:

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

Điều thú vị là là EF5 dường như đã lật các phím nước ngoài xung quanh ... WebUserId phải là 14EventId phải là 1820, không phải cách khác xung quanh như bây giờ.

Tôi đã xem xét mã bản đồ và tôi 99% tôi đã thiết lập chính xác tất cả. Xem Entity Framework Fluent API - Relationships MSDN article để biết thêm thông tin.

LƯU Ý: Tôi cũng nhận thấy rằng điều này không bị hạn chế để lưu hoặc, các lệnh SELECT cũng bị hỏng.

Dưới đây là tất cả các mã liên quan:

Dịch vụ Lớp

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

    _webUserRepo.Update(webUser); 
} 

Repository

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

    _dbContext.SaveChanges(); 
} 

LƯU Ý: Tôi đang sử dụng 1 DataContext mỗi cách tiếp cận theo yêu cầu, số webUser@event sẽ được tải từ cùng một ngữ cảnh giống như trong _webUserRepo.

Entities (đừng lo lắng về DbTable thứ)

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 
} 

Mappings

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

Tôi đã xác nhận rằng việc quay lại EF 4.3.1 sẽ khắc phục được vấn đề trên. Vì vậy, nó chắc chắn là một thay đổi phá vỡ trong EF 5. – Charlino

+0

Không "LeftKey" có nghĩa là cột bên trái/đầu tiên trong bảng nối, và không "RightKey" có nghĩa là cột phải/thứ hai trong bảng nối? Từ quan điểm đó, ánh xạ của bạn mâu thuẫn: Bạn ánh xạ một và cùng một mối quan hệ, nhưng trong ánh xạ đầu tiên cột bên trái của bạn được gọi là 'EventId' và cột thứ hai bên trái được gọi là' WebUserId'. Tôi sẽ đổ lỗi cho EF không ném một ngoại lệ ở nơi đầu tiên về bản đồ mâu thuẫn, nhưng dường như nó chỉ mất một trong số họ và cái này khác nhau giữa các phiên bản EF. Chỉ cần loại bỏ một trong hai ánh xạ, bạn chỉ cần một. – Slauma

+0

@Slauma Làm thế nào nó mâu thuẫn? Khi tôi lập bản đồ từ phối cảnh của một sự kiện **, phím trái phải là EventId ** ... thì khi tôi ánh xạ từ phối cảnh của một ** WebUser thì LeftKey phải là WebUserId **. Tôi sẽ xem xét việc sử dụng cùng một giá trị cho LeftKey (và RightKey cho rằng vấn đề) từ cả hai sự kiện và phối cảnh WebUser một mâu thuẫn ... sau đó tôi mong đợi một ngoại lệ. – Charlino

Trả lời

13

Nhìn này, tôi nghi ngờ rằng vấn đề có thể được gây ra bởi thực tế là bạn ánh xạ cùng một mối quan hệ hai lần. Và bạn lập bản đồ theo thứ tự khác nhau.

tôi đã thực hiện một thử nghiệm đơn giản mà tôi lần đầu tiên vẽ bản đồ các mối quan hệ một lần:

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

    } 
} 

Và sau đó tôi đã thay đổi OnModelCreating là:

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

Những gì tôi thấy và bị nghi ngờ là thời gian đầu tiên tạo sql này:

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

Tương ứng với giây tạo ra:

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

Bạn thấy các giá trị được lật? Ở đây nó thực sự đếm cột ChildId là ParentId. Bây giờ điều này không sụp đổ cho tôi nhưng tôi cho phép EF tạo ra cơ sở dữ liệu có nghĩa là nó có thể chỉ chuyển đổi tên cột và nếu tôi nhìn vào các khóa ngoại, chúng cũng sẽ được chuyển đổi. Nếu bạn đã tạo cơ sở dữ liệu theo cách thủ công thì có thể sẽ không xảy ra.

Vì vậy, trong ngắn hạn: Ánh xạ của bạn không bằng nhau và tôi mong đợi một trong số chúng được sử dụng và đó có thể là sai. Trong các phần trước, tôi đoán EF đã chọn chúng theo thứ tự khác nhau.

CẬP NHẬT: Tôi có chút tò mò về khóa ngoại và đã kiểm tra sql.

Từ mã đầu tiên:

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

Và từ mã thứ hai:

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

Bây giờ không phải là tốt đẹp. ParentId ánh xạ chống lại trẻ em chắc chắn không phải là những gì chúng tôi muốn.

Vậy ánh xạ thứ hai là sai? Không thực sự bởi vì xem điều gì sẽ xảy ra khi tôi xóa hình ảnh đầu tiên:

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

Bằng cách nào đó có hai ánh xạ dường như làm mọi thứ lộn xộn. Lỗi hay không tôi không biết.

+0

Thú vị, tôi có thể phải thử nghiệm ý tưởng này nhiều hơn. Tôi vẫn không hiểu tại sao lập bản đồ nó hai lần sẽ có hiệu ứng này, lập bản đồ chúng hai lần trong các đơn đặt hàng khác nhau là hoàn toàn có mục đích, làm cho nó khác nhau sẽ không có ý nghĩa. Tóm lại, nếu những gì bạn đã tìm thấy là nguyên nhân, tôi sẽ xem đây là một lỗi trong EF5. – Charlino

+1

Nó có vẻ như là một lỗi đối với tôi. Bạn không chắc chắn liệu mình có thời gian để xem bản cập nhật hay không. Nhưng có vẻ như khi có hai ánh xạ EF bị lẫn lộn bởi các tên cột. Nếu bạn tạo cơ sở dữ liệu theo cách thủ công (Không phải EF hoặc EF vOld), điều này có thể có nghĩa là nó được ánh xạ không chính xác –

+0

Rất thú vị, cảm ơn bạn rất nhiều vì đã dành thời gian và thực hiện nghiên cứu mà tôi nên làm ... đã tìm thấy kích hoạt cho hành vi/lỗi kỳ lạ này. – Charlino

6

Tôi có thể xác nhận đây là lỗi trong EF5, mặc dù tôi vẫn không chắc chắn cách thức hoạt động trong 4.3.1.

Vấn đề là chúng tôi không kết hợp chính xác các cuộc gọi LeftKey/RightKey với các thuộc tính điều hướng tương ứng của chúng.

Tôi sẽ gửi lỗi EF6 trên trang web dự án CodePlex của chúng tôi.

Để workaround, tôi nghĩ rằng bạn sẽ cần một trong hai:

  1. Cấu hình liên kết khỏi chỉ có một bên. Hoặc,
  2. Chuyển đổi các tên cột LeftKey/RightKey thành giống nhau trong cả hai cấu hình.

Xin lỗi vì sự bất tiện này.

CẬP NHẬT: Đây là bug.

+0

Cảm ơn Andrew, đánh giá cao việc cập nhật từ một người nào đó gần với dự án!:-) Tôi e ngại để chuyển đổi các cột LeftKey/RightKey thành giống nhau trong cả hai cấu hình vì tôi/chúng tôi không biết chính xác lỗi này thể hiện như thế nào. I E. nếu lỗi xảy ra vì khi EF tìm thấy ánh xạ đầu tiên, nó lưu trữ mối quan hệ trong bộ nhớ, sau đó khi nó tìm thấy ánh xạ thứ hai nó sẽ thay đổi các khóa cho mối quan hệ đó. Nếu chúng ta lật các phím sao cho chúng giống nhau, về mặt kỹ thuật, một trong những ánh xạ đó sẽ không chính xác - lỗi có thể xuất hiện trở lại nếu EF đi qua sơ đồ sai trước. – Charlino

+0

Sau đó cấu hình liên kết chỉ một lần là con đường để đi - Và thực sự là cách ưa thích của việc sử dụng Code First vì nó nhiều DRY và cũng hiệu quả hơn. –

Các vấn đề liên quan