【EF Core】优化方案

查询优化策略

避免N+1查询问题​

使用IncludeThenInclude进行预加载关联数据,而不是在循环中单独查询
反例:在循环中查询关联数据会产生多次数据库往返
正例:var orders = context.Orders.Include(o => o.Items).ToList();

拆分查询

​使用AsNoTracking禁用变更追踪​

只读查询场景下使用AsNoTracking()可减少内存占用和跟踪开销,性能提升20%-50%
全局设置:context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

合理使用投影查询​

只选择需要的列而非整个实体,减少数据传输量
示例:context.Products.Select(p => new { p.Id, p.Name }).ToList()

​使用原生SQL优化复杂查询​

使用FromSqlRaw/FromSqlInterpolated直接执行优化SQL,场景:复杂报表、使用窗口函数、UNION查询、LINQ生成的SQL不够高效时

​使用EF.Functions进行高效模糊查询​

EF.Functions.LikeContains/StartsWith/EndsWith性能更优,直接生成SQL LIKE语句
示例:context.Users.Where(u => EF.Functions.Like(u.Name, "%张%"))

​使用Find方法优化主键查询​

Find()会先检查内存缓存,减少不必要的数据库查询
对比:FirstOrDefault()每次都会查询数据库

批量插入、更新、删除

批量插入

AddRange:
高效批量插入操作时,AddRange配合合理设置的BatchSize是一种平衡开发效率与性能的常用方案。
AddRange是EF Core提供的原生批量操作方法,其核心优势在于减少数据库往返次数​,将多个实体的插入操作合并为一次数据库提交

using var context = new ApplicationDbContext();
var entities = GenerateLargeDataSet(); // 生成批量数据
context.MyEntities.AddRange(entities);
await context.SaveChangesAsync(); // 单次提交

BatchSize的配置与优化
从EF Core 6开始,可以通过SetMaxBatchSize配置批处理大小,显著提升性能:

配置方式

services.AddDbContext<MyDbContext>(options => 
    options.UseSqlServer(
        connectionString,
        sqlOptions => sqlOptions
            .SetMaxBatchSize(100) // 设置每批操作的最大条目数
            .UseBulkExecution(true) // 启用批量执行(EF Core 6+)
    )
);

分批处理示例:

foreach (var batch in entities.Chunk(500)) // 使用Chunk分组
{
    context.MyEntities.AddRange(batch);
    await context.SaveChangesAsync();
    context.ChangeTracker.Clear(); // 清除跟踪以释放内存
}

ExecuteInsert:
ExecuteInsert是EF Core 7.0引入的高性能批量插入方法,该方法专门针对批量数据插入场景进行了优化,能够显著提升数据写入性能。

using var transaction = await context.Database.BeginTransactionAsync();
try
{
    await context.Products.ExecuteInsertAsync(products);
    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}

分批插入:

foreach (var batch in products.Chunk(1000))
{
    await context.Products.ExecuteInsertAsync(batch);
}

批量修改、删除

Entity Framework Core 7 引入了革命性的批量操作方法 ExecuteUpdateExecuteDelete,它们彻底改变了EF Core处理批量数据更新的方式,提供了显著的性能优势。
核心优势与工作原理:
​无跟踪直接操作数据库​,传统方式需要先加载实体到内存,修改后再保存,产生大量SQL语句
ExecuteUpdate/ExecuteDelete直接生成并执行单条SQL语句,无需加载实体
示例:context.Blogs.Where(b => b.Rating < 3).ExecuteDelete()生成DELETE FROM [Blogs] WHERE [Rating] < 3
​性能提升显著​,避免了数据传输、变更跟踪等开销,特别适合大规模数据操作
对于10,000条记录,传统方式可能需要几分钟,而新方法只需几秒

context.Products
    .Where(p => p.Category == "iPhone")
    .ExecuteUpdate(setters => setters
        .SetProperty(p => p.StockQuantity, 0)
        .SetProperty(p => p.Price, p => p.Price * 0.9));

生成SQL:

UPDATE [p] 
SET [StockQuantity] = 0, [Price] = [Price] * 0.9
FROM [Products] AS [p] 
WHERE [p].[Category] = 'iPhone'

选择合适的数据加载策略​

​预加载(Eager Loading)​​:使用Include/ThenInclude一次性加载所需数据
​显式加载(Explicit Loading)​​:通过Entry方法按需加载
​延迟加载(Lazy Loading)​​:谨慎使用,可能导致意外查询

​编译查询重用​

对频繁执行的查询使用EF.CompileAsyncQuery编译为委托,减少查询编译时间
EF.CompileAsyncQuery方法是一种高级性能优化技术,它通过将LINQ查询表达式预先编译为可重用的委托,显著减少重复查询时的解析和编译开销。这种技术特别适合在应用程序中频繁执行的固定条件查询场景。
EF.CompileAsyncQuery的工作原理是将LINQ表达式树编译为优化的委托,避免了每次执行查询时的重复解析和编译过程

// 1. 定义编译后的查询委托(通常声明为静态只读字段)
private static readonly Func<MyDbContext, int, Task<Order>> GetOrderById = 
    EF.CompileAsyncQuery((MyDbContext context, int id) => 
        context.Orders.FirstOrDefaultAsync(o => o.Id == id));

// 2. 在实际业务代码中调用
public async Task<Order> GetOrderAsync(int orderId)
{
    using var context = new MyDbContext();
    return await GetOrderById(context, orderId);
}
posted @ 2025-07-16 16:29  .Neterr  阅读(50)  评论(0)    收藏  举报