我正在使用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的一个已知问题。现在寻找可能的解决方法。
答案 0 :(得分:1)
Linq无法将First()
转换为SQL。您需要使用FirstOrDefault()
。
...
PracticeArea = am.FirstOrDefault().PracticeArea,
Location = am.FirstOrDefault().Location,
Publication = am.FirstOrDefault().Publication,
...
编辑为避免错误而提出的解决方案:
需要访问以下内容: context.PracticeAreas
,context.Locations
和context.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<...>
另一方面,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.