Wykryto możliwy cykl obiektów .Net Core 3.0, który nie jest obsługiwany

22

Mam 2 podmioty, które są powiązane jako jeden do wielu

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

Jeśli spróbuję dostać restauracje z rezerwacjami za pomocą mojego interfejsu API

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

W odpowiedzi pojawia się błąd, ponieważ obiekty zawierają odniesienia do siebie. Istnieją pokrewne posty, które zalecają utworzenie osobnego modelu lub dodanie konfiguracji NewtonsoftJson

Problem polega na tym, że nie chcę tworzyć osobnego modelu, a druga sugestia nie pomogła. Czy istnieje sposób ładowania danych bez relacji cyklicznej? *

System.Text.Json.JsonException: Wykryto możliwy cykl obiektu, który nie jest obsługiwany. Może to wynikać z cyklu lub głębokości obiektu jest większa niż maksymalna dozwolona głębokość 32. w System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerCycleDetected (Int32 maxDepth) w System.Text.Json.JsonSerializer.Write (Utf8JsonWriter writer , Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions opcje, WriteStack & state) w System.Text.Json.JsonSerializer.WriteAsyncCore (Stream utf8Json, wartość obiektu, typ inputType, JsonSerializerOptions opcje, CancellationTput.TransruptTextMot. WriteResponseBodyAsync (kontekst OutputFormatterWriteContext, kodowanie selectedEncoding) w Microsoft.AspNetCore.Mvc.

*

Nazar Pylyp
źródło
Poproś, aby zignorował właściwość restauracji klasy Rezerwacja.
Lasse V. Karlsen
6
Naprawdę nie powinieneś zwracać jednostek DB bezpośrednio z interfejsu API. Sugeruję tworzenie DTO specyficznych dla API i odpowiednie mapowanie. To prawda, że ​​powiedziałeś, że nie chcesz tego robić, ale uważam, że ogólnie dobrą praktyką jest oddzielenie wewnętrznych interfejsów API i trwałości.
mackie,
„i druga sugestia nie pomogła” wymaga szczegółów.
Henk Holterman,
„Problem polega na tym, że nie chcę tworzyć osobnego modelu”. Twój projekt jest zasadniczo wadliwy, chyba że to zrobisz. Interfejs API jest umową podobną do interfejsu (dosłownie jest to interfejs programowania aplikacji ). Po opublikowaniu nie powinna się nigdy zmieniać, a każda zmiana wymaga nowej wersji, która będzie musiała działać jednocześnie ze starą wersją (która będzie przestarzała i ostatecznie usunięta w przyszłości). Dzięki temu klienci mogą zaktualizować swoje implementacje. Jeśli zwracasz element bezpośrednio, ściśle łączysz warstwę danych.
Chris Pratt,
Wszelkie zmiany w tej warstwie danych wymagają natychmiastowej i nieodwracalnej zmiany interfejsu API, natychmiast niszcząc wszystkich klientów, dopóki nie zaktualizują swoich implementacji. W przypadku, gdy nie jest to oczywiste, to źle. Krótko mówiąc: nigdy nie przyjmuj ani nie zwracaj jednostek z interfejsu API. Należy zawsze używać DTOs.
Chris Pratt,

Odpowiedzi:

32

Próbowałem kodu w nowym projekcie i drugi sposób wydaje się działać dobrze po zainstalowaniu pakietu Microsoft.AspNetCore.Mvc.NewtonsoftJson po raz pierwszy dla 3.0

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

Wypróbuj nowy projekt i porównaj różnice.

Ryan
źródło
1
Kluczowym momentem jest tutaj ponowna instalacja odpowiedniej wersji Microsoft.AspNetCore.Mvc.NewtonsoftJson Nie zwracałem uwagi na wersję, ponieważ ten pakiet był dostępny pod pudełkiem bez żadnych błędów i ostrzeżeń! Dziękuję za odpowiedź ! Wszystko działa dokładnie tak, jak się spodziewałem!
Nazar Pylyp
1
Czy nie jest źle, że przy ulepszonej perf systemu json musimy używać NewtonsoftJson? : /
Marek Urbanowicz
40

.NET Core 3.1 Zainstaluj pakiet Microsoft.AspNetCore.Mvc.NewtonsoftJson

Startup.cs Dodaj usługę

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
anjoe
źródło
1
Czy możesz sformatować swoją odpowiedź i dodać jakieś szczegóły? To nieczytelne.
Sid
Aby uzyskać więcej informacji, sprawdź: thecodebuzz.com/...
Diego Venâncio
4

Wprowadzenie ustawienia opcji serializacji JSON podczas uruchamiania jest prawdopodobnie preferowanym sposobem, ponieważ prawdopodobnie będziesz mieć podobne przypadki w przyszłości. W międzyczasie możesz jednak spróbować dodać atrybuty danych do swojego modelu, aby nie był serializowany: 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
źródło
To też działa. Ale jak już wspomniałeś, w tym celu musisz zaktualizować wszystkie modele, wolę 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;} 

Powyżej również działało. Ale wolę następujące

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

Ponieważ najpierw musimy dodać atrybut do wszystkich modeli, możemy mieć cykliczne odwołanie.

Nantharupan
źródło