C# EF Core 8 – Не удается решить “Указанный ключ ‘EmptyProjectionMember’ не был найден в словаре”

Вопрос или проблема

Я хотел бы начать с того, что я совершенно нов в 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 не может корректно обработать проектирование результатов при выполнении выполнения группировки и выборки. Это может произойти по ряду причин:

  1. Неправильная структура запроса: EF Core не всегда может выполнить сложные запросы, связанные с группировкой, на уровне базы данных, что может привести к неожиданным ошибкам.
  2. Проблемы с проекцией: Если проекция не может быть нормально обработана 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#, не стесняйтесь обращаться за помощью к сообществу или документации.

Оцените материал
Добавить комментарий

Капча загружается...