JSON 및 엔티티의 순환 참조 문제를 해결하는 방법


13

프레젠테이션 레이어에 JSON과 MVC를 활용하고 데이터 모델 / 데이터베이스에 대한 엔티티 프레임 워크를 활용하는 웹 사이트를 만드는 실험을 해왔습니다. 내 문제는 Model 객체를 JSON으로 직렬화하는 것과 관련이 있습니다.

코드 우선 방법을 사용하여 데이터베이스를 만듭니다. 코드 첫 번째 방법을 수행 할 때 일대 다 관계 (부모 / 자식)는 자식에게 부모에 대한 참조가 필요합니다. (예제 코드는 오타이지만 그림을 얻습니다)

class parent
{
   public List<child> Children{get;set;}
   public int Id{get;set;}

}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId")]
    public parent MyParent{get;set;}
    public string name{get;set;}
 }

JsonResult를 통해 "parent"객체를 반환 할 때 "child"에 parent 클래스의 속성이 있으므로 순환 참조 오류가 발생합니다.

ScriptIgnore 특성을 시도했지만 자식 개체를 볼 수있는 기능이 없습니다. 어느 시점에서 부모 자식 뷰에 정보를 표시해야합니다.

순환 참조가없는 부모와 자식 모두를위한 기본 클래스를 만들려고했습니다. 불행히도 baseParent와 baseChild를 보내려고하면 JSON 파서에서 파생 클래스로 읽습니다 (이 개념이 저를 탈출하고 있다고 확신합니다).

Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);

내가 생각해 낸 한 가지 해결책은 "보기"모델을 만드는 것입니다. 부모 클래스에 대한 참조를 포함하지 않는 간단한 버전의 데이터베이스 모델을 만듭니다. 이러한 뷰 모델에는 각각 데이터베이스 버전 및 데이터베이스 모델을 매개 변수 (viewmodel.name = databasemodel.name)로 사용하는 생성자를 리턴하는 메소드가 있습니다. 이 방법은 효과가 있지만 강제적 인 것 같습니다.

참고 :이 토론에 더 가치가 있다고 생각하기 때문에 여기에 게시하고 있습니다. 다른 디자인 패턴을 사용하여이 문제를 극복하거나 모델에서 다른 속성을 사용하는 것만 큼 간단 할 수 있습니다. 검색 에서이 문제를 극복하는 좋은 방법을 보지 못했습니다.

내 최종 목표는 서버와 통신하고 데이터를 표시하기 위해 JSON을 많이 활용하는 멋진 MVC 응용 프로그램을 만드는 것입니다. 여러 계층에 걸쳐 일관된 모델을 유지하면서

답변:


6

귀하의 질문에 두 가지 뚜렷한 주제가 있습니다.

  • JSON으로 직렬화 할 때 순환 참조를 관리하는 방법은 무엇입니까?
  • 뷰에서 EF 엔터티를 모델 엔터티로 사용하는 것이 얼마나 안전합니까?

순환 참조에 관해서는 간단한 해결책이 없다고 유감입니다. 먼저 JSON을 순환 참조를 나타내는 데 사용할 수 없으므로 다음 코드는 다음과 같습니다.

var aParent = {Children : []}, aChild  = {Parent : aParent};
aParent.Children.push(aChild);
JSON.stringify(aParent);

결과 : TypeError: Converting circular structure to JSON

컴포지션의 컴포지트-> 구성 요소 부분 만 유지하고 "뒤로 탐색"컴포넌트-> 컴포지트를 삭제하는 것이 유일한 선택입니다.

class parent
{
    public List<child> Children{get;set;}
    public int Id{get;set;}
}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId"), ScriptIgnore]
    public parent MyParent{get;set;}
    public string name{get;set;}
}

jQuery를 사용하여 클라이언트 측 에서이 탐색 속성을 재구성 할 수있는 것은 없습니다.

$.each(parent.Children, function(i, child) {
  child.Parent = parent;  
})

그러나 JSON.stringify는 순환 참조를 직렬화 할 수 없으므로 서버로 다시 보내기 전에 다시 버려야합니다.

$.each(parent.Children, function(i, child) {
  delete child.Parent;  
})

이제 EF 엔티티를 뷰 모델 엔티티로 사용하는 문제가 있습니다.

첫 번째 EF는 클래스의 동적 프록시를 사용하여 변경 감지 또는 지연로드와 같은 동작을 구현할 가능성이 높으므로 EF 엔티티를 직렬화하려는 경우이를 비활성화해야합니다.

또한 모든 기본 바인더가 요청의 모든 필드를 사용자가 설정하지 않으려는 항목을 포함한 항목 필드로 매핑하므로 UI에서 EF 항목을 사용하는 것은 위험 할 수 있습니다.

