简体   繁体   English

在 ASP.NET Core 中使用 DbContext 注入并行 EF Core 查询

[英]Parallel EF Core queries with DbContext injection in ASP.NET Core

I have writing an ASP.NET Core web application that needs all the data from some tables of my database to later organize it into readable format for some analysis.我编写了一个 ASP.NET Core Web 应用程序,它需要我数据库中某些表中的所有数据,以便稍后将其组织成可读格式以进行某些分析。

My problem is that this data is potentially massive, and so in order to increase performance i decided to get this data in parallel and not one table at a time.我的问题是这些数据可能非常庞大,因此为了提高性能,我决定并行获取这些数据,而不是一次获取一张表。

My issue is that i dont quite understand how to achieve this with the inherit dependency injection as in order to be able to do parallel work, i need to instantiate the DbContext for each of these parallel work.我的问题是我不太明白如何通过继承依赖注入来实现这一点,因为为了能够进行并行工作,我需要为这些并行工作中的每一个实例化DbContext

The below code produces this exception:以下代码产生此异常:

---> (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 project: ASP.NET 核心项目:

Startup.cs:启动.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();
}

Controller's action method:控制器的动作方法:

[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 Library project: .NET 核心库项目:

DataFetcher's InitializeData - to get all table records according to some irrelevant parameters: 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's sample task: 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();
    }
}

I'm not sure you do actually need multiple contexts here.我不确定您是否真的需要多个上下文。 You have have noticed that in the EF Core docs, there's this conspicuous warning:您已经注意到,在 EF Core 文档中,有一个明显的警告:

Warning警告

EF Core does not support multiple parallel operations being run on the same context instance. EF Core 不支持在同一上下文实例上运行多个并行操作。 You should always wait for an operation to complete before beginning the next operation.在开始下一个操作之前,您应该始终等待操作完成。 This is typically done by using the await keyword on each asynchronous operation.这通常通过在每个异步操作上使用await关键字来完成。

This is not entirely accurate, or rather, it's simply worded somewhat confusingly.这并不完全准确,或者更确切地说,它只是措辞有些混乱。 You can actually run parallel queries on a single context instance.您实际上可以在单个上下文实例上运行并行查询。 The issue comes in with EF's change tracking and object fixup.问题来自 EF 的更改跟踪和对象修复。 These types of things don't support multiple operations happening at the same time, as they need to have a stable state to work from when doing their work.这些类型的东西不支持同时发生多个操作,因为它们在工作时需要有一个稳定的状态才能工作。 However, that really just limits your ability to do certain things.然而,这实际上只是限制了你做某些事情的能力。 For example, if you were to run parallel saves/select queries, the results could be garbled.例如,如果您要运行并行保存/选择查询,结果可能会出现乱码。 You might not get back things that are actually there now or change tracking could get messed up while it's attempt to create the necessary insert/update statements, etc. However, if you're doing non-atomic queries, such as selects on independent tables as you wish to do here, there's no real issue, especially, if you're not planning on doing further operations like edits on the entities you're selecting out, and just planning on returning them to a view or something.您可能无法取回现在实际存在的内容,或者在尝试创建必要的插入/更新语句等时更改跟踪可能会变得一团糟。 但是,如果您正在执行非原子查询,例如在独立表上进行选择正如您希望在这里做的那样,没有真正的问题,特别是如果您不打算进行进一步的操作,例如对您选择的实体进行编辑,而只是打算将它们返回到视图或其他内容。

If you truly determine you need separate contexts, your best bet is new up your context with a using.如果你真的确定你需要单独的上下文,你最好的选择是使用 using 来更新你的上下文。 I haven't actually tried this before, but you should be able to inject DbContextOptions<AmsdbaContext> into your class where these operations are happening.我之前实际上没有尝试过,但是您应该能够将DbContextOptions<AmsdbaContext>注入到您的类中,其中发生了这些操作。 It should already be registered in the service collection since it's injected into your context when the service collection instantiates that.它应该已经在服务集合中注册,因为它在服务集合实例化时注入到您的上下文中。 If not, you can always just build a new one:如果没有,您总是可以构建一个新的:

var options = new DbContextOptionsBuilder()
    .UseSqlServer(connectionString)
    .Build()
    .Options;

In either case, then:在任何一种情况下,那么:

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

It's better to use await to get the actual result.最好使用await来获得实际结果。 This way, you don't even need WaitAll / WhenAll /etc.这样,您甚至不需要WaitAll / WhenAll / 等。 and you're not blocking on the call to Result .并且您不会阻止对Result的调用。 Since tasks return hot, or already started, simply postponing calling await until each has been created is enough to buy you parallel processing.由于任务返回热值,或者已经开始,只需推迟调用 await 直到每个任务都被创建就足以购买并行处理。

Just be careful with this that you select everything you need within the usings.请注意这一点,您可以在使用中选择所需的一切。 Now that EF Core supports lazy-loading, if you're using that, an attempt to access a reference or collection property that hasn't been loaded will trigger an ObjectDisposedException , since the context will be gone.现在 EF Core 支持延迟加载,如果您正在使用它,尝试访问尚未加载的引用或集合属性将触发ObjectDisposedException ,因为上下文将消失。

Simple answer is - you do not.简单的答案是 - 你没有。 You need an alternative way to generate dbcontext instances.您需要另一种生成 dbcontext 实例的方法。 The standard approach is to get the same instance on all requests for a DbContext in the same HttpRequest.标准方法是在同一个 HttpRequest 中对 DbContext 的所有请求获取相同的实例。 You can possibly override ServiceLifetime, but that then changes the behavior of ALL requests.您可以覆盖 ServiceLifetime,但这会改变所有请求的行为。

  • You can register a second DbContext (subclass, interface) with a different service lifetime.您可以注册具有不同服务生命周期的第二个 DbContext(子类、接口)。 Even then you need to handle the creation manually as you need to call it once for every thread.即使这样,您也需要手动处理创建,因为您需要为每个线程调用一次。

  • You manaully create them.您手动创建它们。

Standard DI simply comes to an end here.标准 DI 到这里就结束了。 It is QUITE lacking, even compared to older MS DI frameworks where you possibly could put up a separate processing class with an attribute to override creation.即使与旧的 MS DI 框架相比,它也非常缺乏,在旧的 MS DI 框架中,您可能会放置一个带有属性的单独处理类来覆盖创建。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM