我正在编写一个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();
}
}
答案 0 :(得分:1)
简单的答案是-您不会。您需要一种替代方法来生成dbcontext实例。标准方法是在同一HttpRequest中对DbContext的所有请求上获得相同的实例。您可以覆盖ServiceLifetime,但这会更改所有请求的行为。
您可以注册另一个具有不同服务寿命的DbContext(子类,接口)。即使那样,您也需要手动处理创建,因为您需要为每个线程调用一次。
您可以手动创建它们。
标准DI到这里就结束了。即使与较旧的MS DI框架相比,它还是相当缺乏的,在MS DI框架中,您可能会建立一个带有属性的单独处理类以覆盖创建。
答案 1 :(得分:1)
我不确定这里是否确实需要多个上下文。您已经注意到,在EF Core文档中,有一个明显的警告:
警告
EF Core不支持在同一上下文实例上运行多个并行操作。在开始下一个操作之前,您应始终等待操作完成。通常,通过在每个异步操作上使用
await
关键字来完成此操作。
这并不完全准确,或者只是措辞有些混乱。您实际上可以在单个上下文实例上运行并行查询。该问题与EF的更改跟踪和对象修复有关。这些类型的事物不支持同时发生的多个操作,因为它们需要保持稳定的状态才能开始工作。但是,这实际上只是限制了您执行某些操作的能力。例如,如果要运行并行保存/选择查询,则结果可能会出现乱码。您可能不会找回现在实际存在的东西,或者在尝试创建必要的插入/更新语句等时更改跟踪可能会弄乱。但是,如果您执行的是非原子查询,例如对独立表的选择就像您希望在此处所做的那样,没有任何实际问题,特别是如果您不打算进行进一步的操作,例如对要选择的实体进行编辑,而只是计划将其返回到视图或其他内容。
如果您确实确定需要单独的上下文,那么最好的选择是使用来重新创建上下文。我以前实际上并没有尝试过,但是您应该能够将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
的呼叫。由于任务返回热态或已经开始,因此只需将调用等待推迟到创建每个任务为止就足以向您提供并行处理。
请谨慎选择您在使用中需要的所有内容。现在,EF Core支持延迟加载,如果您正在使用延迟加载,则尝试访问尚未加载的引用或集合属性将触发ObjectDisposedException
,因为上下文将消失。