迁移到 .net core 3.1 后 EF OrderBy 出现问题

Posted

技术标签:

【中文标题】迁移到 .net core 3.1 后 EF OrderBy 出现问题【英文标题】:Problem with EF OrderBy after migration to .net core 3.1 【发布时间】:2019-12-15 17:26:28 【问题描述】:

考虑这段代码:

_dbContext.Messages
    .GroupBy(m => new
        
            MinId = m.SenderId <= m.RecipientId ? m.SenderId : m.RecipientId,
            MaxId = m.SenderId > m.RecipientId ? m.SenderId : m.RecipientId
        )
        .Select(gm => gm.OrderByDescending(m => m.SentAt).FirstOrDefault());

这样,无论是谁发送消息,我都会按用户的 ID 对用户的所有对话进行分组。然后我在组内按 SentAt 日期排序消息,并从每个对话中选择最后一条消息。 问题是这段代码有效,而且更多的是它把它全部翻译成纯 T-Sql(我使用 SQL Server Profiler 来检查)。但后来我决定将我的项目从 Core 2.1 迁移到 3.1,现在我得到了这个:

LINQ 表达式'(GroupByShaperExpression: KeySelector:

new  
    MinId = (CASE
        WHEN ((m.SenderId) <= (m.RecipientId)) THEN (m.SenderId)
        ELSE (m.RecipientId)
    END), 
    MaxId = (CASE
        WHEN ((m.SenderId) > (m.RecipientId)) THEN (m.SenderId)
        ELSE (m.RecipientId)
    END)
 , 
ElementSelector:(EntityShaperExpression: 
    EntityType: Message
    ValueBufferExpression: 
        (ProjectionBindingExpression: EmptyProjectionMember)
    IsNullable: False
)
).OrderByDescending(m => m.SentAt)

无法翻译。要么重写查询的形式,可以 被翻译,或通过插入显式切换到客户评估 调用 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync()。见https://go.microsoft.com/fwlink/?linkid=2101038 更多信息。

任何解决此问题的想法将不胜感激。

附:我知道我可以深入研究 T-SQL 并为其编写存储过程,但我仍在寻找一种使用 Linq to Entity 实现它的方法。

【问题讨论】:

如果你可以相信最近的消息也有最高的MessageID,你可以得到每个MinId,MaxId对的Max(MessageID),并通过Contains查询检索所属的消息(all in 1表达)。与@Ivan 所展示的其他不可避免的变通方法相比,这产生了更体面的 SQL。然而,这与为代理键添加商业意义相近,令人不安。 @GertArnold 谢谢。但问题是 - 我的 MessageId 是 Guid 类型。 呵呵,这个解决方案就这么多。 【参考方案1】:

不幸的是,目前 EF Core 3.0 / 3.1 仅支持 GroupBy 的服务器翻译,并带有键/聚合的投影(类似于 SQL)。

这是不可接受的,因为尽管 EF6 也没有客户端评估,但它能够成功翻译此类查询。

GroupBy 翻译问题得到解决之前,解决方法是将GroupBy 替换为两个相关的子查询——第一个仅包含分组键,第二个包含组元素。

在你的情况下,它会是这样的:

var source = _dbContext.Messages
    .Select(m => new
    
        Key = new
        
            MinId = m.SenderId <= m.RecipientId ? m.SenderId : m.RecipientId,
            MaxId = m.SenderId > m.RecipientId ? m.SenderId : m.RecipientId
        ,
        Message = m
    );

var query = source.Select(e => e.Key).Distinct()
    .SelectMany(key => source
        .Where(e => e.Key.MinId == key.MinId && e.Key.MaxId == key.MaxId)
        .Select(e => e.Message)
        .OrderByDescending(m => m.SentAt)
        .Take(1));

【讨论】:

我已经从我的question 重定向到这里。第一个查询不会检索客户端上的所有数据表吗?这可能是有害的。 @OlivierMATROT 不,这就是整个想法 - 上述技术创建了服务器可翻译查询。请注意,sourceIQueryable&lt;T&gt;(因此不执行)并且在 GroupBy 之前包含公共查询部分(如果有过滤器)。 好的,我现在知道了。仍在试图围绕您对我的问题的解决方案进行思考。 @OlivierMATROT 将其应用于您的场景将是这样的:var sourceQuery = context.CallbackHistoryDbSet .Where(e =&gt; e.CompanyId == companyId) .Select(e =&gt; new Key = new e.Caller.PhoneNumber , Entry = e ); var query = sourceQuery .Select(e =&gt; e.Key).Distinct() .SelectMany(key =&gt; sourceQuery .Where(e =&gt; e.Key.PhoneNumber == key.PhoneNumber) .Select(e =&gt; e.Entry) .OrderByDescending(e =&gt; e.LastCallTimeStamp).Take(1)) .Include(e =&gt; e.Caller) .Take(5) .AsNoTracking(); 不错。坦克。

以上是关于迁移到 .net core 3.1 后 EF OrderBy 出现问题的主要内容,如果未能解决你的问题,请参考以下文章

从 .NET Core 2.1 迁移到 .NET Core 3.1 后,publish 有问题

从 Body 迁移 .NET Core 2.2 到 3.1 集成测试始终为空

EF Core 5.0.4 - 从核心 3.1 升级后,通过 Include() 的急切加载不起作用

从.NET Core 3.1(Docker)迁移后,.NET 6 应用程序日志为 JSON 格式 [重复]

Azure Devops - 在 Nuget 包中从 .NET Core 3.1 迁移到 .NET 5 的兼容性问题

EF Core ASP.Net Core 编辑迁移