Вопрос или проблема
Я хотел бы начать с того, что я совершенно нов в EF Core и уже ознакомился с другими вопросами по этой теме, и не смог понять, как решить свою проблему.
У меня есть модель Attempt
:
public class Attempt(int Id, string UserId, int score)
{
public int Id { get; set; } = Id;
public string UserId { get; set; } = UserId!;
public int score { get; set; } = score;
}
Я пытаюсь выполнить этот запрос, который группирует все попытки по их UserId, выбирает попытку с наивысшим баллом для каждого пользователя, а затем сортирует их по баллу:
IQueryable<Attempt> GetBestAttempts() =>
db.Attempt
.GroupBy(a => a.UserId)
.Select(g => g.OrderByDescending(attempt => attempt.score).First())
.OrderByDescending(attempt => attempt.score)
.ThenBy(attempt => attempt.UserId);
Однако, когда я пытаюсь выполнить этот запрос, я получаю эту ошибку:
System.Collections.Generic.KeyNotFoundException: 'Данный ключ 'EmptyProjectionMember' не найден в словаре.'
Есть ли у кого-нибудь решение этой проблемы, которое не требует выполнения запроса? Этот запрос является частью множества других запросов, и я хотел бы избежать выполнения этого большого списка на сервере, если это возможно.
Удалось решить это с помощью сырого запроса, спасибо Microsoft copilot…
IQueryable<Attempt> GetBestAttempts() =>
Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSqlRaw(db.Attempt, @"
SELECT a.*
FROM ""Attempt"" a
INNER JOIN (
SELECT ""UserId"", MAX(""score"") AS ""MaxScore""
FROM ""Attempt""
GROUP BY ""UserId""
) maxAttempts ON a.""UserId"" = maxAttempts.""UserId"" AND a.""score"" = maxAttempts.""MaxScore""
WHERE a.""Id"" = (
SELECT MIN(innerA.""Id"")
FROM ""Attempt"" innerA
WHERE innerA.""UserId"" = a.""UserId"" AND innerA.""score"" = a.""score""
)
ORDER BY a.""score"" DESC, a.""UserId"";
");
Насколько я ненавижу сырые SQL-запросы, это подойдет на данный момент.
(Я вызвал метод расширения напрямую, потому что также использую Linq2Db в своем проекте и хочу избежать конфликтов имен)
Ответ или решение
Ошибка "The given key ‘EmptyProjectionMember’ was not present in the dictionary" в C# EF Core 8
При использовании Entity Framework Core (EF Core) 8 вы можете столкнуться с ошибкой, связанной с отсутствием ключа EmptyProjectionMember
в словаре. Эта проблема часто возникает при выполнении сложных запросов, таких как группировка и выборка данных. В данной статье мы подробно рассмотрим причины возникновения этой ошибки и предложим возможные решения, включая альтернативный подход с использованием сырого SQL-запроса.
Проблема: Контекст и Код Запроса
В приведённом примере вы работаете с моделью Attempt
, которая представляет собой попытку пользователя и содержит поля Id
, UserId
и score
. Цель вашего запроса — сгруппировать все попытки по UserId
, выбрать попытку с наивысшим счётом на пользователя и отсортировать результаты по счёту.
Ваш код запроса выглядит следующим образом:
IQueryable<Attempt> GetBestAttempts() =>
db.Attempt
.GroupBy(a => a.UserId)
.Select(g => g.OrderByDescending(attempt => attempt.score).First())
.OrderByDescending(attempt => attempt.score)
.ThenBy(attempt => attempt.UserId);
При попытке выполнить этот запрос возникает ошибка:
System.Collections.Generic.KeyNotFoundException: 'The given key 'EmptyProjectionMember' was not present in the dictionary.'
Причины Возникновения Ошибки
Ошибка KeyNotFoundException
в данном контексте указывает на то, что EF Core не может корректно обработать проектирование результатов при выполнении выполнения группировки и выборки. Это может произойти по ряду причин:
- Неправильная структура запроса: EF Core не всегда может выполнить сложные запросы, связанные с группировкой, на уровне базы данных, что может привести к неожиданным ошибкам.
- Проблемы с проекцией: Если проекция не может быть нормально обработана EF Core, это может вызывать подобные ошибки.
Рабочее Решение: Сырой SQL-запрос
Вы сумели решить проблему, используя сырой SQL-запрос. Этот метод не только обходит ограничения EF Core, но и позволяет вам иметь больше контроля над запросом. Вот как ваш окончательный запрос выглядит:
IQueryable<Attempt> GetBestAttempts() =>
Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSqlRaw(db.Attempt, @"
SELECT a.*
FROM ""Attempt"" a
INNER JOIN (
SELECT ""UserId"", MAX(""score"") AS ""MaxScore""
FROM ""Attempt""
GROUP BY ""UserId""
) maxAttempts ON a.""UserId"" = maxAttempts.""UserId"" AND a.""score"" = maxAttempts.""MaxScore""
WHERE a.""Id"" = (
SELECT MIN(innerA.""Id"")
FROM ""Attempt"" innerA
WHERE innerA.""UserId"" = a.""UserId"" AND innerA.""score"" = a.""score""
)
ORDER BY a.""score"" DESC, a.""UserId"";
");
Данная конструкция SQL-запроса предоставляет вам полноценный доступ к функциональности базы данных без необходимости завязываться на ограничения LINQ.
Заключение
Хотя использование сырого SQL-запроса может показаться менее удобным, это часто наиболее эффективный способ решения задач с комплексными выборками и агрегациями в EF Core. Теперь вы можете получить данные без ошибок, устойчиво и быстро. В будущем, если вы решите продолжать встраивать LINQ-запросы, старайтесь упрощать их структуру или избегайте сложной группировки, если проблема продолжает возникать.
Если вам нужно больше информации о работе с EF Core или другими аспектами C#, не стесняйтесь обращаться за помощью к сообществу или документации.