'SubSonic.Schema .DatabaseColumn'유형의 오브젝트를 직렬화하는 중에 순환 참조가 발견되었습니다.


170

간단한 JSON 반환을 시도하고 있지만 아래에 다음과 같은 문제가 있습니다.

public JsonResult GetEventData()
{
    var data = Event.Find(x => x.ID != 0);
    return Json(data);
}

이 질문의 제목에 표시된 예외를 제외하고 HTTP 500을 얻습니다. 나는 또한 시도했다

var data = Event.All().ToList()

같은 문제가 발생했습니다.

이것이 버그입니까, 아니면 제 구현입니까?


1
이것 좀 봐 ScriptIgnore속성을 사용하는 솔루션이 있습니다 . stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
— freddoo

이것이 저에게 가장 좋은 해결책이었습니다. 나는 Game> Tournament> Game> Tournament> Game 등을 가졌다. 나는 ScriptIgnoreTournament.Game 속성에 속성을 배치했고 잘 작동했다 :)
— eth0

: 경우 사람이 QA 체크 아웃, 더 추가 코드가 필요하지 않습니다이 문제에 대한 (하지 모범 사례) 솔루션 "자동"원한다 JSON (ServiceStack.Text 라이브러리)에서 엔티티 프레임 워크 클래스 참조를 직렬화하지 마십시오
— nikib3ro

답변:


175

JSON 시리얼 라이저가 지원하지 않는 객체 계층 구조에 순환 참조가있는 것 같습니다. 모든 열이 필요합니까? 뷰에서 필요한 속성 만 선택할 수 있습니다.

return Json(new 
{  
    PropertyINeed1 = data.PropertyINeed1,
    PropertyINeed2 = data.PropertyINeed2
});

이것은 JSON 객체를 더 가볍고 이해하기 쉽게 만듭니다. 많은 속성이있는 경우 AutoMapper를 사용하여 DTO 객체와 View 객체 를 자동으로 매핑 할 수 있습니다 .


나는 내가 원하는 것을 선택하는 것이 효과가 있다고 생각한다. 순환 참조는 이벤트에서 IQueryable <Category>를 가지고 있기 때문에 IQueryable <Event>를 가질 것이기 때문이다.
— Jon

7
Automapper는이 문제가 발생하지 않는다고 보장하지 않습니다. 나는 대답을 찾기 위해 여기에 왔고 실제로 automapper를 사용하고 있습니다.
— 선장 켄 파치

1
@ClayKaboom의 답변을 참조하십시오. 왜 그것이 순환 일 수 있는지 설명합니다.
— PandaWood

106

나는 같은 문제를 겪었고 using Newtonsoft.Json;

var list = JsonConvert.SerializeObject(model,
    Formatting.None,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});

return Content(list, "application/json");

3
이 인라인 코드는 저에게 효과적이었습니다. kravits88에서 언급 한 전역 구성의 동일한 내용이 작동하지 않습니다. 또한이 코드에 대한 ContentResult를 반환하도록 메서드 서명을 업데이트해야합니다.
— BiLaL

6
허용 된 것으로 표시된 답변에서와 같이 객체를 다른 표현으로 변환하는 데 시간을 소비 할 수없는 경우를 다루므로 이는 최상의 답변으로 표시되어야합니다.
— Renan

56

복잡한 객체가 결과 json 객체를 실패하게하기 때문에 실제로 발생합니다. 그리고 객체가 매핑되면 자식을 매핑하고 부모를 매핑하여 순환 참조가 발생하기 때문에 실패합니다. Json은 직렬화하는 데 시간이 오래 걸리므로 예외로 인한 문제를 방지합니다.

Entity Framework 매핑도 동일한 동작을 생성하며 솔루션은 원치 않는 모든 속성을 삭제하는 것입니다.

최종 답변을 명시하면 전체 코드는 다음과 같습니다.

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           new {
                Result = (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
               }
           , JsonRequestBehavior.AllowGet
           );
}

