Se detectó un posible ciclo de objetos de .Net Core 3.0 que no es compatible

22

Tengo 2 entidades que están relacionadas como una a muchas

public class Restaurant {
   public int RestaurantId {get;set;}
   public string Name {get;set;}
   public List<Reservation> Reservations {get;set;}
   ...
}
public class Reservation{
   public int ReservationId {get;set;}
   public int RestaurantId {get;set;}
   public Restaurant Restaurant {get;set;}
}

Si trato de obtener restaurantes con reservas usando mi API

   var restaurants =  await _dbContext.Restaurants
                .AsNoTracking()
                .AsQueryable()
                .Include(m => m.Reservations).ToListAsync();
    .....

Recibo un error en respuesta, porque los objetos contienen referencias entre sí. Hay publicaciones relacionadas que recomiendan crear un modelo separado o agregar la configuración de NewtonsoftJson

El problema es que no quiero crear un modelo separado y la segunda sugerencia no ayudó. ¿Hay alguna forma de cargar datos sin una relación cíclica? * *

System.Text.Json.JsonException: se detectó un posible ciclo de objeto que no es compatible. Esto puede deberse a un ciclo o si la profundidad del objeto es mayor que la profundidad máxima permitida de 32. en System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerCycleDetected (Int32 maxDepth) en System.Text.Json.JsonSerializer.Write (Utf8JsonWriterizer , Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions opciones, WriteStack & state) en System.Text.Json.JsonSerializer.WriteAsyncCore (Stream utf8Json, Object value, Type inputType, JsonSerializerOptions opciones, CancellationToken.tm. WriteResponseBodyAsync (contexto OutputFormatterWriteContext, codificación selectedEncoding) en Microsoft.AspNetCore.Mvc.

* *

Nazar Pylyp
fuente
Pídale que ignore la propiedad del restaurante de la clase Reservation.
Lasse V. Karlsen
66
Realmente no deberías devolver tus entidades de base de datos directamente desde tu API. Sugeriría crear DTO específicos de API y mapear en consecuencia. De acuerdo, dijiste que no querías hacer eso, pero considero que es una buena práctica general mantener separados los elementos internos de API y persistencia.
mackie
"y la segunda sugerencia no ayudó" necesita detalles.
Henk Holterman
"El problema es que no quiero crear un modelo separado". Su diseño es fundamentalmente defectuoso a menos que haga exactamente eso. Una API es un contrato como una interfaz (es literalmente una interfaz de programación de aplicaciones ). Nunca debe cambiar, una vez publicado, y cualquier cambio requiere una nueva versión, que deberá ejecutarse simultáneamente con la versión anterior (que será obsoleta y eventualmente eliminada en el futuro). Eso les da a los clientes tiempo para actualizar sus implementaciones. Si devuelve una entidad directamente, está estrechamente acoplando su capa de datos.
Chris Pratt
Cualquier cambio en esa capa de datos requiere un cambio inmediato e irreversible en la API, interrumpiendo a todos los clientes de inmediato hasta que actualicen sus implementaciones. En caso de que no sea obvio, eso es algo malo. En resumen: nunca acepte ni devuelva entidades de una API. Usted debe siempre utilizar dtos.
Chris Pratt

Respuestas:

32

He probado su código en un nuevo proyecto y la segunda forma parece funcionar bien después de instalar el paquete Microsoft.AspNetCore.Mvc.NewtonsoftJson en primer lugar para 3.0

services.AddControllerWithViews()
    .AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

Pruebe con un nuevo proyecto y compare las diferencias.

Ryan
fuente
1
El momento clave aquí es reinstalar la versión adecuada de Microsoft.AspNetCore.Mvc.NewtonsoftJson. ¡No presté atención a la versión ya que este paquete estaba disponible en la caja sin ningún error y advertencia! Gracias por responder ! ¡Todo funciona exactamente como esperaba!
Nazar Pylyp
1
¿No está mal que con un rendimiento mejorado del sistema json, tengamos que usar NewtonsoftJson? : /
Marek Urbanowicz
40

.NET Core 3.1 Instalar el paquete Microsoft.AspNetCore.Mvc.NewtonsoftJson

Startup.cs Agregar servicio

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
anjoe
fuente
1
¿Puedes formatear tu respuesta y agregar algunos detalles? Es ilegible.
Sid
Para más detalles, consulte: thecodebuzz.com/…
Diego Venâncio
4

Probablemente sea preferible configurar las opciones de serialización JSON en el inicio, ya que es probable que tenga casos similares en el futuro. Mientras tanto, puede intentar agregar atributos de datos a su modelo para que no se serialice: https://www.newtonsoft.com/json/help/html/PropertyJsonIgnore.htm

public class Reservation{ 
    public int ReservationId {get;set;} 
    public int RestaurantId {get;set;} 
    [JsonIgnore]
    public Restaurant Restaurant {get;set;} 
}
timur
fuente
Esto tambien funciona. Pero como mencionó, con esto debe actualizar todos los modelos, prefiero services.AddControllers (). AddNewtonsoftJson (options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
Nantharupan
1
public class Reservation{ 
public int ReservationId {get;set;} 
public int RestaurantId {get;set;} 
[JsonIgnore]
public Restaurant Restaurant {get;set;} 

Por encima trabajó también. Pero prefiero lo siguiente

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

Porque primero necesitamos agregar el atributo a todos los modelos, podemos tener la referencia cíclica.

Nantharupan
fuente