EFCore性能优化方案-优化查询

文章讨论了如何优化EFCore在数据库性能方面的方法,包括正确使用索引以加快查询速度,只查询必要的属性以减少数据传输,限制结果集大小,优先使用Async异步方法提高并发性能,关闭状态追踪和DetectChanges以减少资源消耗,使用EF.Functions进行高效模糊查询,实现高效分页,以及在必要时使用原始SQL来提升查询效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据库性能是一个宏大而复杂的主题,涉及整个组件堆栈:数据库、网络、数据库驱动程序和数据访问层(如 EF Core)。 尽管高级层和 O/RM(如 EF Core)大大简化了应用程序开发并改善了可维护性,但它们有时可能是不透明的,隐藏了性能关键的内部详细信息,例如正在执行的 SQL。

一些让人诟病的性能问题一直是热议话题,实际情况可能并不是性能差,而是需要掌握如何规避陷阱和避开影响性能的坑。

1.正确使用索引

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

// 在开始时匹配,使用索引 (SQL Server)
var posts1 = context.Posts.Where(p => p.Title.StartsWith("A")).ToList();
// 在末尾匹配,不使用索引
var posts2 = context.Posts.Where(p => p.Title.EndsWith("A")).ToList();
2.只查询需要的属性

EF Core 能非常轻松地查询出实体实例,然后将它们用于代码中。 但是,查询实体实例可能会频繁从数据库中拉取回超出所需的数据。 考虑以下情况:

foreach (var blog in context.Blogs)
{
 Console.WriteLine("Blog: " + blog.Url);
}

SQL

SELECT [b].[BlogId], [b].[CreationDate], [b].[Name], [b].[Rating], [b].[Url]...
FROM [Blogs]

对于这一点的优化方法是,使用 Select 告诉 EF 要查询出的列:

foreach (var blogName in context.Blogs.Select(b => b.Url))
{
  Console.WriteLine("Blog: " + blogName);
}

SQL

SELECT [b].[Url]
FROM [Blogs] AS [b]

这种方法对只读查询非常有用,但如果你需要更新提取的Blog,事情会变得复杂,因为 EF 的更改跟踪仅适用于实体实例。

3.限制结果集大小

返回的行数取决于数据库中的实际数据,因此不可能知道将从数据库中加载的数据量,结果占用的内存量以及处理这些结果时(例如,通过网络将它们发送到用户浏览器)将额外生成的负载量。

var blogs25 = context.Posts
 .Where(p => p.Title.StartsWith("A"))
 .Take(5)
 .ToList();
4.优先使用Async 异步方法

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

5.关闭状态追踪

跟踪行为决定了 EF Core 是否将有关实体实例的快照信息保留在其更改跟踪器中。 如果已跟踪某个实体,则该实体中检测到的任何更改都会在 SaveChanges() 期间永久保存到数据库。当决定只查询数据,不更改数据时,非跟踪查询十分有用,非跟踪查询的执行会更快,因为无需为查询实体设置快照跟踪信息。 如果不需要更新从数据库中检索到的实体,则应优先使用非跟踪查询。

//单体查询关闭:
AsNoTracking()
//整个上下文关闭:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
6.关闭DetectChanges 状态同步

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

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

Entry 等。

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

context.ChangeTracker.AutoDetectChangesEnabled = false;
//执行操作后手动同步状态
context.ChangeTracker.DetectChanges();
7.使用 EF.Functions.xxx 进行模糊查询

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

var result= context.Blogs.Where(b => EF.Functions.Like(b.BlogName, "%xcode%"))
8.高效分页

分页是指在页面中检索结果,而不是一次性检索结果;这通常针对大型结果集完成,其中显示用户界面,允许用户导航到结果的下一页或上一页。

9.缓冲和流式处理

缓冲指将所有查询结果加载到内存中,而流式处理意味着 EF 每次向应用程序提供一个结果,绝不让内存中包含整个结果集。 原则上,流式处理查询的内存要求是固定的:无论查询返回 1 行还是 1000 行,内存要求都相同。另一方面,返回的行数越多,缓冲查询需要的内存越多。 对于产生大型结果集的查询,这可能是一个重要的性能因素。

