【问题标题】:Linq GroupBy multiple columns with Projection to new objectLinq GroupBy 多列投影到新对象
【发布时间】:2020-07-14 16:55:05
【问题描述】:

我正在使用 Microsoft.EntityFrameworkCore 版本 3.1.3:

我有以下对象:

public class OrganisationMention
{
    public int Id { get; set; }
    public int OrganisationId { get; set; }
    public Organisation Organisation { get; set; }
    public int PublicationId { get; set; }
    public Publication Publication { get; set; }
    public int LocationId { get; set; }
    public Location Location { get; set; }
    public int PracticeAreaId { get; set; }
    public PracticeArea PracticeArea { get; set; }
    public int Count { get; set; }
    public int MarketAverage { get; set; }
}

我想按 Publication\Location\PracticeArea 聚合一个 OrganisationMentions 列表,以获取一个具有计数总和的相关组织提及对象列表,如下所示:

public class RelatedOrganisationMentions
{
    public int PublicationId { get; set; }
    public Publication Publication { get; set; }
    public int LocationId { get; set; }
    public Location Location { get; set; }
    public int PracticeAreaId { get; set; }
    public PracticeArea PracticeArea { get; set; }
    public int Count { get; set; }
    public int MarketAverage { get; set; }
}

如果我使用以下 LINQ 查询,我会收到与 Publication\Location\PracticeArea 对象的投影有关的可怕错误。如果我省略它们,则查询返回正常。但是,我确实希望它们回到聚合结果中,并且可以保证如果 PublicationId 相同,每个 Publication 对象都是同一个实体:

        IQueryable<RelatedOrganisationMentions> relatedOrganisationMentions = collection
            .GroupBy(m => new { m.LocationId, m.PublicationId, m.PracticeAreaId })
            .Select(am => new RelatedOrganisationMentions
            {
                PracticeArea = am.First().PracticeArea, //Causes long and horrid error
                Location = am.First().Location,         //Causes long and horrid error
                Publication = am.First().Publication,   //Causes long and horrid error
                PracticeAreaId = am.Key.PracticeAreaId,
                PublicationId = am.Key.PublicationId,
                LocationId = am.Key.LocationId,
                Count = am.Sum(x => x.Count)
            })
        .OrderBy(r => r.Publication.Description)
        .ThenBy(r => r.PracticeArea.Description);

    return await relatedOrganisationMentions.ToListAsync(token).ConfigureAwait(false);

请问如何按多列分组并保留聚合投影中的对象?

错误

System.InvalidOperationException:LINQ 表达式 '(GroupByShaperExpression:KeySelector:新{ LocationId = EntityMaterializerSource.TryReadValue(grouping.Key, 0, 属性: OrganisationMention.LocationId (int) 必需的 FK 索引), PublicationId = EntityMaterializerSource.TryReadValue(grouping.Key, 1, 属性: OrganisationMention.PublicationId (int) 必需的 FK 索引), PracticeAreaId = EntityMaterializerSource.TryReadValue(grouping.Key, 2, 属性: OrganisationMention.PracticeAreaId (int) 必需的 FK 索引) }, 元素选择器:(实体形状表达式: 实体类型:组织提及 值缓冲区表达式: (ProjectionBindingExpression:EmptyProjectionMember) IsNullable: 假 ) ) .First()' 无法翻译。要么以可翻译的形式重写查询,要么显式切换到客户端评估 通过插入对 AsEnumerable()、AsAsyncEnumerable() 的调用, ToList() 或 ToListAsync()。看 https://go.microsoft.com/fwlink/?linkid=2101038 了解更多信息。 在

更新 1:看起来这是 efcore 3.1 的一个已知问题,现在正在寻找可能的解决方法。

https://github.com/dotnet/efcore/issues/12088

【问题讨论】:

  • GroupBy EF 核心 3 中的支持仅限于最简单的情况(按原始属性分组,只选择关键属性 + 聚合)。对于其他任何事情,您都必须找到解决方法。不仅仅是First 不可翻译(尽管它不是),无论你尝试什么,整个GroupBy 都会失败。

标签: c# linq


【解决方案1】:

First() 无法由 Linq 转换为 SQL。您需要使用FirstOrDefault()

...
PracticeArea = am.FirstOrDefault().PracticeArea,
Location = am.FirstOrDefault().Location,
Publication = am.FirstOrDefault().Publication,
...

编辑避免错误的建议解决方案:

