EF4 POCO 개체의 변경 사항을 저장할 때 관계 업데이트


107

Entity Framework 4, POCO 개체 및 ASP.Net MVC2. BlogPost와 Tag 엔티티 사이에 다 대다 관계가 있습니다. 이것은 내 T4 생성 POCO BlogPost 클래스에 다음이 있음을 의미합니다.

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

ObjectContext 인스턴스에서 BlogPost 및 관련 태그를 요청하고 다른 레이어로 보냅니다 (MVC 애플리케이션에서보기). 나중에 속성이 변경되고 관계가 변경된 업데이트 된 BlogPost를 다시 가져옵니다. 예를 들어 태그 "A" "B"와 "C"가 있고 새 태그는 "C"와 "D"입니다. 내 특정 예에서는 새 태그가없고 태그의 속성이 변경되지 않으므로 저장해야하는 유일한 것은 변경된 관계입니다. 이제 이것을 다른 ObjectContext에 저장해야합니다. (업데이트 : 이제 동일한 컨텍스트 인스턴스에서 시도했지만 실패했습니다.)

문제 : 관계를 제대로 저장하지 못합니다. 내가 찾은 모든 것을 시도했습니다.

  • Controller.UpdateModel 및 Controller.TryUpdateModel이 작동하지 않습니다.
  • 컨텍스트에서 이전 BlogPost를 가져온 다음 컬렉션을 수정하는 것은 작동하지 않습니다. (다음 시점에서 다른 방법으로)
  • 이것은 아마도 작동 할 것이지만 이것이 해결책이 아닌 해결 방법이기를 바랍니다.
  • 가능한 모든 조합에서 BlogPost 및 / 또는 태그에 대한 Attach / Add / ChangeObjectState 함수를 시도했습니다. 실패한.
  • 이것은 내가 필요한 것처럼 보이지만 작동하지 않습니다 (문제를 해결하려고했지만 문제를 해결할 수 없습니다).
  • ChangeState / Add / Attach / ... 컨텍스트의 관계 개체를 시도했습니다. 실패한.

"작동하지 않음"은 대부분의 경우 오류가 발생하지 않고 적어도 BlogPost의 속성을 저장할 때까지 주어진 "솔루션"에 대해 작업했음을 의미합니다. 관계에서 발생하는 일은 다양합니다. 일반적으로 태그는 새 PK가있는 태그 테이블에 다시 추가되고 저장된 BlogPost는 원본이 아닌이를 참조합니다. 물론 반환 된 태그에는 PK가 있으며 저장 / 업데이트 메서드 전에 PK를 확인하고 데이터베이스에있는 것과 동일하므로 EF는 새 개체이고 해당 PK가 임시 개체라고 생각합니다.

