Se detectó una referencia circular al serializar un objeto de tipo 'SubSonic.Schema .DatabaseColumn'.

170

Estoy tratando de hacer una simple devolución JSON pero tengo problemas, tengo lo siguiente a continuación.

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

Recibo un HTTP 500 con la excepción que se muestra en el título de esta pregunta. También intenté

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

Eso dio el mismo problema.

¿Es esto un error o mi implementación?

Jon
fuente
1
Mira este. Hay una solución usando el ScriptIgnoreatributo. stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
freddoo
Esta fue la mejor solución para mí; Tenía Juego> Torneo> Juego> Torneo> Juego, etc. Coloqué un ScriptIgnoreatributo en la propiedad Torneo.Juego y funcionó bien :)
eth0
En caso de que alguien quiera una solución "automatizada" (no la mejor práctica) para este problema que no requiere código adicional, consulte este QA: No
serialice las

Respuestas:

175

Parece que hay referencias circulares en su jerarquía de objetos que no es compatible con el serializador JSON. ¿Necesitas todas las columnas? Puede seleccionar solo las propiedades que necesita en la vista:

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

Esto hará que su objeto JSON sea más ligero y fácil de entender. Si tiene muchas propiedades, AutoMapper podría usarse para asignar automáticamente entre objetos DTO y objetos de vista.

Darin Dimitrov
fuente
Creo que quizás seleccionar los que quiero funcionen Creo que la referencia circular es porque en el Evento tengo IQueryable <Categoría> que a su vez tendrá un IQueryable <Event>
Jon
77
Automapper no garantiza que no obtendrá este problema. Vine aquí buscando una respuesta y en realidad estoy usando automapper.
Capitán Kenpachi
1
Ve la respuesta de @ClayKaboom, ya que explica por qué podría ser circular
PandaWood
106

Tuve el mismo problema y resuelto por using Newtonsoft.Json;

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

return Content(list, "application/json");
ddfnfal
fuente
3
Este código en línea funcionó bien para mí. Las mismas cosas en la configuración global mencionadas por kravits88 no me funcionan. TAMBIÉN, la firma del método debe actualizarse para devolver ContentResult para este código.
BiLaL
66
Esto debe marcarse como la mejor respuesta, ya que cubre casos en los que no puede pasar horas convirtiendo sus objetos en otras representaciones como en la respuesta marcada como aceptada.
Renan
56

Esto realmente sucede porque los objetos complejos son los que hacen que el objeto json resultante falle. Y falla porque cuando el objeto está mapeado, mapea a los niños, que mapea a sus padres, haciendo que ocurra una referencia circular. Json tomaría un tiempo infinito para serializarlo, por lo que evita el problema con la excepción.

El mapeo de Entity Framework también produce el mismo comportamiento, y la solución es descartar todas las propiedades no deseadas.

Simplemente explicitando la respuesta final, todo el código sería:

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

También podría ser el siguiente en caso de que no desee los objetos dentro de una Resultpropiedad:

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
fuente
1
+1 para cosas claras y fáciles de entender, gracias @Clay. Me gusta su explicación sobre los conceptos detrás del error.
Ajay2707
14

Para resumir, hay 4 soluciones para esto:

Solución 1: apague ProxyCreation para DBContext y restaúrelo al 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;
        }
    }

Solución 2: Uso de JsonConvert configurando ReferenceLoopHandling para ignorar la configuración del 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);
        }
    }

Seguir dos soluciones es lo mismo, pero usar un modelo es mejor porque tiene un tipo fuerte.

Solución 3: devuelva un modelo que incluya solo las propiedades necesarias.

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

Solución 4: devuelve un nuevo objeto dinámico que incluye solo las propiedades necesarias.

    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
fuente
7

JSON, como xml y varios otros formatos, es un formato de serialización basado en árbol. No te amará si tienes referencias circulares en tus objetos, ya que el "árbol" sería:

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

A menudo hay formas de desactivar la navegación a lo largo de un determinado camino; por ejemplo, con XmlSerializerusted puede marcar la propiedad principal como XmlIgnore. No sé si esto es posible con el serializador json en cuestión, ni si DatabaseColumntiene marcadores adecuados ( muy poco probable, ya que necesitaría hacer referencia a cada API de serialización)

