EFCore调优

EFCore调优篇

一.DBFirst

1.引入程序包

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design

命令执行:

Scaffold-DbContext "Data Source=LAPTOP-JU1DEJP1;Initial Catalog=ZhaoxiDBSet;User
ID=sa;Password=sa123" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -
Force

2.DFFirst命令执行:

Scaffold-DbContext "Data Source=LAPTOP-JU1DEJP1;Initial Catalog=ZhaoxiDBSet;User
ID=sa;Password=sa123" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -
Force

3.命令说明

-OutputDir *** 实体文件所存放的文件目录
-ContextDir *** DbContext文件存放的目录
-Context *** DbContext文件名
-Schemas *** 需要生成实体数据的数据表所在的模式
-Tables *** 需要生成实体数据的数据表的集合
-DataAnnotations
-UseDatabaseNames 直接使用数据库中的表名和列名(某些版本不支持)
-Force 强制执行,重写已经存在的实体文件

二.性能优化篇

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

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

MultipleActiveResultSets=True
即可启用 MARS 特性。

2.批处理语句

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

注:操作数据条数 <=3的时候,不会批处理,还是分多次请求,只有>3,才会批处理。
PS:可以手动设置批处理的条数MaxBatchSize,默认值很大。

optionsBuilder.UseSqlServer(“Server=localhost;Database=EFDB01;User
ID=sa;Password=123456;, 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

IEnumerable IQueryable
linq to object linq to sql
用于操作内存对象。是个迭代器的实现 用于操作数据库,且继承了IEnumerable。IQueryable中实现了表达式目录树(Expression),IQueryProvider根据表达式目录树来构建sql语句。
Where(t=>t.id>10)中的“t=>t.id>10”是个委托 Where(t=>t.id>10)中的“t=>t.id>10”是个表达式目录树
封装的函数的时候将返回值设为IEnumerable,即使返回return IQueryable也会立刻查询数据库。 AsEnumerable() 和 AsQueryable()如果后面不继续跟过滤条件等,效果是一样的。 如果后面加了Where / Select / Take() /Skip 等条件,AsEnumerable()先查数据库再过滤,AsQueryable()将条件生成sql,一起在数据库中过滤。

9.导航属性延迟查询(延迟加载)

9.正确使用导航属性 和 延迟查询(延迟加载)
在主表对象中包含一个子表集合对象的属性就是导航属性。跟数据库中的主外键设置无关。 利用延迟加载可以叠加多次查询条件,一次性提交给数据库。

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

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

  • a、导航属性是virtual的;

主从表设计

 public List CrmFollows { get; set; } = new();
   public virtual CrmCustomer  crmCustomer { get; set; }
   modelBuilder.Entity()
                  .HasOne(p => p.crmCustomer)
                  .WithMany(b => b.CrmFollows)
                  .HasForeignKey(p => p.CustomerId)
                  .HasPrincipalKey(b => b.Id);
  • 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()过滤。

正确使用ToList()时机
  /// 
        /// 根据登录账户获取个人的客户信息
        /// 
        /// 
        /// 
        public CustomerDto[] GetMyCustomer(int uid)
        {
            return _crmCustomerRepository.GetAllList().Where(x=>x.EmployeeId== uid).ToList()
               .Select(r => ToDTO(r)).ToArray();
        }

其它的还有:Count() 、FirstOrDefault()、迭代器IEnumerable/foreach 等都会立刻执 行sql。
封装函数的时候可以返回IQueryable,而不是IEnumable,防止立马查询数据库。

9.4 迭代(如foreach)使用延迟查询的时候,迭代完了才关闭链接,应当避免使用这种场景。可以在迭代之 前ToList()。
9.6 延迟加载的对象(IQueryable)脱离了DbContext上下文对象的范围后不能被查询,因为 DbContext被释放了。

10.预先加载

10.预先加载 使用Include,查询主表时把子表(导航属性)也一次性查出来。
延迟查询关闭后也不影响的。 例如:context.Set().Include(“Users”).Where(t=>t.id<10);

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

 public async Task<PagedResultDto<CrmCustomer>> GetPaginatedResult(GetCustomerInput input)
        {
          
            var query = _crmCustomerRepository.GetAll().AsNoTracking().Where(x => x.EmployeeId == input.Id);

            //统计查询数据的总条数,用于分页计算总页数
            var count = query.Count();
            //根据需求进行排序,然后进行分页逻辑的计算
            query = query.OrderBy(input.Sorting).Skip((input.CurrentPage - 1) * input.MaxResultCount).Take(input.MaxResultCount);
            //将查询结果转换为List集合,加载到内存中
            var models = await query.Include(a => a.CrmFollows)
             .OrderByDescending(d => d.CreateDate)
             .AsNoTracking().ToListAsync();
            //var models = await query.Include(a => a.CrmCustomerFollows).ThenInclude(c => c.CustomerId) .AsNoTracking().ToListAsync();
            //var models = await query.Include(a => a.Id).AsNoTracking().ToListAsync();
            var dtos = new PagedResultDto<CrmCustomer>
            {
                TotalCount = count,
                CurrentPage = input.CurrentPage,
                MaxResultCount = input.MaxResultCount,
                Data = models,
                FilterText = input.FilterText,
                Sorting = input.Sorting
            };
            return dtos;
        }

11.使用TransactionScope

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();
 }
}

