【EFCORE笔记】性能优化方案

谈谈性能问题

EF Core 是一种快速而令人满意的ORM 数据访问框架,但随着Web 应用程序越来越频繁的访问,性能变得越来越重要,但让人诟病的性能问题一直是很多程序员热聊的话题,实际情况并不是性能差,而是需要我们掌握如何规     避陷阱和避开影响性能的坑。

纯手共执行一个 SQL 语句和用 ORM 框架性能基本相当,只要优化得好,性能问题可以忽略,一般大系统的性能问题都是数据库设计问题,与使用何种数据访问技术无直接关系,EF Core 中也可以直接执行 SQL 语句。

使用上下文实例池

services.AddDbContext<EmployeeContext>(options => options.UseSqlServer(connection));

services.AddDbContextPool<EmployeeContext>(options => options.UseSqlServer(connection));

如果使用 AddDbContextPool 方法,那么在控制器请求DbContext 实例时,我们会首先检查池中有无可用的实例。 请求处理完成后,实例的任何状态都将被重置,并且实例本身会返回池中。

从概念上讲,此方法类似于ADO.NET 连接池的运行原理,并具有节约DbContext 实例初始化成本的优势。

https://docs.microsoft.com/zh-cn/ef/core/what-is-new/ef-core-2.0#dbcontext-pooling

Use DbContextPooling to improve the performance: .Net Core 2.1 feature

显式编译查询

EF 早期版本以及 LINQ to SQL 中已经提供手动或显式编译的查询API,允许应用程序缓存查询转换,使其可仅被计算一次并执行多次。

尽管 EF Core 通常可基于查询表达式的哈希表示法自动编译和缓存查询,但是使用此机制可绕过哈希计算和缓存查询,允许应用程序通过调用委托来使用已编译查询,从而实现性能小幅提升。

// Create an explicitly compiled query
private static Func<CustomerContext, int, Customer> _customerById =
        EF.CompileQuery((CustomerContext db, int id) =>
                db.Customers
                        .Include(c => c.Address)
                        .Single(c => c.Id == id));

// Use the compiled query by invoking it using (var db = new CustomerContext())
{
      var customer = _customerById(db, 147);

}

  测试 EF Core 使用编译查询提高性能

多活动结果集数据库连接复用

多活动结果集 (MARS) 是一项允许对单个连接执行多个批处理的功能。 在以前的版本中,在单个连接上一次只能执行一个批处理。 使用 MARS 执行多个批处理并不意味着同时执行操作。

在连接字符串中添加:MultipleActiveResultSets=True 即可启用 MARS 特性。

Multiple Active Result Sets (MARS)

如果未启用 MARS 连接复用,多次打开应用程序,在 SQL Server Management Studio 中使用 sp_who 命令会查询当前存在多个活动的连接。

如果启用了 MARS 连接复用,进行上述操作,发现只有一个活动连接访问数据库,因为连接被复用。

优先使用Async 异步方法

EF Core 中提供了很多形如xxxAsync 的异步方法,推荐使用这些方法提高吞吐量和性能,减少不必要的延时等待。

批处理语句

optionbuilder.UseSqlServer(sConnString , b => b.MaxBatchSize(1));
//默认是批处理,这样配置是关闭批处理

Entity Framework Core 批处理语句性能测试

    

添加索引提高数据查询性能

索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。

modelBuilder.Entity<Blog>().HasIndex(b => b.Url).IsUnique(); 

modelBuilder.Entity<Person>()
.HasIndex(p => new { p.FirstName, p.LastName });

避免N+1 查询

模型中可使用导航属性来加载相关实体。 有三种常见的 O/RM 模式可用于加载关联数据。预先加载表示从数据库中加载关联数据,作为初始查询的一部分。显式加载表示稍后从数据库中显式加载关联数据。延迟加载表示在访问     导航属性时,从数据库中以透明方式加载关联数据。

var blogs = context.Blogs.Include(blog => blog.Posts)
.ThenInclude(post => post.Author).ToList();

  但是,任何技术都有两面性,优势不可能让你占完了,使用N+1 模式的优点是可以单独缓存部分数据。

跟踪与非跟踪查询

跟踪行为决定了 EF Core 是否将有关实体实例的快照信息保留在其更改跟踪器中。 如果已跟踪某个实体,则该实体中检测到的任何更改都会在SaveChanges() 期间永久保存到数据库。

当决定只查询数据,不更改数据时,非跟踪查询十分有用,非跟踪查询的执行会更快,因为无需为查询实体设置快     照跟踪信息。 如果不需要更新从数据库中检索到的实体,则应优先使用非跟踪查询。

var blogs = context.Blogs.AsNoTracking().ToList(); 

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

  

关闭 DetectChanges 状态同步

当从数据库进行查询数据时,上下文便捕获了每个实体属性的快照(数据库值,原始值,当前值),当调用SaveChanges 时,在内部会自动调用 DetectChanges 方法,此方法将扫描上下文中所有实体,并比较当前属性值和存储在快照中的原始属性值,如果被找到的属性值发生了改变,此时EF将会与数据库进行交互,进行数据更      新。

会导致自动调用 DetectChanges 方法: Find、Local、Remove、Add、Update、Attach、SaveChanges 和

Entry 等。

但是,自动同步状态会频繁调用,可手动关闭以上方法的自动同步,当数据都修改好后,一次性手动同步。

context.ChangeTracker.AutoDetectChangesEnabled = false;

//执行操作后手动同步状态
context.ChangeTracker.DetectChanges();

  

数据缓存

缓存可以减少重复内容的多次生成成本,从而显著提高应用程序的性能和可伸缩性。     缓存最适用于不经常更改的数据,生成成本很高的数据。 通过缓存,可以比从数据源返回的数据的副本速度快得多。

EF Core 的高性能二级查询缓存开源库:EntityFrameworkCore.Cacheable

var cacheableQuery = cacheableContext.Books
    .Include(d => d.Pages)
    .ThenInclude(d => d.Lines)
    .Where(d => d.ID == 200)
    .Cacheable(TimeSpan.FromSeconds(60));

  

使用 EF.Functions.Link 模糊查询

EF Core 支持使用 StartsWith、Contains  EndsWith 方法进行模糊查询,这些方法被翻译成 LIKE 语句,但为了提高性能也提供了一个EF.Functions.Like 方式,这种方式生成的 SQL 语句性能更优。

var result= context.Blogs
.Where(b => EF.Functions.Like(b.BlogName, "%xcode%"))

  Entity Framework Core Like 查询揭秘

DbFunctionAttribute 标量函数

EF Core 支持映射数据库中定义的函数,可以在LINQ 查询中使用,该功能支持将数据库标量函数映射到方法存根, 使其可用于LINQ 查询并转换为 SQL。

DbContext 上声明静态方法,并使用 DbFunctionAttribute 对其批注:

public class BloggingContext : DbContext
{
        [DbFunction]
        public static int PostReadCount(int blogId)
        {
                throw new Exception();
        }
}

  此类方法会自动注册,对LINQ 查询方法的调用可转换为SQL 中的函数调用:

var query =context.Posts.Where(p=>BloggingContext.PostReadCount(p.Id) > 5)

  

原文地址:https://www.cnblogs.com/lbonet/p/14608870.html