Cập nhật mối quan hệ khi lưu các thay đổi của đối tượng EF4 POCO


107

Entity Framework 4, các đối tượng POCO và ASP.Net MVC2. Tôi có nhiều mối quan hệ, hãy nói giữa các thực thể BlogPost và Tag. Điều này có nghĩa là trong lớp POCO BlogPost được tạo T4 của tôi, tôi có:

public virtual ICollection<Tag> Tags {
    // getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;

Tôi yêu cầu một BlogPost và các Thẻ liên quan từ một phiên bản của ObjectContext và gửi nó đến một lớp khác (Xem trong ứng dụng MVC). Sau đó, tôi lấy lại BlogPost đã cập nhật với các thuộc tính đã thay đổi và các mối quan hệ đã thay đổi. Ví dụ: nó có thẻ "A" "B" và "C", và các thẻ mới là "C" và "D". Trong ví dụ cụ thể của tôi không có Thẻ mới và các thuộc tính của Thẻ không bao giờ thay đổi, vì vậy điều duy nhất nên được lưu là các mối quan hệ đã thay đổi. Bây giờ tôi cần lưu nó trong một ObjectContext khác. (Cập nhật: Bây giờ tôi đã cố gắng thực hiện trong cùng một trường hợp ngữ cảnh và cũng không thành công.)

Vấn đề: Tôi không thể làm cho nó cứu vãn các mối quan hệ đúng cách. Tôi đã thử mọi thứ tôi tìm thấy:

  • Controller.UpdateModel và Controller.TryUpdateModel không hoạt động.
  • Lấy BlogPost cũ từ ngữ cảnh sau đó sửa đổi bộ sưu tập không hoạt động. (với các phương pháp khác nhau từ điểm tiếp theo)
  • Điều này có thể sẽ hiệu quả, nhưng tôi hy vọng đây chỉ là một giải pháp thay thế, không phải là giải pháp :(.
  • Đã thử các chức năng Đính kèm / Thêm / ChangeObjectState cho BlogPost và / hoặc Thẻ trong mọi kết hợp có thể. Thất bại.
  • Điều này trông giống như những gì tôi cần, nhưng nó không hoạt động (Tôi đã cố gắng khắc phục nó, nhưng không thể cho sự cố của tôi).
  • Đã thử ChangeState / Add / Attach / ... các đối tượng quan hệ của ngữ cảnh. Thất bại.

"Không hoạt động" có nghĩa là trong hầu hết các trường hợp, tôi đã làm việc trên "giải pháp" đã cho cho đến khi nó không tạo ra lỗi và lưu ít nhất các thuộc tính của BlogPost. Điều gì xảy ra với các mối quan hệ sẽ khác nhau: thông thường Thẻ được thêm lại vào bảng Thẻ với PK mới và BlogPost đã lưu tham chiếu những thẻ đó chứ không phải những thẻ ban đầu. Tất nhiên các Thẻ trả về có PK và trước khi lưu / cập nhật phương pháp tôi kiểm tra PK và chúng bằng với các PK trong cơ sở dữ liệu nên có thể EF nghĩ rằng chúng là đối tượng mới và PK đó là đối tượng tạm thời.

Một vấn đề mà tôi biết và có thể khiến không thể tìm ra giải pháp đơn giản tự động: Khi bộ sưu tập của đối tượng POCO bị thay đổi, điều đó sẽ xảy ra bởi thuộc tính bộ sưu tập ảo đã đề cập ở trên, vì khi đó thủ thuật FixupCollection sẽ cập nhật các tham chiếu ngược ở đầu bên kia của mối quan hệ nhiều-nhiều. Tuy nhiên, khi Chế độ xem "trả về" đối tượng BlogPost đã cập nhật, điều đó đã không xảy ra. Điều này có nghĩa là có thể không có giải pháp đơn giản nào cho vấn đề của tôi, nhưng điều đó sẽ khiến tôi rất buồn và tôi sẽ ghét chiến thắng EF4-POCO-MVC :(. Điều đó cũng có nghĩa là EF không thể làm điều này trong môi trường MVC, bất kỳ điều gì Các loại đối tượng EF4 được sử dụng :(. Tôi nghĩ rằng theo dõi thay đổi dựa trên ảnh chụp nhanh sẽ phát hiện ra rằng BlogPost đã thay đổi có mối quan hệ với Thẻ với PK hiện có.

Btw: Tôi nghĩ vấn đề tương tự cũng xảy ra với quan hệ một-nhiều (google và đồng nghiệp của tôi nói vậy). Tôi sẽ thử nó ở nhà, nhưng ngay cả khi điều đó không hiệu quả với tôi trong sáu mối quan hệ nhiều-nhiều trong ứng dụng của tôi :(.


Vui lòng đăng mã của bạn. Đây là một kịch bản phổ biến.
John Farrell

1
Tôi có một giải pháp tự động cho vấn đề này, nó ẩn trong các câu trả lời dưới đây rất nhiều sẽ bỏ lỡ nó nhưng xin hãy xem vì nó sẽ giúp bạn tiết kiệm một địa ngục của một công việc bài xem tại đây
brentmckendrick

@brentmckendrick Tôi nghĩ cách tiếp cận khác tốt hơn. Thay vì gửi toàn bộ đồ thị đối tượng đã sửa đổi qua dây, tại sao không chỉ gửi delta? Bạn thậm chí sẽ không cần các lớp DTO được tạo trong trường hợp đó. Nếu bạn có ý kiến ​​về cách này, hãy thảo luận tại stackoverflow.com/questions/1344066/calculate-object-delta .
HappyNomad

Câu trả lời:


145

Hãy thử theo cách này:

  • Đính kèm BlogPost vào ngữ cảnh. Sau khi gắn đối tượng vào ngữ cảnh, trạng thái của đối tượng, tất cả các đối tượng liên quan và tất cả các quan hệ được đặt thành Không thay đổi.
  • Sử dụng context.ObjectStateManager.ChangeObjectState để đặt BlogPost của bạn thành Đã sửa đổi
  • Lặp lại qua bộ sưu tập Thẻ
  • Sử dụng context.ObjectStateManager.ChangeRelationshipState để đặt trạng thái cho mối quan hệ giữa Thẻ hiện tại và BlogPost.
  • Lưu thay đổi

Biên tập:

Tôi đoán một trong những nhận xét của tôi đã cho bạn hy vọng sai lầm rằng EF sẽ thực hiện việc hợp nhất cho bạn. Tôi đã chơi rất nhiều với vấn đề này và kết luận của tôi nói rằng EF sẽ không làm điều này cho bạn. Tôi nghĩ bạn cũng đã tìm thấy câu hỏi của tôi trên MSDN . Trong thực tế, có rất nhiều câu hỏi như vậy trên Internet. Vấn đề là nó không được nói rõ ràng làm thế nào để đối phó với kịch bản này. Vì vậy, chúng ta hãy xem xét vấn đề:

Bối cảnh vấn đề

EF cần theo dõi các thay đổi trên các thực thể để tính liên tục biết bản ghi nào phải được cập nhật, chèn hoặc xóa. Vấn đề là ObjectContext có trách nhiệm theo dõi các thay đổi. ObjectContext chỉ có thể theo dõi các thay đổi đối với các thực thể đính kèm. Các thực thể được tạo bên ngoài ObjectContext hoàn toàn không được theo dõi.

Mô tả vấn đề

Dựa trên mô tả ở trên, chúng ta có thể nói rõ rằng EF phù hợp hơn với các tình huống được kết nối nơi thực thể luôn được gắn với ngữ cảnh - điển hình cho ứng dụng WinForm. Các ứng dụng web yêu cầu kịch bản ngắt kết nối trong đó ngữ cảnh được đóng lại sau khi xử lý yêu cầu và nội dung thực thể được chuyển dưới dạng phản hồi HTTP cho máy khách. Yêu cầu HTTP tiếp theo cung cấp nội dung đã sửa đổi của thực thể phải được tạo lại, gắn với ngữ cảnh mới và tồn tại. Việc giải trí thường xảy ra bên ngoài phạm vi ngữ cảnh (kiến trúc phân lớp với sự bỏ qua bền bỉ).

Giải pháp

Vậy làm thế nào để đối phó với kịch bản mất kết nối như vậy? Khi sử dụng các lớp POCO, chúng ta có 3 cách để xử lý theo dõi thay đổi:

  • Ảnh chụp nhanh - yêu cầu cùng ngữ cảnh = vô dụng đối với trường hợp bị ngắt kết nối
  • Proxy theo dõi động - yêu cầu cùng ngữ cảnh = vô dụng đối với trường hợp bị ngắt kết nối
  • Đồng bộ thủ công.

Đồng bộ hóa thủ công trên một thực thể là một nhiệm vụ dễ dàng. Bạn chỉ cần đính kèm thực thể và gọi AddObject để chèn, DeleteObject để xóa hoặc đặt trạng thái trong ObjectStateManager thành Modified để cập nhật. Nỗi đau thực sự xảy đến khi bạn phải xử lý đồ thị đối tượng thay vì thực thể đơn lẻ. Nỗi đau này thậm chí còn tồi tệ hơn khi bạn phải đối mặt với các hiệp hội độc lập (những hiệp hội không sử dụng tài sản Khoá ngoại) và nhiều mối quan hệ. Trong trường hợp đó, bạn phải đồng bộ hóa thủ công từng thực thể trong biểu đồ đối tượng mà còn từng quan hệ trong biểu đồ đối tượng.

Đồng bộ hóa thủ công được đề xuất như một giải pháp bởi tài liệu MSDN: Đính kèm và tách các đối tượng cho biết:

Các đối tượng được gắn vào ngữ cảnh đối tượng ở trạng thái Không thay đổi. Nếu bạn cần thay đổi trạng thái của một đối tượng hoặc mối quan hệ vì bạn biết rằng đối tượng của mình đã được sửa đổi ở trạng thái tách rời, hãy sử dụng một trong các phương pháp sau.

Các phương pháp được đề cập là ChangeObjectState và ChangeRelationshipState của ObjectStateManager = theo dõi thay đổi thủ công. Đề xuất tương tự nằm trong bài viết tài liệu MSDN khác: Xác định và Quản lý các mối quan hệ cho biết:

Nếu bạn đang làm việc với các đối tượng bị ngắt kết nối, bạn phải quản lý việc đồng bộ hóa theo cách thủ công.

Hơn nữa, có bài đăng trên blog liên quan đến EF v1 chỉ trích chính xác hành vi này của EF.

Lý do cho giải pháp

EF có nhiều hoạt động và cài đặt "hữu ích" như Refresh , Load , ApplyCurrentValues , ApplyOriginalValues , MergeOption , v.v. Nhưng theo điều tra của tôi, tất cả các tính năng này chỉ hoạt động cho một thực thể và chỉ ảnh hưởng đến các ưu tiên vô hướng (= không phải thuộc tính và quan hệ điều hướng). Tôi không nên thử nghiệm phương pháp này với các kiểu phức tạp được lồng trong thực thể.

Giải pháp đề xuất khác

Thay vì chức năng Hợp nhất thực sự, nhóm EF cung cấp một thứ gọi là Thực thể Tự theo dõi (STE) không giải quyết được vấn đề. Trước hết STE chỉ hoạt động nếu cùng một phiên bản được sử dụng cho toàn bộ quá trình xử lý. Trong ứng dụng web, nó không phải là trường hợp trừ khi bạn lưu trữ phiên bản ở trạng thái xem hoặc phiên. Do đó, tôi rất không hài lòng khi sử dụng EF và tôi sẽ kiểm tra các tính năng của NHibernate. Quan sát đầu tiên nói rằng NHibernate có lẽ có chức năng như vậy .

Phần kết luận

Tôi sẽ kết thúc giả định này bằng một liên kết duy nhất đến một câu hỏi liên quan khác trên diễn đàn MSDN. Kiểm tra câu trả lời của Zeeshan Hirani. Ông là tác giả của Entity Framework 4.0 Recipes . Nếu anh ấy nói rằng việc hợp nhất tự động các đồ thị đối tượng không được hỗ trợ, tôi tin anh ấy.

Nhưng vẫn có khả năng là tôi hoàn toàn sai và một số chức năng hợp nhất tự động tồn tại trong EF.

Chỉnh sửa 2:

Như bạn có thể thấy điều này đã được thêm vào MS Connect dưới dạng đề xuất vào năm 2007. MS đã đóng nó như một điều gì đó sẽ được thực hiện trong phiên bản tiếp theo nhưng thực sự không có gì được thực hiện để cải thiện khoảng cách này ngoại trừ STE.


7
Đây là một trong những câu trả lời hay nhất mà tôi đã đọc trên SO. Bạn đã trình bày rõ ràng điều mà rất nhiều bài báo MSDN, tài liệu và các bài đăng trên blog về chủ đề này không thể vượt qua được. EF4 vốn không hỗ trợ cập nhật các mối quan hệ từ các thực thể "tách rời". Nó chỉ cung cấp các công cụ để bạn tự thực hiện. Cảm ơn bạn!
tyriker

1
Vậy sau vài tháng qua, NHibernate liên quan đến vấn đề này như thế nào so với EF4?
CallMeLaNN

1
Điều này được hỗ trợ rất tốt trong NHibernate :-) không cần phải hợp nhất theo cách thủ công, trong ví dụ của tôi, đó là biểu đồ đối tượng sâu 3 cấp, câu hỏi có câu trả lời, mỗi câu trả lời có nhận xét và câu hỏi cũng có nhận xét. NHibernate có thể tồn tại / hợp nhất biểu đồ đối tượng của bạn, bất kể nó phức tạp đến mức nào ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html Một người dùng NHibernate hài lòng khác: codinginstinct.com/2009/11/…
Michael Buen

2
Một trong những lời giải thích hay nhất mà tôi từng đọc !! Thanks a lot
marvelTracker

2
Nhóm EF có kế hoạch giải quyết vấn đề này sau EF6. Bạn có thể muốn bỏ phiếu cho entityframework.codeplex.com/workitem/864
Eric J.

19

Tôi có một giải pháp cho vấn đề đã được Ladislav mô tả ở trên. Tôi đã tạo một phương thức mở rộng cho DbContext, phương thức này sẽ tự động thực hiện thêm / cập nhật / xóa dựa trên sự khác biệt của biểu đồ được cung cấp và biểu đồ liên tục.

Hiện tại bằng cách sử dụng Entity Framework, bạn sẽ cần phải thực hiện cập nhật các liên hệ theo cách thủ công, kiểm tra xem từng liên hệ có mới và thêm hay không, kiểm tra xem đã cập nhật và chỉnh sửa, kiểm tra nếu bị xóa thì xóa nó khỏi cơ sở dữ liệu. Một khi bạn phải làm điều này cho một số tổng hợp khác nhau trong một hệ thống lớn, bạn bắt đầu nhận ra rằng phải có một cách tốt hơn, chung chung hơn.

Vui lòng xem và xem nó có giúp ích được gì không http://refactorthis.wordpress.com/2012/12/11/introductioning-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a- đồ thị của các thực thể tách rời /

Bạn có thể truy cập thẳng vào mã tại đây https://github.com/refactorthis/GraphDiff


Tôi chắc rằng bạn có thể giải quyết câu hỏi này một cách dễ dàng, tôi đang gặp khó khăn với nó.
Shimmy Weitzhandler

1
Chào Shimmy, xin lỗi cuối cùng cũng có chút thời gian để xem qua. Tôi sẽ xem xét nó tối nay.
brentmckendrick, 13/07/13

Thư viện này thật tuyệt và đã tiết kiệm cho tôi rất nhiều thời gian! Cám ơn!
Lordjeb

9

Tôi biết là đã muộn cho OP nhưng vì đây là một vấn đề rất phổ biến nên tôi đã đăng bài này đề phòng nó phục vụ người khác. Tôi đã xoay quanh vấn đề này và tôi nghĩ rằng tôi có một giải pháp khá đơn giản, những gì tôi làm là:

  1. Lưu đối tượng chính (ví dụ: Blog) bằng cách đặt trạng thái của nó thành Đã sửa đổi.
  2. Truy vấn cơ sở dữ liệu cho đối tượng được cập nhật bao gồm các bộ sưu tập tôi cần cập nhật.
  3. Truy vấn và chuyển đổi .ToList () các thực thể mà tôi muốn bộ sưu tập của mình bao gồm.
  4. Cập nhật (các) bộ sưu tập của đối tượng chính vào Danh sách mà tôi nhận được từ bước 3.
  5. Lưu thay đổi();

Trong ví dụ sau "dataobj" và "_categories" là các tham số mà bộ điều khiển của tôi nhận được "dataobj" là đối tượng chính của tôi và "_categories" là IEnumerable chứa ID của các danh mục mà người dùng đã chọn trong chế độ xem.

    db.Entry(dataobj).State = EntityState.Modified;
    db.SaveChanges();
    dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id);
    var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null;
    dataobj.Categories = it;
    db.SaveChanges();

Nó thậm chí hoạt động cho nhiều mối quan hệ


7

Nhóm Entity Framework nhận thức được rằng đây là một vấn đề về khả năng sử dụng và có kế hoạch giải quyết nó sau EF6.

Từ nhóm Khung thực thể:

Đây là một vấn đề về khả năng sử dụng mà chúng tôi nhận thức được và là điều chúng tôi đã suy nghĩ và có kế hoạch thực hiện nhiều công việc hơn về hậu EF6. Tôi đã tạo mục công việc này để theo dõi vấn đề: http://entityframework.codeplex.com/workitem/864 Mục công việc cũng chứa liên kết đến mục thoại của người dùng cho mục này - tôi khuyến khích bạn bỏ phiếu cho nó nếu bạn có chưa làm như vậy đã.

Nếu điều này ảnh hưởng đến bạn, hãy bỏ phiếu cho tính năng tại

http://entityframework.codeplex.com/workitem/864


hậu EF6? sau đó nó sẽ là năm nào trong trường hợp lạc quan?
quetzalcoatl

@quetzalcoatl: Ít nhất thì nó cũng nằm trong tầm ngắm của họ :-) EF đã đi được một chặng đường kể từ EF 1 nhưng vẫn còn nhiều cách để tiếp tục.
Eric J.

1

Tất cả các câu trả lời đều tuyệt vời để giải thích vấn đề, nhưng không có câu trả lời nào thực sự giải quyết được vấn đề cho tôi.

Tôi thấy rằng nếu tôi không sử dụng mối quan hệ trong thực thể mẹ mà chỉ thêm và xóa các thực thể con thì mọi thứ đều hoạt động tốt.

Xin lỗi cho VB nhưng đó là những gì dự án tôi đang làm việc được viết trong đó.

Thực thể mẹ "Báo cáo" có mối quan hệ từ một đến nhiều đối với "ReportRole" và có thuộc tính "ReportRoles". Các vai trò mới được chuyển vào bằng một chuỗi được phân tách bằng dấu phẩy từ lệnh gọi Ajax.

Dòng đầu tiên sẽ xóa tất cả các thực thể con và nếu tôi sử dụng "report.ReportRoles.Remove (f)" thay vì "db.ReportRoles.Remove (f)" thì tôi sẽ gặp lỗi.

report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f))
Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(","))
newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.