// oList和ToArray会导致整个结果集被缓冲:
var blogsList = context.Posts.Where(p => p.Title.StartsWith("A")).ToList();
var blogsArray = context.Posts.Where(p => p.Title.StartsWith("A")).ToArray();
​
// foreach流式,每次处理一行:
foreach (var blog in context.Posts.Where(p => p.Title.StartsWith("A")))
{
 // ...
}

如果查询只返回几个结果,可能无需担心这一点。 但是,如果查询可能返回的行数非常多,则有必要考虑使用流式处理而不是缓冲。

10.预先加载相关实体

在ORM中有一个经典的问题,那就是N+1问题,一般出现在一对多查询中。N+1问题是这样产生的:查询主表一次,得到N条记录,根据N条记录,查询关联副表,更需要N次。在数据量较大的情况下,是非常消耗数据路性能的。

EFCore中预先加载表示从数据库中加载关联数据,作为初始查询的一部分,这样就可以在一次往返中提取所有必需的数据。

使用 Include 方法来指定要包含在查询结果中的关联数据:

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

任何技术都有两面性,使用N+1 模式的优点是可以单独缓存部分数据。

11.使用原始 SQL

在某些情况下,你的查询存在更优化的 SQL,而 EF 不能生成这种 SQL。 在这些情况下,手动编写 SQL 可以显著提高性能,例如通过 FromSqlRaw.

