Entity Framework Core, JSON - cykle

0

Cześć.

Projekt w .Net5 (Core).

Poniżej cały kontroler z zapytaniem.
W skrócie, mamy wyrazy (Word), wyrazy mają definicje (Clue), definicja ma wiele tagów (Tag) i kategorii (Category).
Kiedy próbuję zwrócić words z kontrolera to zapytanie praktycznie się nie kończy. Na prostych niewielkich danych zauważyłem, że JSON ma cykle, czyli te same dane znajdują się w nim wielokrotnie.

Wpadłem na pomysł żeby ręcznie wynulować referencje, po których nie chciałem żeby serializator schodził podczas serializacji (głównie wsteczne referencje). Dopóki nie wynulowałem dosłownie wszystkich takich, to potrafiłem dostać JSON 40MB. Dopiero kiedy wynulowałem wszystkie dostaję ładne odpowiedzi po kilkanaście/kilkadziesiąt kilobajtów, a kod wykonuje się szybciutko. Tak wygląda to "nulowanie" i cały kontroler z zapytaniem:

[HttpGet]
		[Route("{lang_id}/Words/{likePattern}")]
		public List<Word> GetWordsByFragAndLang(int lang_id, string likePattern)
		{
            var query = _context.Word
                .Include(w=> w.Clue)
                    .ThenInclude(clue=>clue.IdLevelNavigation)
                .Include(w=> w.Clue)
                    .ThenInclude(clue=>clue.TagClue)
                        .ThenInclude(cc=>cc.IdTagNavigation)
                .Include(w=> w.Clue)
                    .ThenInclude(clue=>clue.CategoryClue)
                        .ThenInclude(cc=>cc.IdCategoryNavigation)
                .Where(w=> EF.Functions.Like(w.Word1, likePattern) && w.IdDictionaryNavigation.LangId==lang_id);
            var words = query.ToList();

            
            words.ForEach(word=> 
            { 
                foreach(var clue in word.Clue)
				{
                    clue.IdWordNavigation = null;
                    clue.IdLangNavigation = null;

                    // level
                    clue.IdLevelNavigation.Clue = null;
                    // tag
                    foreach(var tagClue in clue.TagClue)
					{
                        tagClue.IdClueNavigation = null;
                        tagClue.IdTagNavigation.TagClue = null;
                        tagClue.IdTagNavigation.Clues = null;
                        tagClue.IdTagNavigation.Lang = null;
                        tagClue.IdTagNavigation.WordPackTag = null;
					}
                    // category
                    foreach(var categoryClue in clue.CategoryClue)
					{
                        categoryClue.IdClueNavigation = null;
                        categoryClue.IdCategoryNavigation.CategoryClue = null;
                        categoryClue.IdCategoryNavigation.Clues = null;
                        categoryClue.IdCategoryNavigation.Lang = null;
                        categoryClue.IdCategoryNavigation.WordPackCategory = null;
					}
				}
            });

            return words;
		}

Używam NewtonsoftJson. W Startup.cs mam:

services.AddControllersWithViews().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

Domyślam się, że nie tak to powinno wyglądać. Co robię nie tak ლ(ಠ_ಠ ლ) ?

0

A co się stanie gdy zrobisz tak:

Zamienisz

public List<Word>

na

public IActionResult

oraz

return words;

na

var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var test = JsonConvert.SerializeObject(words, settings);
return Ok(test);
0

@WeiXiao: Niestety nie pomogło, wystąpił dokładnie ten sam opisany problem. Jedno zapytanie wisi (nie kończy się), drugie zakończyło się ale JSON waży 750kB, a w mojej wersji z wynulowanymi referencjami 4kB.

1

A po co Ci includy po każdej stronie relacji wiele do wielu?
Duplikujesz dane niemiłosiernie.

Weź se zrób jakiegoś dtosa, zmapuj to spaghetti bazodanowe na model danych, który chcesz otrzymać i po problemie.

0

Dodaj do konfiguracji JSONa:

x.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;

0

@urke: rzeczywiście nie wiedziałem, że mogę wciągnąć kolekcję bez przechodzenia przez tabelę przejściową, skopiowałem też wszystko ręcznie zamiast nulować referencje. Ostatecznie kod wygląda tak:

public List<Word> GetWordsByFragAndLang(int lang_id, string likePattern)
		{
            var query = _context.Word
                .Include(w=> w.Clue)
                    .ThenInclude(clue=>clue.IdLevelNavigation)
                .Include(w=> w.Clue)
                        .ThenInclude(cc=>cc.Tags)
                .Include(w=> w.Clue)
                    .ThenInclude(clue=>clue.Categories)
                .Where(w=> EF.Functions.Like(w.Word1, likePattern) && w.IdDictionaryNavigation.LangId==lang_id);
            var words = query.ToList();

            return words.Select( word => new Word {
                Id = word.Id,
                Length = word.Length,
                Word1 = word.Word1,
                Clue = word.Clue.Select( clue => new Clue{
                    Id = clue.Id,
                    Clue1 = clue.Clue1,
                    Spelling = clue.Spelling,
                    WordDistribution = clue.WordDistribution,
                    IdLevelNavigation = new Level {
                        Id = clue.IdLevelNavigation.Id,
                        IntValue = clue.IdLevelNavigation.IntValue
                    },
                    Tags = clue.Tags.Select( tag => new Tag{
                        Id = tag.Id,
                        Description = tag.Description,
                        Name = tag.Name
                    } ).ToList(),
                    Categories = clue.Categories.Select( category => new Category{
                        Id = category.Id,
                        Name = category.Name
                    } ).ToList()
                } ).ToList()
            } ).ToList();

		}

1 użytkowników online, w tym zalogowanych: 0, gości: 1