EF Core 难道不支持GroupBy吗?

摘要:
我要使用的第一个解决方案是向每个条件查询字段添加一个索引,因此可以肯定,GroupBY之后的操作是在内存中处理原始EF查询,如下所示:vargroupList_one=dbConContext.TMemberWelcomeLog.AsNoTracking()。其中(p=>p.Status==0&=DateTime.Now)。GroupBY(p=>

   最近在修改一个.NET Core的项目,其中ORM用的EF Core,在一次查询分页中,遇到了一个很奇怪的问题,每次查询都很慢,明明已经按照某个编号字段Group By并且还做了分页,为啥查询还这么的慢呢?

首选我想当的解决方案就是为 每个条件查询字段添加索引,但是依然无效,还是很慢;然后查看log日志,仔细核对EF生成的sql,发现了生成的sql根本就没有Group by 以及后面的分页操作也没有生成,sql只是到where条件判断之后就结束了,相当于查询了所有结果,当然展示的数据是我们想要的结果,所以可以肯定的是Group BY 之后的操作是在内存中处理的

原始EF 查询如下

var groupList_one = dbConContext.TMemberWelcomeLog.AsNoTracking().Where(p => p.Status == 0 &&
                                                                   p.MerchantCode == "SH202009094127602" &&
                                                                   p.CreateDateTime >= startTime &&
                                                                   p.CreateDateTime <= DateTime.Now).
                                                                   GroupBy(p => p.MemberCode);
var list_one = await groupList_one.OrderByDescending(r => r.Count()).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
var total_one = list_one.Count();

上面的语句生成的sql如下:

 SELECT [t].[Id], [t].[CreateDateTime], [t].[EnterDateTime], [t].[EnterImg], [t].[LeaveDateTime], [t].[LeaveImg], [t].[MemberCode], [t].[MerchantCode], [t].[Status], [t].[StayTime], [t].[StoreCode], [t].[UpdateDateTime]
 FROM[T_MemberWelcomeLog] AS[t]
 WHERE((([t].[Status] = 0) AND([t].[MerchantCode] = N'SH202009094127602')) AND([t].[CreateDateTime] >= @__startTime_0)) 
 AND([t].[CreateDateTime] <= GETDATE())

从上面的语句来看,很显然是没有生成Group by及以后的分页语句,为什么会是这样呢???

注意: EF CORE 3.0及以上版本会报错:Unable to translate the given 'GroupBy' pattern. Call 'AsEnumerable' before 'GroupBy' to evaluate it client-side

于是查询官方文档【客户端与服务器评估

大概意思是:

EF CORE会尽可能的尝试服务器评估,生成等效的数据库查询SQL,但是有些方法是客户端特有的处理方式,例如在客户端写了一个特殊的方法,去处理EFCore查询中的某一个字段,这个时候服务端是无法预知结果,并转换成对应的sql,这个时候EF CORE会报上面的那个错
那么如何处理上面这个问题呢?官方给出了解决方案,就是需要显示客户端评估,官方话语是:在这种情况下,通过调用 AsEnumerable 或 ToList 等方法(若为异步,则调用 AsAsyncEnumerable 或 ToListAsync),以显式方式选择进行客户端评估,这个结果就是我们上面的查询列子相同,会把AsEnumerable()前面的结果从数据库查询出来,加载到内存中,然后在内存中去做分组及分页的操作

 

说了这么多,貌似跟上面的查询Group by 又有什么关系呢?为何Group by服务端会无法生成对应的sql呢?

    我们仔细思考一下 GroupBy(p => p.MemberCode)返回的是什么对象呢?IQueryable<IGrouping<TKey, TSource>>对象,而sql中 group by 查询必须是包含在聚合函数或 GROUP BY 子句中,所以是按照sql去查询是无法返回TSource这个对象的,这个时候程序就会需要显示客户端评估,才能解决

这个时候有的小伙伴灵机一动,将上面的查询代码改成如下:

 var groupList_one = dbConContext.TMemberWelcomeLog.AsNoTracking().Where(p => p.Status == 0 &&
                                                                     p.MerchantCode == "SH202009094127602" &&
                                                                     p.CreateDateTime >= startTime &&
                                                                     p.CreateDateTime <= DateTime.Now).
                                                                     GroupBy(p => p.MemberCode).
                                                                     Select(r => new { key = r.Key, count = r.Count() });
 var list_one = await groupList_one.OrderByDescending(r => r.Count()).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
 var total_one = list_one.Count();

 

其实这样就对了,生成的SQL如下:

SELECT [t].[MemberCode] AS [Key], COUNT(*) AS [Count]
FROM[T_MemberWelcomeLog] AS[t]
WHERE((([t].[Status] = 0) AND([t].[MerchantCode] = N'SH202009094127602')) AND([t].[CreateDateTime] >= @__startTime_0)) AND([t].[CreateDateTime] <= GETDATE())
GROUP BY[t].[MemberCode]
ORDER BY COUNT(*) DESC
OFFSET 0 ROWS FETCH NEXT @__p_1 ROWS ONLY

 

在官方文档中也可以找到对应的示例【复杂查询
可以变换成如下方案:

var groupList_two = from p in dbConContext.TMemberWelcomeLog
                      where p.Status == 0 &&
                            p.MerchantCode == "SH202009094127602" &&
                            p.CreateDateTime >= startTime &&
                            p.CreateDateTime <= DateTime.Now
                      group p by p.MemberCode
                      into g
                      select new { g.Key, Count = g.Count() };                                                
var list_two = groupList_two.OrderByDescending(r => r.Count).Skip((pageIndex - 1) * pageSize).Take(pageSize);
总结

在EF CORE查询中,一定要多去想想,客户端的方法是否真的合理吗?这样是否能生成对应的sql吗?不过现在EF CORE3.0及以上版本是可以在运行的时候,抛出异常,并且在EF CORE 3.0早期版本也是可以添加警告,官方示例代码:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
  optionsBuilder
   .UseSqlServer(@"Server=(localdb)mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
   .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}

免责声明:文章转载自《EF Core 难道不支持GroupBy吗?》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇mybatis 批量插入和where条件使用接口testing需要的技能下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

电子签名实现的思路、困难及解决方案

        在办公自动化的流程中希望实现电子签名。        思路:            1、图片的存放:安全起见存放在库中为宜。最好不能被轻易下载。            2、使用的过程:显示一个密码框和“签名”按钮,输入密码并按下按钮后,如果正确,隐藏输入框和按钮,显示图片。            3、我的所有控件都是通过解析xml后动态生成...

[ZZ] NumPy 处理数据

NumPy-快速处理数据--ndarray对象--数组的创建和存取https://www.cnblogs.com/moon1992/p/4946114.html NumPy-快速处理数据--ndarray对象--数组的创建和存取  本文摘自《用Python做科学计算》,版权归原作者所有。 NumPy为Python提供了快速的多维数组处理的能力,而SciP...

MyBatis:条件构造器QueryWrapper方法详解

QueryWrapper 说明:      继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取. 测试用表: 1. eq、ne 说明: eq:等于,ne:不等于 测试: @Test pub...

pixijs shader 设置透明度的方法

pixijs shader 设置透明度的方法 precision mediump float; varying vec2 vTextureCoord; varying vec4 vColor; uniform sampler2D uSampler; uniform sampler2D noise; uniform float customUniform...

Linux系统定时任务启动

 分类: linux,shell,python cron是一个linux下的定时执行工具,可以在无需人工干预的情况下运行作业。由于Cron 是Linux的内置服务,但它不自动起来,可以用以下的方法启动、关闭这个服务: /sbin/service crond start //启动服务 /sbin/service crond stop //关闭服务 /sbin...

Django 之 Form 组件

常用功能 From 组件主要有以下几大功能: 生成 HTML 标签 验证用户数据(显示错误信息) HTML Form 提交保留上次提交数据 初始化页面显示内容 小试牛刀 下面我们通过 Form 组件来生成 HTML 标签和验证用户提交的数据以及保留上次提交的数据。 创建 Form 类 form_verify.py from django import...