c# json entity-framework asp.net-web-api json.net

c# - ¿Cómo serializar "realmente" los objetos circulares de referencia con Newtonsoft.Json?



entity-framework asp.net-web-api (1)

Tengo problemas para obtener datos serializados correctamente desde mi controlador de API web ASP.NET utilizando Newtonsoft.Json.

Esto es lo que creo que está sucediendo; corrígeme si me equivoco. En determinadas circunstancias (específicamente cuando no hay referencias circulares en los datos), todo funciona de la manera esperada: una lista de objetos poblados se serializa y se devuelve. Si introduzco datos que provocan una referencia circular en el modelo (descrito a continuación, e incluso con el conjunto PreserveReferencesHandling.Objects ), solo los elementos de la lista que conducen al primer objeto con una referencia circular se serializan de forma que el cliente pueda " trabajar con". Los "elementos previos a" pueden ser cualquiera de los elementos de los datos si se ordenan de forma diferente antes de enviarlos al serializador, pero al menos uno se serializará de forma que el cliente pueda "trabajar con". Los objetos vacíos terminan siendo serializados como referencias de Newtonsoft ( {$ref:X} ).

Por ejemplo, si tengo un modelo EF completo con propiedades de navegación que se ve así:

En mi global.asax:

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

Aquí está la consulta fundamental que estoy haciendo usando Entity Framework (lazy-loading está desactivada por lo que no hay ninguna clase de proxy aquí):

[HttpGet] [Route("starting")] public IEnumerable<Balance> GetStartingBalances() { using (MyContext db = new MyContext()) { var data = db.Balances .Include(x => x.Source) .Include(x => x.Place) .ToList() return data; } }

Hasta ahora todo bien, los data están poblados.

Si no hay referencias circulares, la vida es grandiosa. Sin embargo, tan pronto como haya 2 entidades de Balance con la misma Source o Place , la serialización convertirá los objetos de Balance posteriores de la lista más alta que estoy devolviendo en referencias de Newtonsoft en lugar de sus objetos de pleno derecho porque ya estaban serializado en la propiedad Balances de los objetos Source o Place :

[{"$id":"1","BalanceID":4,"SourceID":2,"PlaceID":2 ...Omitted for clarity...},{"$ref":"4"}]

El problema con esto es que el cliente no sabe qué hacer con {$ref:4} a pesar de que los humanos comprendemos lo que está sucediendo. En mi caso, esto significa que no puedo usar AngularJS para ng-repeat en toda mi lista de Balances con este JSON, porque no son todos objetos True Balance con una propiedad Balance para enlazar. Estoy seguro de que hay toneladas de otros casos de uso que tendrían el mismo problema.

No puedo apagar el json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects porque se json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects muchas otras cosas (lo que está bien documentado en otras 100 preguntas aquí y en todas partes).

¿Existe una solución mejor para esto aparte de pasar por las entidades en el controlador de la API web y hacer

Balance.Source.Balances = null;

a todas las propiedades de navegación para romper las referencias circulares? Porque ESO tampoco parece correcto.


Sí, usar PreserveReferencesHandling.Objects es realmente la mejor manera de serializar un gráfico de objetos con referencias circulares, ya que produce el JSON más compacto y en realidad conserva la estructura de referencia del gráfico de objetos. Es decir, cuando deserializa el JSON de nuevo a los objetos (utilizando una biblioteca que comprende la notación $id y $ref ), cada referencia a un objeto particular apuntará a la misma instancia de ese objeto, en lugar de tener múltiples instancias con el mismo datos.

En su caso, el problema es que el analizador del lado del cliente no comprende la notación $id y $ref producida por Json.Net, por lo que las referencias no se están resolviendo. Esto se puede solucionar utilizando un método de javascript para reconstruir las referencias de objeto después de deserializar el JSON. Vea aquí y aquí para ejemplos.

Otra posibilidad que podría funcionar, según su situación, es establecer ReferenceLoopHandling para Ignore al serializar en lugar de establecer PreserveReferencesHandling en Objects . Sin embargo, esta no es una solución perfecta. Consulte esta pregunta para obtener una explicación detallada de las diferencias entre el uso de ReferenceLoopHandling.Ignore y PreserveReferencesHandling.Objects .