Uma referência circular foi detectada durante a serialização de um objeto do tipo 'SubSonic.Schema .DatabaseColumn'.

170

Estou tentando fazer um retorno JSON simples, mas estou tendo problemas com os seguintes itens abaixo.

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

Eu recebo um HTTP 500 com a exceção, como mostrado no título desta pergunta. Eu também tentei

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

Isso deu o mesmo problema.

Isso é um bug ou minha implementação?

Jon
fonte
1
Olhe para este. Existe uma solução usando o ScriptIgnoreatributo stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
freddoo 27/10/09
Essa foi a melhor solução para mim; Eu tive Jogo> Tournament> Jogo> Tournament> Game, etc. Eu coloquei um ScriptIgnoreatributo na propriedade Tournament.Game e funcionou muito bem :)
eth0
No caso de alguém quer (e não de melhores práticas) solução para este problema que não requer código extra, confira este QA "automatizado": Do not serializar referências de classe Entity Framework em JSON (biblioteca ServiceStack.Text)
nikib3ro

Respostas:

175

Parece que há referências circulares na hierarquia de objetos que não são suportadas pelo serializador JSON. Você precisa de todas as colunas? Você pode escolher apenas as propriedades necessárias na exibição:

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

Isso tornará seu objeto JSON mais leve e fácil de entender. Se você tiver muitas propriedades, o AutoMapper poderá ser usado para mapear automaticamente entre objetos DTO e Exibir objetos.

Darin Dimitrov
fonte
Eu acho que talvez selecionando os que eu quero pode funcionar Eu acho que a referência circular é porque no Evento tenho IQueryable <Categoria> que por sua vez terá um IQueryable <Evento>
Jon
7
O Automapper não garante que você não terá esse problema. Eu vim aqui procurando uma resposta e na verdade estou usando o automapper.
capitão Kenpachi
1
Não ver a resposta da @ClayKaboom como ele explica por que ele pode ser circular
PandaWood
106

Eu tive o mesmo problema e resolvi por using Newtonsoft.Json;

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

return Content(list, "application/json");
ddfnfal
fonte
3
Esse código embutido funcionou bem para mim. As mesmas coisas na configuração global mencionadas por kravits88 não estão funcionando para mim. Além disso, a assinatura do método deve ser atualizada para retornar o ContentResult para esse código.
Bilal
6
Isso deve ser marcado como a melhor resposta, pois abrange casos em que você não pode passar horas convertendo seus objetos em outras representações, como na resposta marcada como aceita.
Renan
56

Na verdade, isso acontece porque os objetos complexos são o que faz com que o objeto json resultante falhe. E falha porque, quando o objeto é mapeado, ele mapeia os filhos, que mapeiam seus pais, fazendo com que uma referência circular ocorra. O Json levaria um tempo infinito para serializá-lo, impedindo o problema com a exceção.

O mapeamento do Entity Framework também produz o mesmo comportamento, e a solução é descartar todas as propriedades indesejadas.

Apenas explicando a resposta final, todo o código seria:

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

Também pode ser o seguinte, caso você não queira os objetos dentro de uma Resultpropriedade:

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
           );
}
ClayKaboom
fonte
1
+1 por coisas claras e fáceis de entender, graças à @Clay. Eu gosto da sua explicação sobre os conceitos por trás do erro.
Ajay2707
14

Para resumir, existem 4 soluções para isso:

Solução 1: desative o ProxyCreation para o DBContext e restaure-o no final.

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

Solução 2: Usando JsonConvert, definindo ReferenceLoopHandling para ignorar nas configurações do serializador.

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

As duas soluções a seguir são as mesmas, mas o uso de um modelo é melhor porque é tipado com bastante força.

Solução 3: retorne um modelo que inclua apenas as propriedades necessárias.

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

Solução 4: retorne um novo objeto dinâmico que inclua apenas as propriedades necessárias.

    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);
        }
    }
Amro
fonte
7

JSON, como xml e vários outros formatos, é um formato de serialização baseado em árvore. Não vai te amar se você tiver referências circulares em seus objetos, como seria a "árvore":

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

