应用场景

最近被应用程序中页面加载慢的问题所折磨,看似容易的问题,其实并不容易(已经持续两天时间了),经过“侦查”,发现了两个“嫌疑犯”:

  1. EntityFramework 生成执行的 SQL
  2. 数据库中索引创建

在《程序员眼中的 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 条件就是这样用的,那就是排序字段类型的问题,关于这个问题,经过反复测试,也没有好的方式解决,我就网上搜了下,对我有所帮助的资源有下面三个:

  1. EF orderby / thenby combo extension method
  2. Help me understand “LINQ to Entities only supports casting Entity Data Model primitive types”
  3. 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 关键字(分页产生的页码数量),然后又对数据库中的索引进行了调整,页面加载慢的问题终于得到了一定解决。

看似改一点代码的问题,却花了这么长时间,结果很简单,过程却很复杂,就记录到这。

EntityFramework 分页问题探讨之 OrderBy的更多相关文章

  1. EF分页问题探讨之 OrderBy

    EntityFramework 应用场景 最近被应用程序中页面加载慢的问题所折磨,看似容易的问题,其实并不容易(已经持续两天时间了),经过“侦查”,发现了两个“嫌疑犯”: EntityFramewor ...

  2. EntityFrameWork分页

    EF分页代码 using System; using System.Collections.Generic; using System.Linq; using System.Web; using Sy ...

  3. Ling to entity实现分页

    Ling to entity实现分页 最近用MVC做的一个项目涉及到分页,中间用了entity framework来查数据库,不用直接写sql语句,方便了很多. 一般分页的思路是获得两个变量的值: 1 ...

  4. asp.net mvc多条件+分页查询解决方案

    开发环境vs2010 css:bootstrap js:jquery bootstrap paginator 原先只是想做个mvc的分页,但是一般的数据展现都需要检索条件,而且是多个条件,所以就变成了 ...

  5. 重构MVC多条件分页解决方案

    重构MVC多条件+分页解决方案 为支持MVC的验证,无刷新查询,EF,以及让代码可读性更强一点,所以就重构了下原来的解决方案. 这里就简单讲下使用方法吧: Model: 继承PagerBase:  S ...

  6. ASP.NET EF(LINQ/Lambda查询)

    EF(EntityFrameWork) ORM(对象关系映射框架/数据持久化框架),根据实体对象操作数据表中数据的一种面向对象的操作框架,底层也是调用ADO.NET ASP.NET MVC 项目会自动 ...

  7. 【原创】基于.NET的轻量级高性能 ORM - TZM.XFramework

    [前言] 接上一篇<[原创]打造基于Dapper的数据访问层>,Dapper在应付多表自由关联.分组查询.匿名查询等应用场景时不免显得吃力,经常要手写SQL语句(或者用工具生成SQL配置文 ...

  8. CQRS学习——Storage实现(EF+Code First+DynamicReponsitory)[其四]

    [这里是的实现,指的是针对各个数据访问框架的一个基础实现] 目标 定义仓储/QueryEntry的基本功能 实现仓储的基本功能,以利于复用 实现一些常用的功能 提供一些便利的功能 目标框架 博主使用的 ...

  9. MongoDB实战开发 【零基础学习,附完整Asp.net示例】

    MongoDB实战开发 [零基础学习,附完整Asp.net示例] 阅读目录 开始 下载MongoDB,并启动它 在C#使用MongoDB 重构(简化)代码 使用MongoDB的客户端查看数据 使用Mo ...

随机推荐

  1. java分享第二十天(build.xml的语法及写法)

    通常情况下,Ant构建文件build.xml应该在项目的基础目录.可以自由使用其他文件名或将构建文件中其他位置.在本练习中,创建一个名为build.xml 在电脑的任何地方的文件. <?xml ...

  2. iOS开发资源(持续更新)

    vm10虚拟机安装Mac OS X10.10教程 马上着手开发 iOS 应用程序 (Start Developing iOS Apps Today) Xcode使用教程详细讲解 (上) Xcode使用 ...

  3. HTTP协议入门要点

    应用层协议.基于tcp HTTP/0.9 命令 GET 特点 服务器只能回应HTML字符串 服务器发送完毕后就关闭tcp连接 HTTP/1.0 命令 GET POST HEAD 特点 每次通信都必须包 ...

  4. indows server 2008 多用户远程桌面连接设置(验证有效

    然后,在运行框中输入 gpedit.msc 之后,点击确定或者直接按键盘上的回车键  计算机配置-->管理模板-->Windows组件---->远程桌面服务--->远程桌面会话 ...

  5. iOS开发UI高级手势识别器

    ####手势识别器 UIGestureRecognizer类 ·UITapGestureRecognizer(轻击) ·UIPinchGestureRecognizer(捏合) ·UIPanGestu ...

  6. [ios]利用alertView 插入数据都数据库。笔记

    利用alertView 插入数据都数据库 -(void)addItemToList { UIAlertView *alter=[[UIAlertViewalloc]initWithTitle:@&qu ...

  7. webpack配置ES6 + react套装开发

    配置ES6 1.安装插件: babel-loader,babel-preset-es2015,babel-preset-react. 2.添加配置文件中部分内容: { test: /\.js$/, l ...

  8. SQL Server

    1.通过触发器来级联删除: 具体的触发器代码如下: Create TRIGGER [dbo].[DeleteRelatedProducts] ON [dbo].[ProductCategory]  A ...

  9. java获取cpu和内存

    利用jar包sigar 下载地址:http://sourceforge.net/projects/sigar/files/latest/download?source=files 需要将sigar-x ...

  10. 一鼓作气 博客--第三篇 note3

    1 推荐读书消费者行为学 -商业的本质,APP得到,5分钟商学院 2定义字典 dic={'name':haibao,'age':18} 3字典的基本操作--查询 dic={'name':'haibao ...