需要访问: context.PracticeAreascontext.Locationscontext.Publications

IQueryable<RelatedOrganisationMentions> relatedOrganisationMentions = collection
    .GroupBy(
        m => new { m.LocationId, m.PublicationId, m.PracticeAreaId },
        (k, g) => new{ k.LocationId, k.PublicationId, k.PracticeAreaId, Count = g.Sum(x => x.Count) }
    )
    .Select(am => new RelatedOrganisationMentions
    {
        PracticeArea = context.PracticeAreas.FirstOrDefault(x => x.Id == am.PracticeAreaId),
        Location = context.Locations.FirstOrDefault(x => x.Id == am.LocationId),
        Publication = context.Publications.FirstOrDefault(x => x.Id == am.PublicationId),
        PracticeAreaId = am.PracticeAreaId,
        PublicationId = am.PublicationId,
        LocationId = am.LocationId,
        Count = x.Count
    })
    .OrderBy(r => r.Publication.Description)
    .ThenBy(r => r.PracticeArea.Description);

【讨论】:

  • 恐怕同样的错误。 “.FirstOrDefault()' 无法翻译。要么将查询重写为可翻译的形式”
  • @sarin 请检查编辑。我没有测试过这段代码,但它应该适合你。
  • 谢谢!我在 Count = am.Sum(x => x.Count) 上遇到错误,大概是因为它没有包含在 GroupBy 的第二个子句中。所以添加了: new { k.LocationId, k.PublicationId, k.PracticeAreaId, Count = g.Sum(x => x.Count) } 。它现在正在工作。现在在选择中返回 PracticeAreas\Locations\Publications 的方式,它们会为每一行执行一次吗?即这会产生许多单独的选择语句并且可能效率低下吗?发布此查询的单独语句是否可以提高性能?
  • 我忘记了Count 列:D。将更新答案.... 该解决方案将生成一个LEFT JOIN 来检索PracticeAreas\Locations\Publications;现在我不知道如何以更好的方式进行此查询。尝试用纯 SQL 自己编写以进行比较;你需要做这样的事情来选择PracticeAreas\Locations\Publications 列。您可以启用EF Logging 来显示生成的SQL。
【解决方案2】:

简答

通常先Select你想要的属性就足够了,然后是FirstOrDefault

PracticeArea = am.Select(amItem => amItem.PracticeArea)
    .FirstOrDefault(),
Location = am.Select(amItem => amItem.Location)
    .FirstOrDefault(),
Publication = am.Select(amItem => amItem.Publication)
    .FirstOrDefault(),

错误说明

您必须注意 IEnumerable&lt;...&gt; 和 'IQueryable<...>` 之间的区别。

IEnumerable 旨在由您自己的进程执行。它保存了获取第一个元素的所有内容,一旦你得到它,你就可以获取下一个元素,只要有元素。

在最低级别,这是使用GetEnumerator() 完成的。获得枚举器后,您可以重复使用 MoveNext()Current 来逐一访问序列的元素。

在更高级别上,这是使用foreach 完成的。这也在每个不返回 IEnumerable&lt;...&gt; 的 LINQ 方法中完成

另一方面,IQueryable&lt;...&gt; 意味着由另一个进程处理,通常是数据库管理系统。它拥有一个Expression 和一个Provider。 Expression 以某种通用格式表示查询,Provider 知道谁必须执行查询,以及使用什么语言与数据库通信(通常是 SQL)。

如果您仔细查看 LINQ 方法,您会发现有两个组:返回 IQueryable&lt;...&gt;(或 IEnumerable&lt;...&gt;)的组和其他组。第一组的方法使用延迟执行(有时称为延迟执行)。在这些 LINQ 方法的每个描述中,您都会找到这个术语。

这些函数不会与数据库通信,它们只会更改表达式。连接这些函数并不是一项昂贵的操作。

仅当您开始枚举时,允许使用GetEnumerator() 或使用foreach 或第二组的任何LINQ 方法,如ToList()、``Count(), Any( )` 等。表达式被发送给提供者,提供者将尝试将其转换为 SQL 并在数据库中执行查询。

问题在于 Provider 不知道如何将所有方法转换为 SQL。例如,它不知道您自己的方法。此外,有几种 LINQ 方法不受支持。见List of supported and unsupported LINQ methods (LINQ to entities)

您的 Provider 无法翻译您对 First() 的使用:

...First()' could not be translated.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多