Foi detectado o possível ciclo de objetos do .Net Core 3.0 que não é suportado

22

Eu tenho 2 entidades que estão relacionadas como uma para muitas

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

Se eu tentar obter restaurantes com reservas usando minha API

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

Eu recebo erro na resposta, porque os objetos contêm referências um ao outro. Existem posts relacionados que recomendam a criação de um modelo separado ou a adição da configuração do NewtonsoftJson

O problema é que eu não quero criar um modelo separado e a segunda sugestão não ajudou. Existe alguma maneira de carregar dados sem relação ciclada? *

System.Text.Json.JsonException: um possível ciclo de objeto foi detectado e não é suportado. Isso pode ser devido a um ciclo ou se a profundidade do objeto for maior que a profundidade máxima permitida de 32. no System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerCycleDetected (Int32 maxDepth) no System.Text.Json.JsonSerializer.Write (Utf8JsonWriter writer , Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions opções, WriteStack & state) em System.Text.Json.JsonSerializer.WriteAsyncCore (Stream utf8Json, Valor do objeto, Tipo inputType, JsonSerializerOptions opções, CancellationToken canceltingTextServiceFormattersTextSystem.Text. WriteResponseBodyAsync (contexto OutputFormatterWriteContext, Encoding selectedEncoding) em Microsoft.AspNetCore.Mvc.

*

Nazar Pylyp
fonte
Peça para ignorar a propriedade Restaurant da classe Reservation.
Lasse V. Karlsen
6
Realmente, você não deve retornar suas entidades de banco de dados diretamente da sua API. Eu sugiro criar DTOs específicos da API e mapear adequadamente. Concedido que você disse que não queria fazer isso, mas consideraria uma boa prática geral manter a API e as persistências internas separadas.
Mackie
"e a segunda sugestão não ajudou" precisa de detalhes.
Henk Holterman
"O problema é que eu não quero criar um modelo separado". Seu design é fundamentalmente falho, a menos que você faça exatamente isso. Uma API é um contrato como uma interface (é literalmente uma interface de programação de aplicativos ). Ele nunca deve mudar, uma vez publicado, e qualquer alteração requer uma nova versão, que precisará ser executada simultaneamente com a versão antiga (que será descontinuada e eventualmente removida no futuro). Isso permite que os clientes atualizem suas implementações. Se você retornar uma entidade diretamente, estará acoplando firmemente sua camada de dados.
Chris Pratt
Qualquer alteração nessa camada de dados exige uma alteração imediata e irreversível da API, interrompendo todos os clientes imediatamente até que eles atualizem suas implementações. Caso não seja óbvio, isso é uma coisa ruim. Em resumo: nunca aceite ou retorne entidades de uma API. Você sempre deve usar DTOs.
Chris Pratt

Respostas:

32

Eu tentei o seu código em um novo projeto e a segunda maneira parece funcionar bem após a instalação do pacote Microsoft.AspNetCore.Mvc.NewtonsoftJson, primeiramente para o 3.0

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

Tente com um novo projeto e compare as diferenças.

Ryan
fonte
11
O momento principal aqui é reinstalar a versão adequada do Microsoft.AspNetCore.Mvc.NewtonsoftJson Não prestei atenção na versão, pois este pacote estava disponível na caixa sem erros e avisos! Obrigado pela resposta! Tudo funciona exatamente como eu esperava!
Nazar Pylyp 6/12/19
11
Não é errado que, com o aprimoramento do desempenho do sistema json, tenhamos que usar o NewtonsoftJson? : /
Marek Urbanowicz 04/04
40

.NET Core 3.1 Instale o pacote Microsoft.AspNetCore.Mvc.NewtonsoftJson

Startup.cs Adicionar serviço

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
anjoe
fonte
11
Você pode formatar sua resposta e adicionar alguns detalhes? É ilegível.
Sid
Para mais detalhes, consulte: thecodebuzz.com/…
Diego Venâncio
4

A configuração das opções de serialização JSON na inicialização é provavelmente a maneira preferida, pois você provavelmente terá casos semelhantes no futuro. Enquanto isso, entretanto, você pode tentar adicionar atributos de dados ao seu modelo para que ele não seja serializado: 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
fonte
Isso também funciona. Mas, como você mencionou, com isso você deve atualizar todos os modelos, prefiro services.AddControllers (). AddNewtonsoftJson (options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
Nantharupan 22/02
1
public class Reservation{ 
public int ReservationId {get;set;} 
public int RestaurantId {get;set;} 
[JsonIgnore]
public Restaurant Restaurant {get;set;} 

Acima funcionou também. Mas eu prefiro o seguinte

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

Como primeiro precisamos adicionar o atributo a todos os modelos, podemos ter a referência cíclica.

Nantharupan
fonte