Muitas vezes, existem maneiras de desativar a navegação ao longo de um determinado caminho; por exemplo, XmlSerializervocê pode marcar a propriedade pai como XmlIgnore. Não sei se isso é possível com o serializador json em questão, nem se DatabaseColumnpossui marcadores adequados ( muito improvável, pois seria necessário fazer referência a todas as APIs de serialização)

Marc Gravell
fonte
4

Isso se deve ao novo modelo DbContext T4 usado para gerar as entidades EntityFramework. Para poder executar o rastreamento de alterações, esses modelos usam o padrão Proxy, envolvendo seus POCOs legais com eles. Isso causa os problemas ao serializar com o JavaScriptSerializer.

Então, as 2 soluções são:

  1. Você apenas serializa e retorna as propriedades necessárias no cliente
  2. Você pode desativar a geração automática de proxies configurando-a na configuração do contexto

    context.Configuration.ProxyCreationEnabled = false;

Muito bem explicado no artigo abaixo.

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

nilesh
fonte
4

Usando Newtonsoft.Json: No seu método Global.asax Application_Start, adicione esta linha:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
kravits88
fonte
1
Aparentemente parece muito simples, mas não funcionou para mim
Bilal
4

adicione [JsonIgnore]às propriedades virtuais no seu modelo.

MorenajeRD
fonte
4

Evite converter o objeto de tabela diretamente. Se as relações forem definidas entre outras tabelas, isso poderá gerar esse erro. Em vez disso, você pode criar uma classe de modelo, atribuir valores ao objeto de classe e depois serializá-lo.

Unais.NI
fonte
3

As respostas fornecidas são boas, mas acho que elas podem ser melhoradas adicionando uma perspectiva "arquitetônica".

Investigação

MVC's Controller.JsonA função está fazendo o trabalho, mas é muito ruim em fornecer um erro relevante nesse caso. Ao usar Newtonsoft.Json.JsonConvert.SerializeObject, o erro especifica exatamente qual é a propriedade que está acionando a referência circular. Isso é particularmente útil ao serializar hierarquias de objetos mais complexas.

Arquitetura adequada

Nunca se deve tentar serializar modelos de dados (por exemplo, modelos EF), pois as propriedades de navegação do ORM são o caminho para a perdição no que diz respeito à serialização. O fluxo de dados deve ser o seguinte:

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

Modelos de serviço podem ser obtidos em modelos de dados usando mapeadores automáticos (por exemplo, Automapper ). Embora isso não garanta a falta de referências circulares, o design adequado deve fazê-lo: os modelos de serviço devem conter exatamente o que o consumidor de serviços exige (ou seja, as propriedades).

Nesses casos raros, quando o cliente solicita uma hierarquia envolvendo o mesmo tipo de objeto em diferentes níveis, o serviço pode criar uma estrutura linear com o relacionamento pai-> filho (usando apenas identificadores, não referências).

Os aplicativos modernos tendem a evitar o carregamento de estruturas de dados complexas de uma só vez e os modelos de serviço devem ser escassos. Por exemplo:

  1. acessar um evento - apenas os dados do cabeçalho (identificador, nome, data etc.) são carregados -> modelo de serviço (JSON) contendo apenas dados do cabeçalho
  2. lista de participantes gerenciados - acesse um pop-up e carregue preguiçosamente a lista -> modelo de serviço (JSON) contendo apenas a lista de participantes
Alexei
fonte
1

Estou usando a correção, porque usando Knockout nos modos de exibição MVC5.

Em ação

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

função

   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_;  
    }
A.Kosecik
fonte
0

Você pode observar as propriedades que causam a referência circular. Então você pode fazer algo como:

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

   return object
}
Bassel
fonte
-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);
}
Ynnoboy
fonte
Isso não responde à pergunta #
242 Dane I
-1

Uma alternativa mais fácil para resolver esse problema é retornar uma sequência e formatar essa sequência para json com JavaScriptSerializer.

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

É importante a parte "Selecionar", que escolhe as propriedades que você deseja na sua exibição. Algum objeto tem uma referência para o pai. Se você não escolher os atributos, a referência circular poderá aparecer, se você apenas tomar as tabelas como um todo.

Não faça isso:

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

Faça isso se não quiser a tabela inteira:

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

Isso ajuda a renderizar uma exibição com menos dados, apenas com os atributos que você precisa, e torna a sua Web mais rápida.

Sterling Diaz
fonte