<think>我们正在讨论如何优化EF Core 8的查询性能。根据引用[1]和[2],我们可以从多个方面入手,包括查询的生成机制、跟踪查询与非跟踪查询、加载导航属性的方式、拆分查询、执行原始SQL以及全局查询过滤器等。下面我将详细说明优化策略。 ### 1. 使用非跟踪查询(AsNoTracking) 当只需要读取数据而不进行更新时,使用非跟踪查询可以显著提高性能,因为EF Core不会为实体创建跟踪快照,从而减少内存消耗和计算开销。 ```csharp var products = context.Products .AsNoTracking() .ToList(); ``` ### 2. 合理加载导航属性 避免使用懒加载(延迟加载)导致的N+1查询问题。建议使用预加载(Include)或显式加载。 - **预加载**:一次性加载所有相关数据。 ```csharp var orders = context.Orders .Include(o => o.OrderDetails) // 预加载OrderDetails .ToList(); ``` - **显式加载**:在需要时再加载相关数据。 ```csharp var order = context.Orders.First(); context.Entry(order) .Collection(o => o.OrderDetails) .Load(); ``` ### 3. 使用拆分查询(Split Queries) 对于包含多个集合导航属性的查询,使用拆分查询可以避免笛卡尔爆炸问题。EF Core 5.0+支持拆分查询。 ```csharp var orders = context.Orders .Include(o => o.OrderDetails) .AsSplitQuery() // 拆分为多个SQL查询 .ToList(); ``` 或者在全局范围内启用拆分查询: ```csharp protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(connectionString, options => options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)); } ``` ### 4. 编写高效的LINQ查询 - **避免在查询中使用客户端计算**:确保查询条件能够被转换为SQL。 - **使用投影(Select)**:只查询需要的字段,而不是整个实体。 ```csharp var productNames = context.Products .Where(p => p.Price > 100) .Select(p => p.Name) .ToList(); ``` ### 5. 执行原始SQL查询 对于复杂的查询,直接使用原始SQL可能更高效。注意:使用原始SQL时要防止SQL注入。 ```csharp var products = context.Products .FromSqlRaw("SELECT * FROM Products WHERE Price > 100") .ToList(); ``` ### 6. 使用全局查询过滤器 全局查询过滤器可以自动应用过滤条件,但要注意避免在查询中引入额外的复杂操作。 ```csharp protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Product>().HasQueryFilter(p => !p.IsDeleted); } ``` ### 7. 批量操作 EF Core 7.0+支持批量更新和删除,这比逐条操作更高效。 ```csharp // 批量更新 context.Products .Where(p => p.Price < 50) .ExecuteUpdate(setters => setters.SetProperty(p => p.Price, p => p.Price * 1.1)); // 批量删除 context.Products .Where(p => p.IsDeleted) .ExecuteDelete(); ``` ### 8. 索引优化数据库表中为经常用于查询条件的列创建索引,可以加速查询。 ### 9. 使用异步操作 异步操作可以避免阻塞线程,提高应用程序的响应能力。 ```csharp var products = await context.Products .AsNoTracking() .ToListAsync(); ``` ### 10. 分页 当处理大量数据时,使用分页可以减少单次查询的数据量。 ```csharp var page = context.Products .OrderBy(p => p.Id) .Skip(10) .Take(10) .ToList(); ``` 通过以上方法,可以显著提高EF Core 8的查询性能。在实际应用中,需要根据具体场景选择合适的方法。 §§相关问题§§ 1. EF Core中如何配置全局拆分查询? 2. 如何避免EF Core查询中的N+1问题? 3. EF Core批量操作有哪些方式?</think>### 如何优化 Entity Framework Core 8 查询性能 以下是经过验证的 EF Core 8 查询性能优化策略,按优先级排序: #### 1️⃣ **启用非跟踪查询**(核心优化) ```csharp // 只读场景强烈推荐 var products = context.Products .AsNoTracking() // ⚡ 减少内存开销和变更跟踪计算 .Where(p => p.Price > 100) .ToList(); ``` > 📌 **适用场景**:数据展示、报表生成等只读操作。性能提升可达 30-50%[^2] #### 2️⃣ **精准数据加载**(避免过度加载) ```csharp // 投影查询(仅选择必要字段) var result = context.Orders .Where(o => o.Date > DateTime.Now.AddDays(-30)) .Select(o => new { o.Id, o.Total, CustomerName = o.Customer.Name }) // ✅ 精确字段 .ToList(); // 预加载控制(避免N+1查询) var orders = context.Orders .Include(o => o.Items) // 显式加载关联数据 .ThenInclude(i => i.Product) .Take(100) // 分页限制 .ToList(); ``` #### 3️⃣ **拆分复杂查询**(防止笛卡尔爆炸) ```csharp // EF Core 5+ 特性 var orders = context.Orders .Include(o => o.Items) .AsSplitQuery() // 🔄 分解为多个SQL查询 .ToList(); ``` > ⚠️ 在 `DbContext` 中全局配置: > ```csharp > optionsBuilder.UseSqlServer(connStr, o => > o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)); > ``` #### 4️⃣ **批量操作优化**(EF Core 7+ 特性) ```csharp // 批量删除(避免逐条删除) context.Products .Where(p => p.Stock == 0) .ExecuteDelete(); // 🚀 单条SQL执行 // 批量更新 context.Products .Where(p => p.Price < 10) .ExecuteUpdate(s => s.SetProperty(p => p.Price, p => p.Price * 1.1)); ``` #### 5️⃣ **高效过滤与索引** ```csharp // 启用参数化查询(防SQL注入+重用执行计划) var minPrice = 50; var products = context.Products .Where(p => p.Price > minPrice) // ✅ 参数化 .ToList(); // 数据库层面优化 modelBuilder.Entity<Product>() .HasIndex(p => new { p.Category, p.Price }); // 复合索引 ``` #### 6️⃣ **异步流式处理**(大数据集) ```csharp await foreach (var product in context.Products .AsNoTracking() .Where(p => p.Stock > 0) .AsAsyncEnumerable()) // 📦 流式加载 { // 逐条处理 } ``` #### 7️⃣ **原始SQL优化**(复杂查询兜底方案) ```csharp var results = context.Products .FromSqlRaw("SELECT * FROM Products WITH (NOLOCK) WHERE CategoryId = {0}", categoryId) .IgnoreQueryFilters() // 可跳过全局过滤器 .ToList(); ``` #### 8️⃣ **全局配置优化** ```diff services.AddDbContext<AppDbContext>(options => { options.UseSqlServer(configuration.GetConnectionString("Default"), o => { o.EnableRetryOnFailure(3); o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); // 全局拆分 }); options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); // 全局非跟踪 }); ``` ### 性能验证工具 1. **日志分析**:启用 `DbContextOptionsBuilder.LogTo(Console.WriteLine)` 2. **EF Core 诊断工具**: ```csharp var listener = new DiagnosticListener("Microsoft.EntityFrameworkCore"); var observer = new KeyValueObserver(); listener.Subscribe(observer); ``` 3. **SQL Profiler**:监控实际生成的SQL语句 > 💡 **黄金法则**:始终通过 `EXPLAIN` 分析数据库执行计划,确保索引有效利用[^2] ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值