在EF Core中,在SelectMany之后使用GroupBy。

huangapple go评论65阅读模式
英文:

GroupBy after SelectMany in EF Core

问题

考虑以下查询,该查询列出了学校以及那些在该学校开始的月份的学生的月度统计信息。

EF Core 在这个查询上出现问题:

System.InvalidOperationException: 无法在投影中转换集合子查询,因为要么父查询,要么子查询没有投影所需的信息,以在客户端正确生成结果。当尝试在无键实体类型上进行关联时可能会出现此问题。这也可能在“Distinct”之前的某些投影情况下,或在“GroupBy”情况下的某些分组键形状中发生。这些应该要么包含操作应用于的实体的所有键属性,要么只包含简单的属性访问表达式。

然而,如果我们根据教师表而不是学生表计算每月统计数据,它能够处理类似的查询:

我认为这是因为 EF Core 无法处理 .SelectMany.GroupBy 之前的情况。EF Core 是否能够处理这样的查询,例如通过直接从学校导航到学生或以其他方式重写使用其他操作的 SelectMany?

我已经了解到以下可能性,它们对我来说都不可行:

  1. 使用客户端端点评估
  2. 将查询拆分成 N 个查询
  3. 重构数据库

为了解决这个问题,您可以尝试使用导航属性从学校直接访问学生,而不是使用 .SelectMany。这样,EF Core可以更容易地处理查询,而不会引发异常。下面是修改后的查询示例:

_dbContext.Schools
  .Select(p => new MySchoolView
  {
    School = p,
    MonthlyStatistics = p.Students
      .GroupBy(d => new { d.StartDate.Month, d.StartDate.Year })
      .Select(g => new MonthlyStatisticsView
      {
        Year = g.Key.Year,
        Month = g.Key.Month,
        Count = g.Count(),
        Dollars = g.Sum(d => d.Tuition)
      }).ToList()
  })

通过这种方式,您直接从学校导航到学生,而不是通过教师和 .SelectMany。这应该可以让EF Core更好地处理查询。

英文:

Consider the following query, which lists the Schools, along with monthly statistics for the students that started that month at the school.

 _dbContext.Schools
  .Select(p => new MySchoolView
  {
    School= p,
    MonthlyStatistics = p.Teacher
      .SelectMany(d => d.Students)
      .GroupBy(d => new { d.StartDate.Month, d.StartDate.Year })
      .Select(g => new MonthlyStatisticsView
        {
          Year = g.Key.Year,
          Month = g.Key.Month,
          Count = g.Count(),
          Dollars = g.Sum(d => d.Tuition)
        }).ToList()
  })

EF Core chokes on this query:
> System.InvalidOperationException: Unable to translate a collection subquery in a projection since either parent or the subquery doesn't project necessary information required to uniquely identify it and correctly generate results on the client side. This can happen when trying to correlate on keyless entity type. This can also happen for some cases of projection before 'Distinct' or some shapes of grouping key in case of 'GroupBy'. These should either contain all key properties of the entity that the operation is applied on, or only contain simple property access expressions.

However, it is able to handle a similar query, if we calculate the monthly statistics based on the Teacher table, instead of the student table:

 _dbContext.Schools
  .Select(p => new MySchoolView
  {
    School= p,
    MonthlyStatistics = p.Teacher
      .GroupBy(d => new { d.EmploymentDate.Month, d.EmploymentDate.Year })
      .Select(g => new MonthlyStatisticsView
        {
          Year = g.Key.Year,
          Month = g.Key.Month,
          Count = g.Count(),
          Dollars = g.Sum(d => d.Salary)
        }).ToList()
  })

I believe that this is due to EF Core not being able to handle the .SelectMany before the .GroupBy. Is there a way that EF Core can handle a query like this, e.g. by directly navigating from the School to the Student or otherwise rewriting the SelectMany using other operations?

I already know about the following possibilities, which are not viable solutions for me:

  1. Using client side evaluation instead
  2. Breaking the query into N queries
  3. Restructuring the database

答案1

得分: 1

我建议另一种方法,仍然具有良好的性能,但可翻译。在客户端上准备分组数据,然后进行后处理。

var query = 
    from s in _dbContext.Schools
    from t in s.Teacher
    from st in t.Students
    group st by new { s.Id, st.StartDate.Month, st.StartDate.Year } into g
    select new 
    {
        Id = g.Key.Id,
        Year = g.Key.Year,
        Month = g.Key.Month,
        Count = g.Count(),
        Dollars = g.Sum(d => d.Tuition)
    } into d
    join s in _dbContext.Schools on d.Id equals s.Id
    select new 
    {
        School = s,
        Details = d
    };

var rawData = query.ToList();

var result = rawData
    .GroupBy(r => r.School.Id)
    .Select(g => new MySchoolView
    {
        School = g.First().School,
        MonthlyStatistics = g.Select(s => new MonthlyStatisticsView
        {
            Year = s.Details.Year,
            Month = s.Details.Month,
            Count = s.Details.Count,
            Dollars = s.Details.Dollars
        }).ToList()
    });
英文:

I would suggest another approach which is still performant but translatable. Preparing grouped data and then post process on the client side.

var query = 
    from s in _dbContext.Schools
    from t in s.Teacher
    from st in t.Students
    group st by new { s.Id, st.StartDate.Month, st.StartDate.Year } into g
    select new 
    {
        Id = g.Key.Id,
        Year = g.Key.Year,
        Month = g.Key.Month,
        Count = g.Count(),
        Dollars = g.Sum(d => d.Tuition)
    } into d
    join s in _dbContext.Schools on d.Id equals s.Id
    select new 
    {
        School = s,
        Details = d
    };

var rawData = query.ToList();

var result = rawData
    .GroupBy(r => r.School.Id)
    .Select(g => new MySchoolView
    {
        School = g.First().School,
        MonthlyStatistics = g.Select(s => new MonthlyStatisticsView
        {
            Year = s.Details.Year,
            Month = s.Details.Month,
            Count = s.Details.Count,
            Dollars = s.Details.Dollars
        }).ToList()
    });

huangapple
  • 本文由 发表于 2023年6月2日 03:47:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76385240.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定