EF分页问题探讨之 OrderBy
EntityFramework
应用场景
最近被应用程序中页面加载慢的问题所折磨,看似容易的问题,其实并不容易(已经持续两天时间了),经过“侦查”,发现了两个“嫌疑犯”:
- EntityFramework 生成执行的 SQL
- 数据库中索引创建
在《程序员眼中的 SQL Server-非聚集索引能给我们带来什么?》这一篇博文中,我把怀疑对象放在了数据库索引上,其实索引只是一方面的问题,最后通过仔细观察 EntityFramework 生成执行的 SQL 代码(EntityFramework 中如何查看执行 SQL?),主要查看的是获取分页列表的 SQL,最后发现,在分页列表获取的时候,执行的 SQL 是所有条件,并没有分页,也没有 OrderBy,这是个奇怪的问题,因为结果是分页的,但是在数据库执行的 SQL 代码,却是获取所有列表,我们来看一下,这是个什么情况???
上面所说的有点乱,我再重述下应用场景:我们使用 EntityFramework 做分页查询,一般一行代码就可以搞定(Linq 的强大),比如:
var list = query.where(...).OrderByDescending(...).Skip(...).Take(...).ToList();
对,你没看错,就是这么简单,这也是我们一般所使用的方法,在一般应用中可能不会出现什么问题,但是当数据量非常大的时候,而且你的代码经过不断的改写,这时候可能就有些问题了,在我现在的应用项目中,主要是 OrderByDescending 这个排序没有在 SQL 执行,因为这个原因,还导致后面的分页没有执行,但是运行的结果是分页的,查看到的执行 SQL 是获取所有 where 条件下的列表,也就是说分页并不是在数据库中执行的,而是获取到内存中,然后再进行的分页,这导致两个问题,一个就是内存飙升(获取列表数很大的情况),还有一个当然是页面访问慢。
问题分析
为了方面大家理解,我先贴一下现在应用程序中分页部分的代码:
protected override IEnumerable<TAggregateRoot> FindAll(ISpecification<TAggregateRoot> specification, System.Linq.Expressions.Expression<Func<TAggregateRoot, dynamic>> sortPredicate, SortOrder sortOrder, int pageNumber, int pageSize)
{
if (pageNumber <= 0)
throw new ArgumentOutOfRangeException("pageNumber", pageNumber, "The pageNumber is one-based and should be larger than zero.");
if (pageSize <= 0)
throw new ArgumentOutOfRangeException("pageSize", pageSize, "The pageSize is one-based and should be larger than zero.");
var query = efContext.Context.Set<TAggregateRoot>()
.Where(specification.GetExpression());
int skip = (pageNumber - 1) * pageSize;
int take = pageSize;
if (sortPredicate != null)
{
switch (sortOrder)
{
case SortOrder.Ascending:
return query.OrderBy(sortPredicate.Compile()).Skip(skip).Take(take).ToList();
case SortOrder.Descending:
return query.OrderByDescending(sortPredicate.Compile()).Skip(skip).Take(take).ToList();
default:
break;
}
}
return query.Skip(skip).Take(take).ToList();
}
你能看出什么问题吗?看似没什么问题,执行后就会出现上面我所描述的那个问题,我原来以为是 Where 和 OrderBy 顺序的问题,现在看来还蛮可笑的,后来把中间查询的代码修改了下,进行测试:
var query = efContext.Context.Set<TAggregateRoot>()
.Where(specification.GetExpression()).OrderBy(p=>p.ID).Skip(skip).Take(take);
OrderBy 没有使用参数传过来的 lambda 表达式,而是用聚合根的 ID 进行排序,通过查看生成的 SQL,是正确的分页代码,也就是说问题出在 sortPredicate 参数,或者说是 Expression<Func<TAggregateRoot, dynamic>> 的参数类型上面,EntityFramework(准确的说应该是是 Linq)中 OrderBy 方法的参数类型是 Expression<Func<TSource, TKey>>,TSource 表示数据源类型,TKey 表示返回值类型(注意委托类型为 Func),比如这个参数: p=>p.ID,就表示数据源类型为 TAggregateRoot,返回值类型为 int,执行排序就是 ID 字段。
因为我们数据源类型使用的是 TAggregateRoot,在排序字段类型指定方面,我们没办法具体的指定(比如 p=>p.Name 等),因为 TAggregateRoot 类型中只有一个 ID 属性,所以我们必须通过表达式进行传递,也就是参数 sortPredicate,可以看到数据源类型为 TAggregateRoot,返回值类型(或者称为排序字段类型)为 dynamic,这个表示动态编译类型,没怎么了解过,只是知道大体意思。数据源类型没什么问题,因为我们 where 条件就是这样用的,那就是排序字段类型的问题,关于这个问题,经过反复测试,也没有好的方式解决,我就网上搜了下,对我有所帮助的资源有下面三个:
- EF orderby / thenby combo extension method
- Help me understand “LINQ to Entities only supports casting Entity Data Model primitive types”
- c# 扩展方法 奇思妙用 高级篇 九:OrderBy(string propertyName, bool desc)
还有一个被我关掉找不到了,大概意思是和第二个一样的,都是用范型指定排序类型,第三个是园友写的,很不错,我原来以为看到希望了,但是发现和我的应用场景不太一样,比如:var orderedQueryable = Queryable.OrderBy(repository, (dynamic)keySelector); 这段代码中的 repository 就不知其意思,我最后采用的方式是用范型指定排序类型,经过测试是可以的,代码如下:
protected override IEnumerable<TAggregateRoot> DoFindAll<TOrderSort>(ISpecification<TAggregateRoot> specification, System.Linq.Expressions.Expression<Func<TAggregateRoot, TOrderSort>> sortPredicate, SortOrder sortOrder, int pageNumber, int pageSize)
{
if (pageNumber <= 0)
throw new ArgumentOutOfRangeException("pageNumber", pageNumber, "The pageNumber is one-based and should be larger than zero.");
if (pageSize <= 0)
throw new ArgumentOutOfRangeException("pageSize", pageSize, "The pageSize is one-based and should be larger than zero.");
var query = efContext.Context.Set<TAggregateRoot>()
.Where(specification.GetExpression());
int skip = (pageNumber - 1) * pageSize;
int take = pageSize;
if (sortPredicate != null)
{
switch (sortOrder)
{
case SortOrder.Ascending:
return query.OrderBy(sortPredicate).Skip(skip).Take(take).ToList();
case SortOrder.Descending:
return query.OrderByDescending(sortPredicate).Skip(skip).Take(take).ToList();
default:
break;
}
}
return query.Skip(skip).Take(take).ToList();
}
代码改过之后,通过 Sql Server Profiler 在数据库中跟踪 SQL 执行,终于发现了 Top 关键字(分页产生的页码数量),然后又对数据库中的索引进行了调整,页面加载慢的问题终于得到了一定解决。
看似改一点代码的问题,却花了这么长时间,结果很简单,过程却很复杂,就记录到这。
EF分页问题探讨之 OrderBy的更多相关文章
- EntityFramework 分页问题探讨之 OrderBy
应用场景 最近被应用程序中页面加载慢的问题所折磨,看似容易的问题,其实并不容易(已经持续两天时间了),经过"侦查",发现了两个"嫌疑犯": EntityFram ...
- EF分页中的陷阱
(一) 前言 EF使用非常简单,但是如果使用不当就会误入EF陷阱中. ...
- 存储过程分页 Ado.Net分页 EF分页 满足90%以上
存储过程分页: create proc PR_PagerDataByTop @pageIndex int, @pageSize int, @count int out as select top(@p ...
- (整理)EF分页的实现
最近做一个小功能,需要数据分页,因为小框架使用的是EF,因此查询了一下EF的分页. EF分页主要用到了skip和take两个方法: GetListBy(lamda xxxxx).skip(PageSi ...
- .NET Core使用EF分页查询数据报错:OFFSET语法错误问题
在Asp.Net Core MVC项目中使用EF分页查询数据时遇到一个比较麻烦的问题,系统会报如下错误: 分页查询代码: ) * condition.PageSize).Take(condition. ...
- EF 分页查询优化
按照通常的方式分页查询至少要查询数据两遍,一个操作是查询总数,另一个是查询数据,这样有些耗时 这里介绍一个基于EF的插件 EntityFramework.Extended,当然这个插件有很多的功能,比 ...
- 利用JqGrid结合ashx及EF分页显示列表之二
上一篇文章简单利用JqGrid及ashx进行一个数据列表的显示,要文的重点是利用EF的分页与JqGrid进行结合,EF本文只是简单运用所以没有很规范,重点还是JqGrid分页的实现;本实例把JqGri ...
- 从EF的使用中探讨业务模型能否脱离单一存储层完全抽象存在
上次赶时间,就很流水账地写了上次项目对EF的一次实践应用模式,因为太长了,也没能探讨太多,所以再继续扩展. 这次想探讨的是,实体,如果作为类似于领域模型的业务模型存在,它的数据能否来自不同的数据源.这 ...
- EF分页
先来看看几个LINQ to SQL的几个函数. Take 说明:获取集合的前n个元素:延迟.即只返回限定数量的结果集. var q = ( from e in db.Employees order ...
随机推荐
- debian(wheezy) chrome beta 38.0.2x.xxx Shockwave Flash was crashed 该解决方案崩溃.
版本号chrome beta升级到38.0.2x.xxx什么时候, flash他挂了. 调试 ./libpepflashplayer.so: /lib/x86_64-linux-gnu/libc.so ...
- 解决SQL订阅过程中找不到已经创建的订阅
原文:解决SQL订阅过程中找不到已经创建的订阅 之前有写过一篇博客,主要是图解SQL复制技术:图解SQL 2008数据库复制,当时的测试环境是在我本地同一个服务器上面,所以测试的时候可谓是一帆风顺,最 ...
- JAVA学习第六十二课 — TCP协议练习
通过练习掌握TCP在进行传输过程中的问题 练习1:创建一个英文大写转换server client输入字母数据,发送给服务端,服务端收到后显示到控制台,并将该数据转成大写返回client,知道clien ...
- Pro Aspnet MVC 4读书笔记(5) - Essential Tools for MVC
Listing 6-1. The Product Model Class using System; using System.Collections.Generic; using System.Li ...
- django 简易博客开发 1 安装、创建、配置、admin使用(转)
Django 自称是“最适合开发有限期的完美WEB框架”.本文参考<Django web开发指南>,快速搭建一个blog 出来,在中间涉及诸多知识点,这里不会详细说明,如果你是第一次接触D ...
- 数独 (dfs)
自从2006年3月10日至11日的首届数独世界锦标赛以后,数独这项游戏越来越受到人们的喜爱和重视.据说,在2008北京奥运会上,会将数独列为一个单独的项目进行比赛,冠军将有可能获得的一份巨大的奖品—— ...
- 第7章 桥接模式(Bridge Pattern)
原文 第7章 桥接模式(Bridge Pattern) 定义: 在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能 ...
- uva 12003 Array Transformer (大规模阵列)
白皮书393页面. 乱搞了原始数组中.其实用另一种阵列块记录. 你不能改变原始数组. 请注意,与原来的阵列和阵列块的良好关系,稍微细心处理边境.这是不难. #include <cstdio> ...
- HTML5实现图片文件异步上传
原文:HTML5实现图片文件异步上传 利用HTML5的新特点做文件异步上传非常简单方便,本文主要展示JS部分,html结构.下面的代码并未使用第三发库,如果有参照,请注意一些未展现出来的代码片段.我这 ...
- SVN提交忽略*.class、.classpath、.mymetadata、.project、.settings、.myeclipse和其他非版本控制文件
1.忽略*.class 在TortoiseSVN -->setting(设定)--规设置 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHVrZTY ...