• EF Core 难道不支持GroupBy吗?


       最近在修改一个.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));
    }
  • 相关阅读:
    Project Euler 5 Smallest multiple
    Project Euler 4 Largest palindrome product
    CSS3新增的伪类选择器
    CSS选择器
    HTML 5 表单相关元素和属性
    HTML 5中的新特性
    HTML表格相关元素
    列表相关元素及其属性
    HTML行内元素、块状元素和行内块状元素的区分
    对HTML的大致了解
  • 原文地址:https://www.cnblogs.com/ZQWelcomeIndex/p/14856947.html
Copyright © 2020-2023  润新知