Entity FrameWork知识2 相关性能优化
参考 https://blog.csdn.net/u010028869/article/details/48811811
1、分清真分页和假分页
大家都知道分页分为真分页和假分页,并且假分页是特别耗费性能的。我们在使用的过程中也是以真分页为主,但是在使用EF写分页语句的时候,稍有不慎,真分页便会成为假分页:
query.ToList().Skip((PageIndex - 1) * PageSize).Take(PageSize);
query.Skip((PageIndex - 1) * PageSize).Take(PageSize).ToList();
上面两句话乍一看差不多,并且都会实现我们分页的需求。但是这两句的执行过程,生成的sql语句大有不同。第一条语句的执行过程是:
a.先把数据全部都查询出来,放到内存中 
b.转换成List 
c.从List中进行分页操作,查询出结果。 这其实是假分页的效果。
而第二条语句就是真分页了,将参数传到数据库,生成分页sql语句,在数据库中查询出结果
2.合理使用加载方式
参考上篇文章
选择什么样的数据加载方式需要因时而异,如果选择不当很可能会影响系统性能,每一种数据加载方式都有它存在的意义,但目的只有一个,那就是以最少的代价获取到需要的数据。
3.注意事务的简短
在使用事务时,我们尽量要把与事务无关的东西放到事务外执行,比如(查询语句或者其他事务外的语句),如果让一个事务的执行时间过长,很容易引起资源死锁的问题,当用压力测时,马上就出现资源被锁的错误。
4.no tracking使用
查询出来的实体,如果不需要删除和修改,请用NoTracking查询。
 using (var context = new MyDbContext())
    { 
         var people = context.People.Where(p => p.PersonID > 100).AsNoTracking().ToList();
    }
有时我们的实体只需要显示,无需更新,所以为了提高性能,我们不需要实体被EF context追踪。此时可以使用NoTracking的查询来得到实体,这样实体的状态会是Detached状态。
AsNoTracking是无跟踪查询, 有时我们的实体只需要显示,无需更新,所以为了提高性能,我们不需要实体被EF追踪。此时可以使用AsNoTracking的查询来得到实体,这样实体的状态是Detached状态。这样可以提高性能,但是如果取到数据后,要对数据做修改并保存,则无法反映到数据库里。另外如果对通过AsNoTracking,得到的数据做删除处理,还会报错。
5.对于逻辑相对复杂的查询,要随时监控生成的Sql语句
毕竟EF生成的语句,往往比我们生成的语句更加复杂,这个时候我们就要考虑是否通过其他方式来提高性能。比如自己写原生的sql语句,有时候原生SQL语句是更好的选择。另外我们要善于使用SQL Server Profiler这个工具,实时监控生成的sql语句。
6.批量删除和修改
不知道你是否研究过EF的插入删除和修改操作,当你批量操作数据的时候,通过SQL Server Profiler可以明显看到产生了大量的Insert,Update语句,效率非常低;因为他插入一条数据,会对应生成一条Insert语句,当你的list中有10万条数据时,就会生成10万条插入语句!不过还好咱们有对策:Entity Framework Extendeds ,EF扩展类完美解决批量操作问题。
7.一些配置修改
对EF的上下文做一些配置以减少性能的损耗:
Configuration.AutoDetectChangesEnabled = false;
Configuration.ValidateOnSaveEnabled = false;
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
8.
#region 按条件查询:LoadItems(Func<T, bool> whereLambda)
        /// <summary>
        /// 按条件查询
        /// </summary>
        /// <param name="whereLambda">lambda表达式</param>
        /// <returns>IQueryable 泛型集合</returns>
        public IQueryable<T> LoadItems(Func<T, bool> whereLambda)
        {
            return MyBaseDbContext.Set<T>().Where(whereLambda).AsQueryable();
        }
 #endregion
