У меня есть объект Post
, который имеет отношения "один ко многим" с Author
и Comment
. Я хотел бы загрузить все Post
и соединить их с первыми Author
и всеми Comment
. Код с Include
будет выглядеть так:
Post[] posts = ctx.Posts.Include(p => p.Authors.Take(1)).Include(p => p.Comments).ToArray();
В этом запросе возникает проблема с декартовым взрывом. Если Post
владеет n
Comments
, Author
и Comment
будут повторяться n
раз в результирующем наборе.
Решение №1
В EF Core 5.0 я мог бы использовать Разделить запрос, но тогда это сгенерирует 3 запроса, когда я хочу сначала загрузить Post
с Author
, а затем со всеми Comment
.
Решение №2
Сначала загрузите Post
с помощью Author
, а затем перейдите к сообщению на явно загружать свои комментарии, но это приведет к n + 1
запросам.
Post[] posts = ctx.Posts.Include(p => p.Authors.Take(1)).ToArray();
foreach (Post post in posts)
ctx.Entry(post).Collection(p => p.Comments).Load();
Решение №3
Сначала загрузите Post
с Author
, затем соберите все идентификаторы сообщений, чтобы сгенерировать один запрос для загрузки комментариев.
Dictionary<int, Post> postsById = ctx.Posts.Include(p => p.Authors.Take(1)).ToDictionnary(p => p.Id);
Comment[] comments = ctx.Comments.Where(c => postsById.ContainsKey(c.PostId)).ToArray();
foreach (Comment comment in comments)
postsById[comment.PostId].Comments.Add(comment); // How to avoid re-adding comment?
Это решение будет генерировать только 2 запроса без каких-либо дублирующихся данных, но как я могу избежать повторного добавления комментариев к сообщению? Есть ли лучший способ, чем 3 предложенных решения?
Include
в конечном итоге вызовут огромные наборы результатов из запроса SQL по мере увеличения количества связанных записей. В конечном итоге я переключился на ADO для этих типов запросов — это позволило мне разделить запросы вручную, а также запустить их все одновременно (поскольку EF не является потокобезопасным). Прирост производительности был день и ночь. - person crgolden   schedule 11.12.2020