使用注入DbContext将并行异步调用用于EF Core的最佳做法是什么? [英] What is the best practice in EF Core for using parallel async calls with an Injected DbContext?

查看:2722
本文介绍了使用注入DbContext将并行异步调用用于EF Core的最佳做法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个.NET Core 1.1 API与EF Core 1.1,并使用Microsoft的vanilla设置使用依赖注入来为我的服务提供DbContext。 (参考:



测试#2:在每个新的DbContext内创建新的API调用服务方法调用和并行threa d执行与WhenAll。



测试#2的结果:



结论:



对于那些怀疑结果的人,我用不同的用户负载运行了这些测试多次,平均每次基本相同。 p>

在我看来,并行处理的性能提升是微不足道的,这并不意味着放弃依赖注入的必要性,这将导致开发开销/维护债务,处理可能的错误错误的,并且偏离了微软的官方建议。



还有一件事要注意:正如你可以看到实际上有几个失败的请求与WhenAll策略,即使确保每次创建新的上下文。我不知道这个的原因,但是我宁愿在10ms的表现上获得500个错误。


I have a .NET Core 1.1 API with EF Core 1.1 and using Microsoft's vanilla setup of using Dependency Injection to provide the DbContext to my services. (Reference: https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro#register-the-context-with-dependency-injection)

Now, I am looking into parallelizing database reads as an optimization using WhenAll

So instead of:

var result1 = await _dbContext.TableModel1.FirstOrDefaultAsync(x => x.SomeId == AnId);
var result2 = await _dbContext.TableModel2.FirstOrDefaultAsync(x => x.SomeOtherProp == AProp); 

I use:

var repositoryTask1 = _dbContext.TableModel1.FirstOrDefaultAsync(x => x.SomeId == AnId);     
var repositoryTask2 = _dbContext.TableModel2.FirstOrDefaultAsync(x => x.SomeOtherProp == AProp);   
(var result1, var result2) = await (repositoryTask1, repositoryTask2 ).WhenAll();

This is all well and good, until I use the same strategy outside of these DB Repository access classes and call these same methods with WhenAll in my controller across multiple services:

var serviceTask1 = _service1.GetSomethingsFromDb(Id);
var serviceTask2 = _service2.GetSomeMoreThingsFromDb(Id);
(var dataForController1, var dataForController2) = await (serviceTask1, serviceTask2).WhenAll();

Now when I call this from my controller, randomly I will get concurrency errors like:

System.InvalidOperationException: ExecuteReader requires an open and available Connection. The connection's current state is closed.

The reason I believe is because sometimes these threads try to access the same tables at the same time. I know that this is by design in EF Core and if I wanted to I could create a new dbContext every time, but I am trying to see if there is a workaround. That's when I found this good post by Mehdi El Gueddari: http://mehdi.me/ambient-dbcontext-in-ef6/

In which he acknowledges this limitation:

an injected DbContext prevents you from being able to introduce multi-threading or any sort of parallel execution flows in your services.

And offers a custom workaround with DbContextScope.

However, he presents a caveat even with DbContextScope in that it won't work in parallel (what I'm trying to do above):

if you attempt to start multiple parallel tasks within the context of a DbContextScope (e.g. by creating multiple threads or multiple TPL Task), you will get into big trouble. This is because the ambient DbContextScope will flow through all the threads your parallel tasks are using.

His final point here leads me to my question:

In general, parallelizing database access within a single business transaction has little to no benefits and only adds significant complexity. Any parallel operation performed within the context of a business transaction should not access the database.

Should I not be using WhenAll in this case in my Controllers and stick with using await one-by-one? Or is dependency-injection of the DbContext the more fundamental problem here, therefore a new one should instead be created/supplied every time by some kind of factory?

解决方案

It came to the point where really the only way to answer the debate was to do a performance/load test to get comparable, empirical, statistical evidence so I could settle this once and for all.

Here is what I tested:

Cloud Load test with VSTS @ 200 users max for 4 minutes on a Standard Azure webapp.

Test #1: 1 API call with Dependency Injection of the DbContext and async/await for each service.

Results for Test #1:

Test #2: 1 API call with new creation of the DbContext within each service method call and using parallel thread execution with WhenAll.

Results for Test #2:

Conclusion:

For those who doubt the results, I ran these tests several times with varying user loads, and the averages were basically the same every time.

The performance gains with parallel processing in my opinion is insignificant, and this does not justify the need for abandoning Dependency Injection which would create development overhead/maintenance debt, potential for bugs if handled wrong, and a departure from Microsoft's official recommendations.

One more thing to note: as you can see there were actually a few failed requests with the WhenAll strategy, even when ensuring a new context is created every time. I am not sure the reason for this, but I would much prefer no 500 errors over a 10ms performance gain.

这篇关于使用注入DbContext将并行异步调用用于EF Core的最佳做法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