따라서 MVC 앱을 올바르게 디자인하려면 내부 비즈니스 모델의 "게스트"가 클라이언트에 노출되지 않도록 전용 뷰 모델을 사용하는 것이 좋습니다. 따라서 특정 뷰 모델을 사용하는 것이 좋습니다.


순환 참조와 EF 문제를 모두 해결할 수있는 객체 지향 기술을 사용하는 멋진 방법이 있습니까?
DanScan

순환 참조와 EF 문제를 모두 해결할 수있는 객체 지향 기술을 사용하는 멋진 방법이 있습니까? BaseObject와 같이 entityObject 및 viewObject에 의해 상속됩니다. 따라서 entityObject에는 순환 참조가 있지만 viewObject에는 순환 참조가 없습니다. entityObject (viewObject.name = entityObject.name)에서 viewObject를 빌드 하여이 문제를 해결했지만 시간 낭비입니다. 이 문제를 어떻게 해결할 수 있습니까?
DanScan

그들은 당신을 매우 많이합니다. 귀하의 설명은 매우 명확하고 이해하기 쉬웠습니다.
Nick

2

객체를 직렬화하려는 시도보다 간단한 대안은 부모 / 자식 객체의 직렬화를 비활성화하는 것입니다. 대신, 필요에 따라 관련 부모 / 자식 개체를 가져 오기 위해 별도의 호출을 수행 할 수 있습니다. 이것은 응용 프로그램에 이상적이지 않지만 옵션입니다.

이를 위해 DataContractSerializer를 설정하고 데이터 모델 클래스의 생성자에서 DataContractSerializer.PreserveObjectReferences 속성을 'false'로 설정할 수 있습니다. 이는 HTTP 응답을 직렬화 할 때 오브젝트 참조가 보존되지 않도록 지정합니다.

예 :

JSON 형식 :

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.None;

XML 형식 :

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ false, null);
xml.SetSerializer<Employee>(dcs);

즉, 자식 개체가 참조 된 항목을 가져 오면 자식 개체가 직렬화되지 않습니다.

DataContractsSerializer 클래스 도 참조하십시오 .


1

순환 참조를 처리하는 JSON 시리얼 라이저

다음은 JSONSerializer첫 번째 발생을 직렬화하고 reference모든 후속 발생에서 * 를 첫 번째 발생에 저장하여 순환 참조를 처리하는 사용자 정의 Jackson의 예 입니다.

Jackson으로 객체를 직렬화 할 때 순환 참조 다루기

위 기사의 관련 부분 스 니펫 :

private final Set<ObjectName> seen;

/**
 * Serialize an ObjectName with all its attributes or only its String representation if it is a circular reference.
 * @param on ObjectName to serialize
 * @param jgen JsonGenerator to build the output
 * @param provider SerializerProvider
 * @throws IOException
 * @throws JsonProcessingException
 */
@Override
public void serialize(@Nonnull final ObjectName on, @Nonnull final JsonGenerator jgen, @Nonnull final SerializerProvider provider) throws IOException, JsonProcessingException
{
    if (this.seen.contains(on))
    {
        jgen.writeString(on.toString());
    }
    else
    {
        this.seen.add(on);
        jgen.writeStartObject();
        final List<MBeanAttributeInfo> ais = this.getAttributeInfos(on);
        for (final MBeanAttributeInfo ai : ais)
        {
            final Object attribute = this.getAttribute(on, ai.getName());
            jgen.writeObjectField(ai.getName(), attribute);
        }
        jgen.writeEndObject();
    }
}

0

내가 생각해 낸 한 가지 해결책은 "보기"모델을 만드는 것입니다. 부모 클래스에 대한 참조를 포함하지 않는 간단한 버전의 데이터베이스 모델을 만듭니다. 이러한 뷰 모델에는 각각 데이터베이스 버전 및 데이터베이스 모델을 매개 변수 (viewmodel.name = databasemodel.name)로 사용하는 생성자를 리턴하는 메소드가 있습니다. 이 방법은 효과가 있지만 강제적 인 것 같습니다.

최소한의 데이터 만 보내는 것이 정답입니다. 데이터베이스에서 데이터를 보낼 때 일반적으로 모든 연관이있는 모든 단일 열을 보내는 것은 의미가 없습니다. 소비자는 데이터베이스 연결 및 구조, 즉 데이터베이스를 처리 할 필요가 없습니다. 이렇게하면 대역폭이 절약 될뿐만 아니라 유지 관리, 읽기 및 소비가 훨씬 쉬워집니다. 데이터를 쿼리 한 다음 실제로 eq를 전송하는 데 필요한 데이터를 모델링하십시오. 최소한.


빅 데이터에 대해 이야기 할 때 더 많은 처리 시간이 필요합니다. 이제 모든 것을 두 번 변환해야합니다.
David van Dugteren

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