Linq Group通过多列投影到新对象

时间:2020-07-14 16:55:05

标签: c# linq

我正在使用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的列表,以获取具有Count Sum的RelatedOrganisationMentions对象的列表,如下所示:

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:new { 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索引)}, ElementSelector:(EntityShaperExpression: 实体类型:OrganisationMention ValueBufferExpression: (ProjectionBindingExpression:空投影成员) IsNullable:False)) .First()'无法翻译。以可以翻译的形式重写查询,或显式切换到客户端评估 通过插入对AsEnumerable(),AsAsyncEnumerable()的调用, ToList()或ToListAsync()。看到 https://go.microsoft.com/fwlink/?linkid=2101038了解更多信息。 在

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

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

2 个答案:

答案 0 :(得分:1)

Linq无法将First()转换为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);

答案 1 :(得分:1)

简短回答

通常通常首先Select所需的属性,然后FirstOrDefault

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

错误说明

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

IEnumerable是由您自己的进程执行的。它拥有所有内容以获取第一个元素,一旦有了它,只要有元素,就可以获取下一个元素。

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

在更高级别上,这是使用foreach完成的。在每个不返回IEnumerable<...>

的LINQ方法中也可以做到这一点。

另一方面,IQueryable<...>是由另一个进程(通常是数据库管理系统)处理的。它包含一个Expression和一个Provider。表达式以某种通用格式表示查询,提供程序知道谁必须执行查询,以及使用哪种语言与数据库通信(通常是SQL)。

如果仔细观察LINQ方法,您会发现有两组:返回IQueryable<...>(或IEnumerable<...>)的组,以及其他两组。第一组方法使用延迟执行(有时称为延迟执行)。在这些LINQ方法的每个描述中,您都会找到该术语。

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

仅当您开始枚举时,才能使用GetEnumerator()或使用foreach或第二组的任何LINQ方法(例如ToList(), } , Any()`等。表达式将发送到提供程序,该提供程序将尝试将其转换为SQL并在数据库中执行查询。

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

您的提供者无法翻译您对First()的用法:

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