내가 알고 있고 자동화 된 간단한 솔루션을 찾는 것이 불가능할 수있는 문제 : POCO 개체의 컬렉션이 변경되면 위에서 언급 한 가상 컬렉션 속성에 의해 발생해야합니다. FixupCollection 트릭이 반대쪽에서 역 참조를 업데이트하기 때문입니다. 다 대다 관계의. 그러나 View가 업데이트 된 BlogPost 개체를 "반환"할 때는 발생하지 않았습니다. 이것은 아마도 내 문제에 대한 간단한 해결책이 없을 수도 있지만 그것은 나를 매우 슬프게 할 것이고 EF4-POCO-MVC 승리를 싫어할 것입니다. :(. 또한 그것은 EF가 MVC 환경에서 이것을 할 수 없다는 것을 의미합니다. EF4 객체 유형이 사용됩니다 :(. 변경된 BlogPost가 기존 PK를 사용하는 태그와 관계가 있다는 것을 스냅 샷 기반 변경 추적에서 알아 내야한다고 생각합니다.

Btw : 일대 다 관계에서도 같은 문제가 발생한다고 생각합니다 (구글과 제 동료는 그렇게 말합니다). 집에서 시도해 보겠습니다.하지만 앱에서 6 개의 다 대다 관계에 도움이되지 않는다고해도 :(.


코드를 게시하십시오. 이것은 일반적인 시나리오입니다.
John Farrell

1
이 문제에 대한 자동 솔루션이 있습니다. 아래 답변에 숨겨져 있으므로 많은 사람들이 놓칠 수 있지만 지옥에서 작업을 구할 수 있습니다. 여기에서 게시물을 참조하십시오
brentmckendrick

@brentmckendrick 다른 접근 방식이 더 낫다고 생각합니다. 수정 된 전체 개체 그래프를 유선으로 보내는 대신 델타 만 보내지 않는 이유는 무엇입니까? 이 경우 생성 된 DTO 클래스도 필요하지 않습니다. 어느 쪽이든 이에 대한 의견이 있으시면 stackoverflow.com/questions/1344066/calculate-object-delta 에서 논의 해 봅시다 .
HappyNomad 2013

답변:


145

이렇게 해보자 :

  • 컨텍스트에 BlogPost를 첨부하십시오. 객체를 객체의 상태에 연결하면 모든 관련 객체와 모든 관계가 변경되지 않음으로 설정됩니다.
  • context.ObjectStateManager.ChangeObjectState를 사용하여 BlogPost를 Modified로 설정하십시오.
  • 태그 수집을 통해 반복
  • context.ObjectStateManager.ChangeRelationshipState를 사용하여 현재 Tag와 BlogPost 간의 관계에 대한 상태를 설정합니다.
  • 변경 사항을 저장하다

편집하다:

내 의견 중 하나가 EF가 병합을 수행 할 것이라는 잘못된 희망을 준 것 같습니다. 나는이 문제를 많이 가지고 있었고 내 결론은 EF가 당신을 위해 이것을하지 않을 것이라고 말한다. MSDN 에서도 내 질문을 찾은 것 같습니다 . 실제로 인터넷에는 그러한 질문이 많이 있습니다. 문제는이 시나리오를 다루는 방법이 명확하게 명시되어 있지 않다는 것입니다. 따라서 문제를 살펴 보겠습니다.

문제 배경

EF는 지속성이 업데이트, 삽입 또는 삭제해야하는 레코드를 알 수 있도록 엔터티의 변경 내용을 추적해야합니다. 문제는 변경 사항을 추적하는 것이 ObjectContext의 책임이라는 것입니다. ObjectContext는 연결된 엔터티에 대한 변경 사항 만 추적 할 수 있습니다. ObjectContext 외부에서 생성 된 엔티티는 전혀 추적되지 않습니다.

문제 설명

위의 설명을 바탕으로 EF는 엔터티가 항상 컨텍스트에 연결되는 연결된 시나리오에 더 적합하다는 것을 명확하게 말할 수 있습니다. 웹 응용 프로그램에는 요청 처리 후 컨텍스트가 닫히고 엔터티 콘텐츠가 클라이언트에 HTTP 응답으로 전달되는 연결이 끊긴 시나리오가 필요합니다. 다음 HTTP 요청은 다시 생성되고 새 컨텍스트에 연결되고 유지되어야하는 엔티티의 수정 된 콘텐츠를 제공합니다. 재생성은 일반적으로 컨텍스트 범위 밖에서 발생합니다 (지속성이 무시되는 계층 형 아키텍처).

해결책

그렇다면 이러한 단절된 시나리오를 어떻게 처리할까요? POCO 클래스를 사용할 때 변경 내용 추적을 처리하는 세 가지 방법이 있습니다.

  • 스냅 샷-동일한 컨텍스트 필요 = 연결이 끊긴 시나리오에는 쓸모 없음
  • 동적 추적 프록시-동일한 컨텍스트 필요 = 연결이 끊긴 시나리오에는 쓸모 없음
  • 수동 동기화.

단일 엔티티에 대한 수동 동기화는 쉬운 작업입니다. 엔티티를 첨부하고 삽입을 위해 AddObject, 삭제를 위해 DeleteObject를 호출하거나 업데이트를 위해 ObjectStateManager에서 상태를 Modified로 설정하기 만하면됩니다. 진짜 고통은 단일 개체가 아닌 개체 그래프를 처리해야 할 때 발생합니다. 이 고통은 독립적 인 연결 (외래 키 속성을 사용하지 않는 연결)과 다 대다 관계를 처리해야 할 때 더욱 심해집니다. 이 경우 개체 그래프의 각 엔티티뿐만 아니라 개체 그래프의 각 관계도 수동으로 동기화해야합니다.

수동 동기화는 MSDN 문서의 솔루션으로 제안됩니다. 개체 연결 및 분리 는 다음 같이 말합니다.

개체는 Unchanged 상태로 개체 컨텍스트에 연결됩니다. 개체가 분리 된 상태에서 수정 된 것을 알고있어 개체의 상태 또는 관계를 변경해야하는 경우 다음 방법 중 하나를 사용하십시오.

언급 된 메서드는 ObjectStateManager = 수동 변경 추적의 ChangeObjectState 및 ChangeRelationshipState입니다. 유사한 제안이 다른 MSDN 문서 기사에 있습니다. 관계 정의 및 관리에 따르면 다음 같습니다.

연결이 끊어진 개체로 작업하는 경우 동기화를 수동으로 관리해야합니다.

또한 EF의 이러한 동작을 정확하게 비판하는 EF v1과 관련된 블로그 게시물 이 있습니다.

해결 이유

EF에는 Refresh , Load , ApplyCurrentValues , ApplyOriginalValues , MergeOption 등과 같은 많은 "유용한"작업 및 설정 이 있습니다. 그러나 내 조사에 따르면 이러한 모든 기능은 단일 엔터티에 대해서만 작동하며 스칼라 기본 설정에만 영향을줍니다 (= 탐색 속성 및 관계 아님). 오히려 엔터티에 중첩 된 복잡한 형식으로이 메서드를 테스트하지 않습니다.

기타 제안 된 솔루션

실제 병합 기능 대신 EF 팀은 문제를 해결하지 못하는 STE ( Self Tracking Entities) 라는 기능을 제공합니다 . 우선 STE는 전체 처리에 동일한 인스턴스가 사용되는 경우에만 작동합니다. 웹 애플리케이션에서는보기 상태 또는 세션에 인스턴스를 저장하지 않는 한 그렇지 않습니다. 그 때문에 EF를 사용하는 것이 매우 불행하고 NHibernate의 기능을 확인할 것입니다. 첫 번째 관찰은 NHibernate가 아마도 그러한 기능을 가지고 있다고 말한다 .

결론

이 가정 은 MSDN 포럼의 다른 관련 질문대한 단일 링크로 끝낼 것 입니다. Zeeshan Hirani의 대답을 확인하십시오. 그는 Entity Framework 4.0 Recipes의 저자입니다 . 객체 그래프의 자동 병합이 지원되지 않는다고 말하면 그를 믿습니다.

그러나 여전히 내가 완전히 틀렸고 일부 자동 병합 기능이 EF에 존재할 가능성이 있습니다.

편집 2 :

보시다시피 이것은 2007 년 제안 으로 MS Connect 에 이미 추가 되었습니다. MS는 다음 버전에서 수행 할 작업으로 폐쇄했지만 실제로 STE를 제외하고는이 격차를 개선하기 위해 수행 된 작업이 없습니다.


7
이것은 내가 읽은 최고의 답변 중 하나입니다. 주제에 대한 많은 MSDN 기사, 문서 및 블로그 게시물이 전달하지 못한 것을 명확하게 설명했습니다. EF4는 본질적으로 "분리 된"엔터티에서 관계 업데이트를 지원하지 않습니다. 사용자가 직접 구현할 수있는 도구 만 제공합니다. 감사합니다!
tyriker 2010

1
그래서 지난 몇 달 후, NHibernate는 EF4에 비해이 문제와 관련된 어떻습니까?
CallMeLaNN 2011

1
이것은 NHibernate에서 매우 잘 지원됩니다. :-) 수동으로 병합 할 필요가 없습니다. 제 예에서는 3 단계 딥 오브젝트 그래프이고, 질문에는 답변이 있고, 각 답변에는 코멘트가 있으며, 질문에는 코멘트도 있습니다. NHibernate는 얼마나 복잡한 지에 상관없이 개체 그래프를 지속 / 병합 할 수 있습니다. ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html 또 다른 NHibernate 사용자 : codinginstinct.com/2009/11/…
Michael Buen 2011-08-09

2
내가 읽은 최고의 설명 중 하나! 감사합니다
marvelTracker

2
EF 팀은이 포스트 EF6를 다룰 계획입니다. entityframework.codeplex.com/workitem/864
Eric J.

19

위에서 Ladislav가 설명한 문제에 대한 해결책이 있습니다. 제공된 그래프와 지속 형 그래프의 차이를 기반으로 추가 / 업데이트 / 삭제를 자동으로 수행하는 DbContext에 대한 확장 메서드를 만들었습니다.

현재 Entity Framework를 사용하여 연락처 업데이트를 수동으로 수행하고, 각 연락처가 새 것인지 확인하고, 추가하고, 업데이트되었는지 확인하고, 편집하고, 제거되었는지 확인한 다음 데이터베이스에서 삭제해야합니다. 대규모 시스템에서 몇 가지 다른 집합체에 대해이 작업을 수행해야하면 더 나은 방법이 있어야한다는 사실을 깨닫기 시작합니다.

http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-를 살펴보고 도움이되는지 확인하십시오. 분리 된 엔티티의 그래프 /

여기 https://github.com/refactorthis/GraphDiff 코드로 바로 이동할 수 있습니다.


질문을 쉽게 풀 수있을 거라고 확신 합니다.
Shimmy Weitzhandler 2013

1
안녕 시미, 드디어 볼 시간이 생겼습니다. 오늘 밤 조사하겠습니다.
brentmckendrick 2013

이 라이브러리는 훌륭하고 많은 시간을 절약했습니다! 고마워!
lordjeb

9

나는 그것이 OP에 늦었다는 것을 알고 있지만 이것은 매우 일반적인 문제이기 때문에 다른 사람에게 봉사하는 경우에 이것을 게시했습니다. 나는이 문제를 가지고 놀았고 상당히 간단한 해결책을 얻었다 고 생각합니다.

  1. 상태를 수정 됨으로 설정하여 기본 개체 (예 : 블로그)를 저장합니다.
  2. 업데이트해야하는 컬렉션을 포함하여 업데이트 된 개체에 대한 데이터베이스를 쿼리합니다.
  3. 내 컬렉션에 포함 할 엔터티를 .ToList () 쿼리하고 변환합니다.
  4. 주 개체의 컬렉션을 3 단계에서 얻은 목록으로 업데이트합니다.
  5. 변경 사항을 저장하다();

다음 예제에서 "dataobj"및 "_categories"는 컨트롤러에서 수신 한 매개 변수 "dataobj"는 내 기본 개체이고 "_categories"는 사용자가 뷰에서 선택한 범주의 ID를 포함하는 IEnumerable입니다.

    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();

여러 관계에서도 작동합니다.


7

Entity Framework 팀은 이것이 사용성 문제라는 것을 알고 있으며 EF6 이후에 해결할 계획입니다.

Entity Framework 팀에서 :

이것은 우리가 알고있는 사용성 문제이며 우리가 EF6 이후에 더 많은 작업을 할 계획이며 생각하고있는 것입니다. 문제를 추적하기 위해이 작업 항목을 만들었습니다. http://entityframework.codeplex.com/workitem/864 작업 항목에는 이에 대한 사용자 음성 항목에 대한 링크도 포함되어 있습니다. 아직 그렇게하지 않았습니다.

이로 인해 영향을 받으면 다음에서 기능에 투표하십시오.

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


EF6 이후? 그렇다면 낙관적 인 경우는 언제일까요?
quetzalcoatl 2013

@quetzalcoatl : 적어도 그것은 그들의 레이더에 있습니다 :-) EF는 EF 1 이후로 완만 한 길을 왔지만 여전히 갈 방법이 있습니다.
Eric J.

1

모든 답변은 문제를 설명하는 데 훌륭했지만 그중 어느 것도 실제로 문제를 해결하지 못했습니다.

부모 엔터티에서 관계를 사용하지 않고 자식 엔터티를 추가하고 제거하면 모든 것이 잘 작동한다는 것을 알았습니다.

VB에게는 미안하지만 내가 작업중인 프로젝트가 작성되었습니다.

상위 엔터티 "Report"는 "ReportRole"과 일대 다 관계를 가지며 "ReportRoles"속성을 갖습니다. 새 역할은 Ajax 호출에서 쉼표로 구분 된 문자열로 전달됩니다.

첫 번째 줄은 모든 자식 엔터티를 제거하고 "db.ReportRoles.Remove (f)"대신 "report.ReportRoles.Remove (f)"를 사용하면 오류가 발생합니다.

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}))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.