Result속성 내부의 개체를 원하지 않는 경우에도 다음이 될 수 있습니다 .

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
           , JsonRequestBehavior.AllowGet
           );
}

1
@Clay 덕분에 명확하고 이해하기 쉬운 +1 오류 뒤에있는 개념에 대한 설명이 마음에 듭니다.
— Ajay2707

14

요약하면 다음과 같은 4 가지 솔루션이 있습니다.

해결 방법 1 : DBContext에 대한 ProxyCreation을 끄고 결국 복원하십시오.

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        bool proxyCreation = db.Configuration.ProxyCreationEnabled;
        try
        {
            //set ProxyCreation to false
            db.Configuration.ProxyCreationEnabled = false;

            var data = db.Products.ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
        finally
        {
            //restore ProxyCreation to its original state
            db.Configuration.ProxyCreationEnabled = proxyCreation;
        }
    }

해결 방법 2 : Serializer 설정에서 무시하도록 ReferenceLoopHandling을 설정하여 JsonConvert 사용

    //using using Newtonsoft.Json;

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.ToList();

            JsonSerializerSettings jss = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
            var result = JsonConvert.SerializeObject(data, Formatting.Indented, jss);

            return Json(result, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

다음 두 가지 솔루션은 동일하지만 강력한 형식이므로 모델을 사용하는 것이 좋습니다.

해결 방법 3 : 필요한 속성 만 포함 된 모델을 반환하십시오.

    private DBEntities db = new DBEntities();//dbcontext

    public class ProductModel
    {
        public int Product_ID { get; set;}

        public string Product_Name { get; set;}

        public double Product_Price { get; set;}
    }

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new ProductModel
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

해결 방법 4 : 필요한 속성 만 포함하는 새로운 동적 객체를 반환합니다.

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

7

xml 및 기타 다양한 형식과 마찬가지로 JSON은 트리 기반 직렬화 형식입니다. "트리"는 다음과 같이 객체에 순환 참조가 있으면 사랑하지 않습니다.

root B => child A => parent B => child A => parent B => ...

특정 경로를 따라 탐색을 비활성화하는 방법이 종종 있습니다. 예를 들어 XmlSerializer부모 속성을로 표시 할 수 있습니다 XmlIgnore. 해당 json serializer 로 이것이 가능한지 또는 DatabaseColumn적절한 마커가 있는지 여부 는 알 수 없습니다 ( 매우 직렬화 API를 참조해야하기 때문에 매우 가능성이 낮습니다)


4

EntityFramework 엔터티를 생성하는 데 사용되는 새로운 DbContext T4 템플릿 때문입니다. 변경 추적을 수행 할 수 있도록이 템플릿은 멋진 POCO를 감싸서 프록시 패턴을 사용합니다. 그러면 JavaScriptSerializer로 직렬화 할 때 문제가 발생합니다.

따라서 두 가지 해결책은 다음과 같습니다.

  1. 클라이언트에서 필요한 속성을 직렬화하고 반환하면됩니다.
  2. 컨텍스트의 구성에서 프록시 자동 생성을 설정하여 자동 생성을 끌 수 있습니다

    context.Configuration.ProxyCreationEnabled = 거짓;

아래 기사에서 잘 설명했습니다.

http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/


4

Newtonsoft.Json 사용 : Global.asax Application_Start 메소드에서 다음 행을 추가하십시오.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

1
분명히 매우 직설적으로 보이지만 나에게는 효과가 없었습니다
— BiLaL


4

테이블 개체를 직접 변환하지 마십시오. 다른 테이블간에 관계가 설정되면이 오류가 발생할 수 있습니다. 오히려 모델 클래스를 만들고 클래스 객체에 값을 할당 한 다음 직렬화 할 수 있습니다.


3

제공된 답변은 좋지만 "아키텍처"관점을 추가하여 개선 할 수 있다고 생각합니다.

조사

MVC's Controller.Json함수가 작업을 수행하고 있지만이 경우 관련 오류를 제공하는 것이 매우 열악합니다. 를 사용 Newtonsoft.Json.JsonConvert.SerializeObject하면 오류가 순환 참조를 트리거하는 특성을 정확히 지정합니다. 보다 복잡한 객체 계층을 직렬화 할 때 특히 유용합니다.

적절한 아키텍처

ORM의 내비게이션 속성은 직렬화와 관련하여 당혹의 길이 기 때문에 데이터 모델 (예 : EF 모델)을 직렬화하려고 시도해서는 안됩니다. 데이터 흐름은 다음과 같아야합니다.

Database -> data models -> service models -> JSON string 

서비스 모델은 자동 매퍼 (예 : Automapper )를 사용하여 데이터 모델에서 얻을 수 있습니다 . 이것이 순환 참조의 부족을 보장하지는 않지만 적절한 설계는이를 수행해야합니다. 서비스 모델에는 서비스 소비자가 요구하는 것 (예 : 속성)이 정확하게 포함되어야합니다.

드문 경우이지만 클라이언트가 서로 다른 수준에서 동일한 객체 유형을 포함하는 계층을 요청하면 서비스는 부모-> 자식 관계 (참조가 아닌 식별자 만 사용)를 갖는 선형 구조를 만들 수 있습니다.

최신 응용 프로그램은 복잡한 데이터 구조를 한 번에로드하지 않는 경향이 있으며 서비스 모델은 적어야합니다. 예 :

  1. 이벤트에 액세스-헤더 데이터 (식별자, 이름, 날짜 등) 만로드 됨-> 헤더 데이터 만 포함하는 서비스 모델 (JSON)
  2. 관리 대상 참석자 목록-팝업에 액세스하고 지연 대상자 목록-> 참석자 목록 만 포함 된 서비스 모델 (JSON)을로드합니다.

1

MVC5보기에서 녹아웃을 사용하기 때문에 수정 프로그램을 사용하고 있습니다.

행동에

return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));