方法的参数whereLambda,其类型为Func<T, bool> ,在调用LoadItems方法时,通过SQL Server Profiler检测生成的sql语句,你会发现它只生成了一条查询全部结果的语句。然后通过一步步断点调试发现,将数据全部加载完毕后,还会根据查询条件whereLambda进行很多次的循环,才能把最终结果返回。
并且当调用带条件参数的查询方法时,都会遇到这个问题。不过当发现问题之后,基本也就能解决问题了。随后我们发现了与Func<TObject, bool> 非常相似的类型Expression<Func<TObject, bool>>
Expression<Func<TObject, bool>>为表达式类型,它会带上查询条件一并生成sql语句,将查询结果返回,大大提高查询效率。
改为
 public IQueryable<T> LoadItems(Expression<Func<T, bool>> whereLambda)
        {
            return MyBaseDbContext.Set<T>().Where(whereLambda).AsQueryable();
        }
总结:Expression<Func<TObject, bool>>与Func<TObject, bool>
Func<TObject, bool> 委托类型,使用这个类型执行查询时会查询表中的全部数据,加载到内存中然后在根据条件循环进行筛选。
Expression<Func<TObject, bool>> 表达式类型,使用该类型作为参数时,会将查询条件一并添加到sql语句中查询出结果。
9.
IQueryable<>和IEnumerable<>的区别。
IQueryable不会查询数据库。只是生成一段sql语句。
IEnumerable会查询数据库,并且加载数据到本地缓存。
//以下sql没区别
IQueryable<Orders> q = db.Orders.OrderBy(x=>x.id).Skip(1).Take(2);
            IEnumerable<Orders> e = db.Orders.OrderBy(x=>x.id).Skip(1).Take(2);
//以下 IEnumerable生成的语句,是查询全表。IEnumerable是通过缓存全表,然后才分页查找的!!!所以IEnumerable性能不好!
 IQueryable<Orders> q = db.Orders.OrderBy(x => x.id).AsQueryable<Orders>().Skip(10).Take(2);
            IEnumerable<Orders> e = db.Orders.OrderBy(x => x.id).AsEnumerable<Orders>().Skip(10).Take(2);
IQueryable linq to sql 操作数据库
IEnumerable linq to object 操作内存
IEnumerable 会查询全部数据,然后在内存里进行分页或者筛选操作。
但是真正导致他们使用哪种方式的,是AsQueryable() 和 AsEnumerable() !!!!!
IQueryable接口与IEnumberable接口的区别: IEnumerable<T> 泛型类在调用自己的SKip 和 Take 等扩展方法之前数据就已经加载在本地内存里了,而IQueryable<T> 是将Skip ,take 这些方法表达式翻译成T-SQL语句之后再向SQL服务器发送命令,它并不是把所有数据都加载到内存里来才进行条件过滤。
为了减少带宽的消耗,可选择返回IQuerayble接口类型,当然如果内存足够,需要更快的响应速度,也可以选择返回IEnumerable接口类型。
 
自我总结:IQueryable<T>使用会直接在数据库中取出相关数据 
好处:减少带宽 缺点:响应慢 
           IEnumerable<T>使用会将数据库中的数据加载到本地内存,然后在选择取出相 关数据 
好处:响应快  缺点:占内存
var q=dbcontext.Users.where(x=>x.id=1)   这个出来是一个IQueryable,其实并没有查询数据库。
var q=dbcontext.Users.where(x=>x.id=1) .FirstOrDefault() 或者 .ToList()  这个出来就是一个IEnumerable
必须避免所有数据加载到内存里。所以不能先返回一个  var q = dbcontext.Users.tolist()。然后在对这个q进行操作。这类代码,就会使内存爆炸。因此你们应该返回一个iqueryable或者是已经过滤了的IEnumerable,并且增加 where等等的expression来支持表达式树。
例如 
public virtual IEnumerable<T> FindList(Expression<Func<T, bool>> whereLamdba)
        {
            var _list = dbEF.Set<T>().AsNoTracking().Where<T>(whereLamdba);
            return _list.ToList();
        }
调用方法可能是:
var q = biz.FindList(
                x =>(
                        x.属性==值
                    )
                );
            
 
作者:llhhll004            
 

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号