迁移到 .net core 3.1 后 EF OrderBy 出现问题
Problem with EF OrderBy after migration to .net core 3.1
考虑这段代码:
_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 日期排序消息,并在每个对话中 select 最后一条消息。
问题是这段代码有效,而且更多的是将它全部翻译成纯 T-Sql(我使用 SQL Server Profiler 来检查)。但后来我决定将我的项目从 Core 2.1 转移到 3.1,现在我得到了这个:
The LINQ expression '(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)
could not be translated. Either rewrite the query in a form that can
be translated, or switch to client evaluation explicitly by inserting
a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for
more information.
如有解决此问题的任何想法,我们将不胜感激。
P.S。我知道我可以深入研究 T-SQL 并为其编写存储过程,但我仍在寻找一种使用 Linq to Entity 实现它的方法。
不幸的是,目前 EF Core 3.0 / 3.1 仅支持 GroupBy
的服务器翻译以及键/聚合的投影(类似于 SQL)。
这是不可接受的,因为尽管 EF6 也没有客户端评估,但它能够成功翻译此类查询。
在 GroupBy
翻译问题得到解决之前,解决方法是用 2 个相关子查询替换 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));
考虑这段代码:
_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 日期排序消息,并在每个对话中 select 最后一条消息。 问题是这段代码有效,而且更多的是将它全部翻译成纯 T-Sql(我使用 SQL Server Profiler 来检查)。但后来我决定将我的项目从 Core 2.1 转移到 3.1,现在我得到了这个:
The LINQ expression '(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)
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
如有解决此问题的任何想法,我们将不胜感激。
P.S。我知道我可以深入研究 T-SQL 并为其编写存储过程,但我仍在寻找一种使用 Linq to Entity 实现它的方法。
不幸的是,目前 EF Core 3.0 / 3.1 仅支持 GroupBy
的服务器翻译以及键/聚合的投影(类似于 SQL)。
这是不可接受的,因为尽管 EF6 也没有客户端评估,但它能够成功翻译此类查询。
在 GroupBy
翻译问题得到解决之前,解决方法是用 2 个相关子查询替换 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));