함수

   public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
    {
        TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
        foreach (var item in Entity.GetType().GetProperties())
        {
            if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
                item.SetValue(Entity_, Entity.GetPropValue(item.Name));
        }
        return Entity_;  
    }

0

순환 참조를 유발하는 특성을 확인할 수 있습니다. 그런 다음 다음과 같은 작업을 수행 할 수 있습니다.

private Object DeCircular(Object object)
{
   // Set properties that cause the circular reference to null

   return object
}

-1
//first: Create a class as your view model

public class EventViewModel 
{
 public int Id{get;set}
 public string Property1{get;set;}
 public string Property2{get;set;}
}
//then from your method
[HttpGet]
public async Task<ActionResult> GetEvent()
{
 var events = await db.Event.Find(x => x.ID != 0);
 List<EventViewModel> model = events.Select(event => new EventViewModel(){
 Id = event.Id,
 Property1 = event.Property1,
 Property1 = event.Property2
}).ToList();
 return Json(new{ data = model }, JsonRequestBehavior.AllowGet);
}

이것은 질문에 대한 답이 아닙니다
— Dane I

-1

이 문제를 해결하는 더 쉬운 대안은 문자열을 반환하고 JavaScriptSerializer를 사용하여 해당 문자열을 json으로 형식화하는 것입니다.

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

뷰에서 원하는 속성을 선택하는 "선택"부분이 중요합니다. 일부 객체에는 부모에 대한 참조가 있습니다. 속성을 선택하지 않으면 표 전체를 가져 가면 순환 참조가 나타날 수 있습니다.

이 작업을 수행하지 마십시오 :

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.toList();
   return j.Serialize(entityList );
}

전체 테이블을 원하지 않으면 대신이 작업을 수행하십시오.

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

이렇게하면 필요한 속성만으로 적은 데이터로보기를 렌더링하고 웹을 더 빠르게 실행할 수 있습니다.

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