EFCore查询的性能调优有那些经验

优化是个无尽的话题,它是长年累月的实践经验,没有一个标准答案。在回答这类问题最好把你的一些项目优化经验讲给面试官听,如果实在没有可以在网上找找,不过问你过程就可能卡壳了。EF Core收集了如下的几种调优经验,仅供参考。.

EF Core性能调优经验

    1、使用AsNoTracking()方法,查询出来的数据,只是做展示,不做增删改查,可以在查询的时候,增加AsNoTracking()方法, 可以提高性能,可以避免在内存中存在副本;

    2、多使用Find()方法,建议在查询的时候使用Find()方法,会有限走内存缓存,如果内存已经存在,就不会去数据库中去操 查询数据;

    3、预先加载Include、查询主表时把子表(导航属性)也一次性查出来。延迟查询关闭后也不影响的。

    ......

扩展:

网上收集了十多总方式,请参考

1.多活动结果集连接复用

多活动结果集 (MARS) 是一项允许对单个连接执行多个批处理的功能。在以前的版本中,在单个连接上一次只能执行一个批处理。使用 MARS 执行多个批处理并不意味着同时执行操作。在连接字符串中添加:MultipleActiveResultSets=True 即可启用 MARS 特性。

2.批处理语句

EFCore中有一个重大改进,就是批处理,比如向数据库中增加n条数据(n>3),会组合成一次请求访问数据库(而在以前的EF中,不是批处理,增加几条,则会访问几次)。

注:操作数据条数 <=3 的时候,不会批处理,还是分多次请求,只有>3,才会批处理。

PS:可以手动设置批处理的条数MaxBatchSize,默认值很大。optionsBuilder.UseSqlServer(“Server=.;Database=DBSet;User ID=sa;Password=sasa;”, b => b.MaxBatchSize(10));

3.非跟踪查询

_dataContext.Region.AsNoTracking().ToListAsync();

关于同步状态:

当从数据库进行查询数据时,上下文便捕获了每个实体属性的快照(数据库值,原始值,当前值),当调用SaveChanges 时,在内部会自动调用 DetectChanges 方法,此方法将扫描上下文中所有实体,并比较当前属性值和存储在快照中的原始属性值,如果被找到的属性值发生了改变,此时EF将会与数据库进行交互,进行数据更新。会导致自动调用 DetectChanges 方法:Find、Local、Remove、Add、Update、Attach、SaveChanges 和Entry 等。但是,自动同步状态会频繁调用,可手动关闭以上方法的自动同步,当数据都修改好后,一次性手动同步。

关闭:context.ChangeTracker.AutoDetectChangesEnabled = false;

开启:context.ChangeTracker.DetectChanges();

4.非跟踪增加

_dataContext.Entry(region).State = EntityState.Added;

int res = await _dataContext.SaveChangesAsync();

5.非跟踪修改

_dataContext.Entry(region).State = EntityState.Modified;

int res = await _dataContext.SaveChangesAsync();

6.非跟踪删除

_dataContext.Entry(region).State = EntityState.Deleted;

int res = await _dataContext.SaveChangesAsync();

7.正确使用Find/FirstOrDefault

正确使用Find(id=10)来代替FirstOrDefault(t=>t.id=10)

Find会优先查询缓存,当前面已经查询过这条数据的时候使用,而FirstOrDefault每次都会查询数据库;当id=10的数据被修改之后,find查出的数据是新数据。

8.正确区分IQueryable和IEnumerable

8.1 IEnumerable(linq to object)用于操作内存对象。是个迭代器的实现。

8.1.1 Where(t=>t.id>10)中的“t=>t.id>10”是个委托

8.1.2 封装的函数的时候将返回值设为IEnumerable,即使返回return IQueryable也会立刻查询数据库

8.2 IQueryable(linq to sql)用于操作数据库,且继承了IEnumerable。IQueryable中实现了表达式目录树(Expression),IQueryProvider根据表达式目录树来构建sql语句。

8.2.1 Where(t=>t.id>10)中的“t=>t.id>10”是个表达式目录树

8.2.2 AsEnumerable() 和 AsQueryable()如果后面不继续跟过滤条件等,效果是一样的。如果后面加了Where / Select / Take() /Skip 等条件,AsEnumerable()先查数据库再过滤,AsQueryable()将条件生成sql,一起在数据库中过滤。

9.正确使用导航属性 和 延迟查询(延迟加载)

在主表对象中包含一个子表集合对象的属性就是导航属性。跟数据库中的主外键设置无关。

利用延迟加载可以叠加多次查询条件,一次性提交给数据库。

使用建议:在开发中不确定后面是否需要关联表数据,可以使用延迟加载来按需获取数据。

9.1 导航属性要延迟加载必须具备两个条件:

a、导航属性是virtual的;

b、延迟查询必须是开启的。

EF:context.Configuration.LazyLoadingEnabled=true; (默认就是true的)

EF Core:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){if (!optionsBuilder.IsConfigured)var builder = optionsBuilder.UseSqlServer(_connStr);optionsBuilder.UseLazyLoadingProxies(); //启用延迟加载}

9.2 延迟查询关闭之后可以使用显示加载的方式:

加载集合:context.Entry(company).Collection(t=>t.Users).Load();

加载单个:context.Entry(company).Reference(t=>t.Users).Load();

9.3 使用ToList()的时候会立马执行数据库sql查询,看到过很多同学先ToList()再Where()过滤。

其它的还有:Count() 、FirstOrDefault()、迭代器IEnumerable/foreach 等都会立刻执行sql。

封装函数的时候可以返回IQueryable,而不是IEnumable,防止立马查询数据库。

9.4 迭代(如foreach)使用延迟查询的时候,迭代完了才关闭链接,应当避免使用这种场景。可以在迭代之前ToList()。

9.6 延迟加载的对象(IQueryable)脱离了DbContext上下文对象的范围后不能被查询,因为DbContext被释放了。

10.预先加载

使用Include,查询主表时把子表(导航属性)也一次性查出来。延迟查询关闭后也不影响的。

例如:context.Set().Include(“Users”).Where(t=>t.id<10);

使用建议:当开发中能确定后面一定会用到子表数据的时候可以使用预先加载。

11.使用TransactionScope

TransactionScope可以完成多个context多个SaveChange的事务问题,默认一个SaveChange是一个事务

还可以使用context.Database.BeginTransaction做单库事务。

using (DbContext context = new DbContext()){using (TransactionScope tran = new TransactionScope()){context.SaveChange();context.SaveChange();//无异常会执行Complete 提交事务tran.Complete();}}