用上这几种.NET EF Core性能调优,查询性能飙升
Posted .NET100
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用上这几种.NET EF Core性能调优,查询性能飙升相关的知识,希望对你有一定的参考价值。
1、避免在循环中进行查询操作:
避免在循环中进行查询操作,可以将查询结果缓存到内存中,然后对内存中的数据进行操作,可以提高性能。这种方式适合集合数据量少的数据,否则利大于弊。
// 不建议的方式:在循环中进行查询操作
foreach (var item in itemList)
var result = context.Items.FirstOrDefault(i => i.Id == item.Id);
//执行逻辑
// 推荐的方式:将查询结果缓存到内存中,然后对内存中的数据进行操作
var itemIds = itemList.Select(i => i.Id).ToList();
var results = context.Items.Where(i => itemIds.Contains(i.Id)).ToList();
foreach (var item in itemList)
var result = results.FirstOrDefault(r => r.Id == item.Id);
//执行逻辑
2、避免使用懒加载:
避免使用懒加载,因为每次访问导航属性都会触发额外的数据库查询。使用显式加载或预先加载的技术可以提高性能。
// 不建议的方式:使用懒加载
var order = context.Orders.FirstOrDefault();
foreach (var item in order.Items)
//执行逻辑
// 推荐的方式:使用预先加载
var order = context.Orders.Include(o => o.Items).FirstOrDefault();
foreach (var item in order.Items)
//执行逻辑
3、合理使用Include方法:
Include方法可以在一次查询中获取所有相关的实体对象。但是,当涉及到大量数据时,Include方法会导致性能下降。可以使用手动链接查询代替Include方法。
// 不建议的方式:使用Include方法获取所有关联实体
var orders = context.Orders.Include(o => o.Items).ToList();
// 推荐的方式:使用手动链接查询代替Include方法
var orders = context.Orders
.Join(context.OrderItems,
o => o.Id,
oi => oi.OrderId,
(o, oi) => new Order = o, OrderItem = oi )
.ToList();
4、使用NoTracking方法:
使用NoTracking方法可以避免EF Core的跟踪功能。跟踪功能在更新和删除实体对象时非常有用,但是在只需要读取数据时,跟踪功能会导致额外的开销。使用NoTracking方法可以禁用跟踪功能,从而提高性能。
// 不建议的方式:使用默认跟踪功能
var order = context.Orders.FirstOrDefault();
// 推荐的方式:使用NoTracking方法
var order = context.Orders.AsNoTracking().FirstOrDefault();
//欢迎公众号:DOTNET开发跳槽
5、执行原始SQL查询:
有些情况下,使用原始的SQL语句可以比使用EF Core更高效。使用FromSqlRaw或者ExecuteSqlRaw方法可以执行原始SQL查询。
// 执行原始SQL查询
var orders = context.Orders.FromSqlRaw("SELECT * FROM Orders WHERE Status = \'Complete\'").ToList();
6、使用EF.CompileAsyncQuery
EF.CompileAsyncQuery是EF Core的一个扩展方法,它可以将LINQ表达式编译为一个异步查询。相比于动态生成LINQ查询,使用EF.CompileAsyncQuery可以提高查询性能,减少不必要的内存分配。
编译后的查询可以多次调用,而不必每次动态生成查询表达式。这样可以避免不必要的内存分配和查询优化开销,提高查询性能。在EF Core 5.0及以上版本中,EF.CompileAsyncQuery已经成为了标准的扩展方法,无需进行任何特殊的安装或配置即可使用。它适用于查询条件固定的情况,当然也可以重新编译,不过频繁的编译会造成内存和性能的开销。示例如下:
using Microsoft.EntityFrameworkCore.Query;
// 定义一个异步查询
private static readonly Func<MyDbContext, int, Task<Order>> GetOrderById =
EF.CompileAsyncQuery((MyDbContext context, int id) =>
context.Orders.FirstOrDefaultAsync(o => o.Id == id));
// 调用异步查询
var order = await GetOrderById(context, 1);
结语
本文讲述了6种性能调优的方式,希望对大家有所帮助,尤其是面试的时候,当面试官问您有没有什么办法提高EF Core的性能,大家可以回答其中几个。当然上面的方法不是绝对的,需要根据实际场景来应用,比如Include方法在数据量小的情况下适合使用,数据量大的话就适得其反了。大家还有什么EF Core调优的方法,欢迎留言讨论或者吐槽本文。
来源公众号:DotNet开发跳槽
在 ASP.NET Core 中使用 DbContext 注入并行 EF Core 查询
【中文标题】在 ASP.NET Core 中使用 DbContext 注入并行 EF Core 查询【英文标题】:Parallel EF Core queries with DbContext injection in ASP.NET Core 【发布时间】:2019-03-22 05:30:34 【问题描述】:我正在编写一个 ASP.NET Core Web 应用程序,该应用程序需要数据库中某些表中的所有数据,以便稍后将其组织成可读格式以进行某些分析。
我的问题是这些数据可能非常庞大,因此为了提高性能,我决定并行获取这些数据,而不是一次获取一个表。
我的问题是我不太明白如何通过继承依赖注入来实现这一点,因为为了能够进行并行工作,我需要为每个并行工作实例化 DbContext
。
下面的代码会产生这个异常:
---> (Inner Exception #6) System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'MyDbContext'.
at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker()
ASP.NET Core 项目:
Startup.cs:
public void ConfigureServices(IServiceCollection services)
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDistributedMemoryCache();
services.AddDbContext<AmsdbaContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ConnectionString"))
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
services.AddSession(options =>
options.Cookie.HttpOnly = true;
);
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
if (HostingEnvironment.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler("/Error");
app.UseHsts();
loggerFactory.AddLog4Net();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSession();
app.UseMvc();
控制器的动作方法:
[HttpPost("[controller]/[action]")]
public ActionResult GenerateAllData()
List<CardData> cardsData;
using (var scope = _serviceScopeFactory.CreateScope())
using (var dataFetcher = new DataFetcher(scope))
cardsData = dataFetcher.GetAllData(); // Calling the method that invokes the method 'InitializeData' from below code
return something...;
.NET Core 库项目:
DataFetcher的InitializeData——根据一些不相关的参数获取所有表记录:
private void InitializeData()
var tbl1task = GetTbl1FromDatabaseTask();
var tbl2task = GetTbl2FromDatabaseTask();
var tbl3task = GetTbl3FromDatabaseTask();
var tasks = new List<Task>
tbl1task,
tbl2task,
tbl3task,
;
Task.WaitAll(tasks.ToArray());
Tbl1 = tbl1task.Result;
Tbl2 = tbl2task.Result;
Tbl3 = tbl3task.Result;
DataFetcher 的示例任务:
private async Task<List<SomeData>> GetTbl1FromDatabaseTask()
using (var amsdbaContext = _serviceScope.ServiceProvider.GetRequiredService<AmsdbaContext>())
amsdbaContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await amsdbaContext.StagingRule.Where(x => x.SectionId == _sectionId).ToListAsync();
【问题讨论】:
【参考方案1】:我不确定您是否真的需要多个上下文。您已经注意到,在 EF Core 文档中,有一个明显的警告:
警告
EF Core 不支持在同一个上下文实例上运行多个并行操作。在开始下一个操作之前,您应该始终等待操作完成。这通常通过在每个异步操作上使用
await
关键字来完成。
这并不完全准确,或者更确切地说,它只是措辞有些混乱。您实际上可以在单个上下文实例上运行并行查询。该问题与 EF 的更改跟踪和对象修复有关。这些类型的事物不支持同时发生多个操作,因为它们在工作时需要有一个稳定的状态才能工作。然而,这实际上只是限制了你做某些事情的能力。例如,如果您要运行并行保存/选择查询,结果可能会出现乱码。您可能无法取回现在实际存在的内容,或者在尝试创建必要的插入/更新语句等时更改跟踪可能会变得混乱。但是,如果您正在执行非原子查询,例如在独立表上进行选择正如您希望在这里做的那样,没有真正的问题,特别是如果您不打算对您选择的实体进行进一步的操作,例如编辑您选择的实体,而只是计划将它们返回到视图或其他内容。
如果您确实确定需要单独的上下文,最好的办法是使用 using 来更新您的上下文。我之前实际上没有尝试过,但是您应该能够将DbContextOptions<AmsdbaContext>
注入到您正在发生这些操作的类中。它应该已经在服务集合中注册,因为它是在服务集合实例化时注入到您的上下文中的。如果没有,您可以随时构建一个新的:
var options = new DbContextOptionsBuilder()
.UseSqlServer(connectionString)
.Build()
.Options;
无论哪种情况,那么:
List<Tbl1> tbl1data;
List<Tbl2> tbl2data;
List<Tbl3> tbl3data;
using (var tbl1Context = new AmsdbaContext(options))
using (var tbl2Context = new AmsdbaContext(options))
using (var tbl3Context = new AmsdbaContext(options))
var tbl1task = tbl1Context.Tbl1.ToListAsync();
var tbl2task = tbl2Context.Tbl2.ToListAsync();
var tbl3task = tbl3Context.Tbl3.ToListAsync();
tbl1data = await tbl1task;
tbl2data = await tbl2task;
tbl3data = await tbl3task;
最好使用await
来获得实际结果。这样,你甚至不需要WaitAll
/WhenAll
/etc。而且您不会阻止对Result
的呼叫。由于任务返回热,或者已经开始,只需推迟调用 await 直到每个都被创建就足以为您购买并行处理。
请注意在使用中选择所需的所有内容。现在 EF Core 支持延迟加载,如果您使用延迟加载,尝试访问尚未加载的引用或集合属性将触发 ObjectDisposedException
,因为上下文将消失。
【讨论】:
感谢您的详细回答,但如果我需要进行 10-20 次类似的查询怎么办?我必须使用重复关键字吗??【参考方案2】:简单的答案是 - 你没有。您需要另一种方法来生成 dbcontext 实例。标准方法是在同一个 HttpRequest 中对 DbContext 的所有请求获取相同的实例。您可以覆盖 ServiceLifetime,但这会改变所有请求的行为。
您可以注册具有不同服务生命周期的第二个 DbContext(子类、接口)。即使这样,您也需要手动处理创建,因为您需要为每个线程调用一次。
您可以手动创建它们。
标准 DI 到此结束。即使与旧的 MS DI 框架相比,它也很缺乏,在旧框架中,您可以建立一个带有属性的单独处理类来覆盖创建。
【讨论】:
以上是关于用上这几种.NET EF Core性能调优,查询性能飙升的主要内容,如果未能解决你的问题,请参考以下文章
#yyds干货盘点#愚公系列2023年02月 .NET/C#知识点-EF Core性能优化之显示编译
在 ASP.NET Core 中使用 DbContext 注入并行 EF Core 查询