Marc Gravell
fuente
4

Esto se debe a la nueva plantilla DbContext T4 que se utiliza para generar las entidades EntityFramework. Para poder realizar el seguimiento de cambios, estas plantillas utilizan el patrón Proxy, envolviendo sus bonitas POCO con ellas. Esto provoca problemas al serializar con JavaScriptSerializer.

Entonces las 2 soluciones son:

  1. O simplemente serializa y devuelve las propiedades que necesita en el cliente
  2. Puede desactivar la generación automática de proxies configurándola en la configuración del contexto

    context.Configuration.ProxyCreationEnabled = false;

Muy bien explicado en el siguiente artículo.

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

nilesh
fuente
4

Usando Newtonsoft.Json: en su método Global.asax Application_Start agregue esta línea:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
kravits88
fuente
1
Aparentemente parece muy sencillo pero no funcionó para mí
BiLaL
4

agregar [JsonIgnore]a las propiedades virtuales en su modelo.

MorenajeRD
fuente
4

Evite convertir el objeto de tabla directamente. Si se establecen relaciones entre otras tablas, podría arrojar este error. En cambio, puede crear una clase de modelo, asignar valores al objeto de clase y luego serializarlo.

Unais.NI
fuente
3

Las respuestas proporcionadas son buenas, pero creo que pueden mejorarse agregando una perspectiva "arquitectónica".

Investigación

MVC's Controller.JsonLa función está haciendo el trabajo, pero es muy pobre para proporcionar un error relevante en este caso. Al usar Newtonsoft.Json.JsonConvert.SerializeObject, el error especifica exactamente cuál es la propiedad que está activando la referencia circular. Esto es particularmente útil cuando se serializan jerarquías de objetos más complejas.

Arquitectura adecuada

Nunca se debe tratar de serializar modelos de datos (por ejemplo, modelos EF), ya que las propiedades de navegación de ORM son el camino a la perdición cuando se trata de serialización. El flujo de datos debe ser el siguiente:

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

Los modelos de servicio se pueden obtener a partir de modelos de datos utilizando mapeadores automáticos (por ejemplo, Automapper ). Si bien esto no garantiza la falta de referencias circulares, el diseño adecuado debería hacerlo: los modelos de servicio deben contener exactamente lo que requiere el consumidor del servicio (es decir, las propiedades).

En esos casos raros, cuando el cliente solicita una jerarquía que involucra el mismo tipo de objeto en diferentes niveles, el servicio puede crear una estructura lineal con relación padre-> hijo (usando solo identificadores, no referencias).

Las aplicaciones modernas tienden a evitar cargar estructuras de datos complejas a la vez y los modelos de servicio deben ser delgados. P.ej:

  1. acceder a un evento: solo se cargan datos de encabezado (identificador, nombre, fecha, etc.) -> modelo de servicio (JSON) que contiene solo datos de encabezado
  2. lista de asistentes administrados: acceda a una ventana emergente y cargue de forma diferida la lista -> modelo de servicio (JSON) que contiene solo la lista de asistentes
Alexei
fuente
1

Estoy usando la solución, porque uso Knockout en vistas MVC5.

En acción

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

función

   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
fuente
0

Puede notar las propiedades que causan la referencia circular. Entonces puedes hacer algo como:

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

   return object
}
Bassel
fuente
-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
fuente
Esto no responde la pregunta
Dane I
-1

Una alternativa más fácil para resolver este problema es devolver una cadena y formatear esa cadena a json con 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 );
}

Es importante la parte "Seleccionar", que elige las propiedades que desea en su vista. Algún objeto tiene una referencia para el padre. Si no elige los atributos, puede aparecer la referencia circular, si solo toma las tablas como un todo.

No hagas esto:

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

Haga esto en su lugar si no desea toda la tabla:

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

Esto ayuda a representar una vista con menos datos, solo con los atributos que necesita, y hace que su web funcione más rápido.

Sterling Diaz
fuente