¿Cómo se “serializa” realmente los objetos de referencia circular con Newtonsoft.Json?

Tengo un problema para obtener algunos datos serializados correctamente desde mi controlador ASP.NET Web API usando Newtonsoft.Json.

Esto es lo que yopensar está sucediendo, corrígeme si me equivoco. En ciertas circunstancias (específicamente cuando no hay referencias circulares en los datos) todo funciona como es de esperar: una lista de objetos poblados se serializa y devuelve. Si introduzco datos que causan una referencia circular en el modelo (descrito a continuación, e incluso conPreserveReferencesHandling.Objects set) solo los elementos de la lista que conducen al primer objeto con una referencia circular se serializan de manera que el cliente pueda "trabajar con". Los "elementos que conducen a" pueden ser cualquiera de los elementos en los datos si se ordenan de manera diferente antes de enviar las cosas al serializador, pero al menos uno se serializará de manera que el cliente pueda "trabajar". 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 clases 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 aquí todo bien,data está poblado

Si no hay referencias circulares, la vida es grandiosa. Sin embargo, tan pronto como haya 2Balance entidades con el mismoSource oPlace, entonces la serialización se convierte en la posteriorBalance objetos de la lista más alta que estoy volviendo a las referencias de Newtonsoft en lugar de sus objetos completos porque ya estaban serializados en elBalances propiedad de laSource oPlace objetos):

[{"$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 entendemos lo que está pasando. En mi caso, esto significa que no puedo usar AngularJS parang-repeat en toda mi lista de saldos con este JSON, porque no todos son verdadBalance objetos con unBalance propiedad para vincular. Estoy seguro de que hay toneladas de otros casos de uso que tendrían el mismo problema.

No puedo apagar eljson.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects porque muchas otras cosas se romperían (lo cual está bien documentado en otras 100 preguntas aquí y en otros lugares).

¿Existe una mejor solución para esto además de pasar por las entidades en el controlador de 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.

Respuestas a la pregunta(1)

Su respuesta a la pregunta