添加Z.EntityFramework.Plus.EFCore依赖使用一些特殊的语法
这个是免费的,但 Z.EntityFramework.Plus的一些批量数据操作的包是收费的
10.1 EFCore删除必须先查询再删除,优化后可直接删除:context.User.Where(t => t.Id ==
100).Delete();
10.2 优化更新语句:context.User.Where(t => t.Id == 4).Update(t =>new User() {
NickName = “2224114” ,Phone = “1234”} );

12.正确使用索引

查询能否快速运行的主要决定因素是它是否在恰当的位置使用索引:数据库通常用于保存大量数据,而遍历整个表的查询往往是严重性能问题的根源。 索引问题不容易发现,因为给定的查询是否会使用索引并不是显而易见的。 例如:

// Matches on start, so uses an index (on SQL Server)
var posts1 = context.Posts.Where(p => p.Title.StartsWith("A")).ToList();
// Matches on end, so does not use the index
var posts2 = context.Posts.Where(p => p.Title.EndsWith("A")).ToList();

使用索引时要记住的一些一般准则:

  • 索引能加快查询,但也会减缓更新,因为它们需要保持最新状态。
    避免定义不需要的索引,并考虑使用索引筛选器将索引限制为行的子集,从而减少此开销。
  • 复合索引可加速筛选多列的查询,也可加速不筛选所有索引列的查询,具体取决于排序。 例如,列 A 和列 B 上的索引加快按 A 和 B 筛选的查询以及仅按 A 筛选的查询,但不加快仅按 B 筛选的查询。
  • 如果查询按表达式筛选列(例如 price / 2),则不能使用简单索引。 但是,你可以为表达式定义存储的持久化列,并对该列创建索引。 一些数据库还支持表达式索引,可以直接使用这些索引加快按任何表达式筛选的查询。
  • 不同数据库允许以各种不同的方式配置索引,在许多情况下,EF Core 提供程序都通过 Fluent API 公开这些索引。 例如,你可以通过 SQL Server 提供程序配置索引是否为聚集索引,或设置其填充因子。

————————————————

13.LinqShow

14.Z.EntityFramework.Extensions.EFCore

var userlist = context.SysUser.Take(100000).Delete();
var userlist = context.SysUser.Take(100000).Update(u=>new Sysuser(){});
var userlist = context.SysUser.Take(100000).Update(u=>new Sysuser(){}); ;

//1.扩展类
DbContextExtensions

三、日志记录

1.Nuget引入:Microsoft.Extensions.Logging.Console

optionsBuilder.UseLoggerFactory(LoggerFactory.Create(option =>
     {
       option.AddFilter((category, level) => category ==
DbLoggerCategory.Database.Command.Name && level ==
LogLevel.Information).AddConsole();
     }));

四、迁移

1.准备好DbContext
2.准备好实例对象
MPC命令:

add-migrations migrationName —添加迁移文件

 remove-migration—删除迁移文件 

update-database —通过迁移文件生成数据库 

update-database migrationName —指定某一个迁移文件生成数据库

你可能感兴趣的:(DDD,C#,中级知识汇总,.NET,CORE,数据库,sqlserver)