Json y excepción de referencia circular

Tengo un objeto que tiene una referencia circular a otro objeto. Dada la relación entre estos objetos, este es el diseño correcto.

Para ilustrar

Machine => Customer => Machine 

Como es de esperar, me topa con un problema cuando trato de usar Json para serializar una máquina u objeto de cliente. De lo que no estoy seguro es de cómo resolver este problema ya que no quiero romper la relación entre los objetos de la Máquina y del Cliente. ¿Cuáles son las opciones para resolver este problema?

Editar

Actualmente estoy usando el método Json provisto por la clase base del Controlador . Entonces la serialización que estoy haciendo es tan básica como:

 Json(machineForm); 

Actualizar:

No intente utilizar NonSerializedAttribute , ya que JavaScriptSerializer aparentemente lo ignora.

En su lugar, use ScriptIgnoreAttribute en System.Web.Script.Serialization .

 public class Machine { public string Customer { get; set; } // Other members // ... } public class Customer { [ScriptIgnore] public Machine Machine { get; set; } // Parent reference? // Other members // ... } 

De esta forma, cuando Json una Machine al método Json , atravesará la relación de Machine a Customer pero no intentará regresar de Customer a Machine .

La relación sigue ahí para que su código haga lo que le plazca, pero JavaScriptSerializer (utilizado por el método Json ) lo ignorará.

Estoy respondiendo esto a pesar de su edad porque es el 3er resultado (actualmente) de Google para “referencia circular json.encode” y aunque no estoy de acuerdo con las respuestas (completamente) anteriores, en el que usar ScriptIgnoreAttribute supone que ningún lugar en tu código querrá atravesar la relación en la otra dirección para algunos JSON. No creo en bloquear su modelo debido a un caso de uso.

Me inspiró a usar esta solución simple.

Dado que está trabajando en una Vista en MVC, tiene el Modelo y simplemente desea asignar el Modelo a ViewData.Model dentro de su controlador, siga adelante y use una consulta LINQ dentro de su Vista para aplanar los datos eliminando muy bien la ofensa referencia circular para el JSON particular que desee de esta manera:

 var jsonMachines = from m in machineForm select new { mX, mY, // other Machine properties you desire Customer = new { m.Customer.Id, m.Customer.Name, // other Customer properties you desire }}; return Json(jsonMachines); 

O si la relación Máquina -> Cliente es 1 .. * -> * entonces intente:

 var jsonMachines = from m in machineForm select new { mX, mY, // other machine properties you desire Customers = new List( (from c in m.Customers select new Customer() { Id = c.Id, Name = c.Name, // Other Customer properties you desire }).Cast()) }; return Json(jsonMachines); 

Según la respuesta de txl, debe desactivar la carga lenta y la creación de proxy y puede usar los métodos normales para obtener sus datos.

Ejemplo:

 //Retrieve Items with Json: public JsonResult Search(string id = "") { db.Configuration.LazyLoadingEnabled = false; db.Configuration.ProxyCreationEnabled = false; var res = db.Table.Where(a => a.Name.Contains(id)).Take(8); return Json(res, JsonRequestBehavior.AllowGet); } 

Use para tener el mismo problema. He creado un método de extensión simple, que “aplana” objetos L2E en un IDictionary. Un IDictionary se serializa correctamente mediante JavaScriptSerializer. El Json resultante es lo mismo que serializar directamente el objeto.

Como limito el nivel de serialización, se evitan las referencias circulares. Tampoco incluirá tablas vinculadas 1-> n (Entitysets).

  private static IDictionary JsonFlatten(object data, int maxLevel, int currLevel) { var result = new Dictionary(); var myType = data.GetType(); var myAssembly = myType.Assembly; var props = myType.GetProperties(); foreach (var prop in props) { // Remove EntityKey etc. if (prop.Name.StartsWith("Entity")) { continue; } if (prop.Name.EndsWith("Reference")) { continue; } // Do not include lookups to linked tables Type typeOfProp = prop.PropertyType; if (typeOfProp.Name.StartsWith("EntityCollection")) { continue; } // If the type is from my assembly == custom type // include it, but flattened if (typeOfProp.Assembly == myAssembly) { if (currLevel < maxLevel) { result.Add(prop.Name, JsonFlatten(prop.GetValue(data, null), maxLevel, currLevel + 1)); } } else { result.Add(prop.Name, prop.GetValue(data, null)); } } return result; } public static IDictionary JsonFlatten(this Controller controller, object data, int maxLevel = 2) { return JsonFlatten(data, maxLevel, 1); } 

Mi método de acción se ve así:

  public JsonResult AsJson(int id) { var data = Find(id); var result = this.JsonFlatten(data); return Json(result, JsonRequestBehavior.AllowGet); } 

En Entity Framework versión 4 , hay una opción disponible: ObjectContextOptions.LazyLoadingEnabled

Ponerlo en falso debería evitar el problema de ‘referencia circular’. Sin embargo, tendrá que cargar explícitamente las propiedades de navegación que desea incluir.

ver: http://msdn.microsoft.com/en-us/library/bb896272.aspx

Como, por lo que sé, no puedes serializar referencias a objetos, pero solo copias puedes intentar utilizar un poco de un truco sucio que dice algo como esto:

  1. El cliente debe serializar su referencia de máquina como la identificación de la máquina
  2. Cuando deserializas el código json, puedes ejecutar una función simple encima que transforma esos identificadores en referencias adecuadas.

Debe decidir cuál es el objeto “raíz”. Supongamos que la máquina es la raíz, entonces el cliente es un subobjeto de la máquina. Cuando serializa la máquina, serializará al cliente como un subobjeto en el JSON, y cuando el cliente se serialice, NO serializará su referencia posterior a la máquina. Cuando su código deserializa la máquina, deserializará el subobjeto del cliente de la máquina y restablecerá la referencia retrospectiva del cliente a la máquina.

La mayoría de las bibliotecas de serialización proporcionan algún tipo de gancho para modificar cómo se realiza la deserialización para cada clase. Debería usar ese gancho para modificar la deserialización para la clase de máquina para restablecer la referencia inversa en el cliente de la máquina. Exactamente qué es ese gancho depende de la biblioteca JSON que esté utilizando.

También he tenido el mismo problema esta semana y no podía usar tipos anónimos porque necesitaba implementar una interfaz que solicitara una List . Después de hacer un diagtwig que muestra todas las relaciones con la navegabilidad, descubrí que MyType tenía una relación bidireccional con MyObject que causó esta referencia circular, ya que ambos se salvaron entre sí.

Después de decidir que MyObject realmente no necesitaba saber MyType y, por lo tanto, convertirlo en una relación unidireccional, este problema se resolvió.

Lo que he hecho es un poco radical, pero no necesito la propiedad, lo que hace que el desagradable error causante de referencia circular, así que lo haya establecido en nulo antes de la serialización.

 SessionTickets result = GetTicketsSession(); foreach(var r in result.Tickets) { r.TicketTypes = null; //those two were creating the problem r.SelectedTicketType = null; } return Json(result); 

Si realmente necesita sus propiedades, puede crear un modelo de vista que no contenga referencias circulares, pero tal vez guarde algún Id del elemento importante, que podría usar más adelante para restaurar el valor original.