EF Core ошибка с запросом с группировкой и Count()
Нужно получить последний елемент с групп, группируя по нескольким полям. Используя эту кверю:
using(var context = new EFCoreDemoContext())
{
IQueryable<Book> query = context.Books.GroupBy(x => new { x.Year, x.Month })
.Select(y => y.OrderByDescending(z => z.Date ).FirstOrDefault());
//it's ok
var data = query.ToArray()
//it's System.InvalidOperationException: 'Nullable object must have a value. exception here
var count = query.Count();
}
Получаю ошибку "System.InvalidOperationException: 'Nullable object must have a value.'". Использую Net 6 + "Microsoft.EntityFrameworkCore" Version="6.0.6".
Что странно, кверя работает ок с query.ToArray()
Есть идеи?
Минимальный пример:
using Microsoft.EntityFrameworkCore;
Console.WriteLine("Start");
using(var context = new EFCoreDemoContext())
{
IQueryable<Book> query = context.Books.GroupBy(x => new { x.Year, x.Month })
.Select(y => y.OrderByDescending(z => z.Date ).FirstOrDefault());
var count = query.Count();
}
Console.WriteLine("End");
public class EFCoreDemoContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=.\;Database=EFCoreDemo;Trusted_Connection=True;MultipleActiveResultSets=true");
}
}
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public DateTime Date { get; set; }
public int Year { get; set; }
public int Month { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
}
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Book> Books { get; set; } = new List<Book>();
}
Ответы (1 шт):
Поведение запроса с GroupBy соответствует спецификации.
Когда к первоначальному запросу применяется вызов Count() провайдер не понимает, что ему делать с теми данными, которые получаются в Select. Ведь они не используются в Count.
Я не совсем понимаю проблему.
Нужно получить общее количество сгрупированных данных - хорошо, получаем его первым запросом.
Потом нужно получить сами данные с определённой страницы - хорошо, получаем их вторым запросом. Материализуем в массив.
var query = context.Books.GroupBy(x => new { x.Year, x.Month });
IQueryable<Book> pagedQuery = query
//.OrderBy(x => x.Key.Year)
.Skip(10).Take(10)
.Select(y => y.OrderByDescending(z => z.Date).FirstOrDefault());
var count = query.Count();
var array = pagedQuery.ToArray();
Можно включить логирование запросов, добавив в метод OnConfiguring следующую строку:
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Это позволит легко экспериментировать и отлаживать запросы.
В частности, выдаётся подсказка, что при использовании методов Take/Skip крайне желательно использовать OrderBy.