依赖注入导致 Task.Run() 内的 DbContext 运行时错误
Dependency Injection causes DbContext runtime error inside Task.Run()
我的问题是,如何为我的 DbContext 和 Repository classes 使用依赖注入并在多个任务中使用它们?当我尝试这样做时,出现以下错误;我的想法是我应该 不 得到这个错误,因为我正在使用 AddTransient/ServiceLifetime.Transient 添加 DbContext 和 Repository 服务:
An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
如果您愿意,可以在 this shared Google Drive link 下载演示此问题的完整 .Net Core 控制台项目。在 appsettings.json 文件中,将 "SQLDev01" 更改为您有权访问的任何 SQL 服务器的名称,它应该 运行 没有其他更改。我还将在下面包含足够的代码来说明这个问题。
这是我的 appsettings.json:
{
"ConnectionStrings": {
"MyConnectionString": "Data Source=SqlDev01; Initial Catalog=master; Integrated Security=SSPI;"
}
}
这是我的Program.cs。我正在使用 AddTransient 注册我的自定义 classes 并使用 ServiceLifetime.Transient 注册 DBContext。由于 Transient 每次被请求时都会创建一个新实例,所以我认为即使启动了多个任务也能正常工作:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MyTest.Library;
using MyTest.Library.Data;
using System;
using System.IO;
namespace MyTest.Host
{
class Program
{
public static ServiceProvider ServiceProvider { get; set; }
static void Main(string[] args)
{
try
{
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
IServiceCollection services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("MyConnectionString"))
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
, ServiceLifetime.Transient);
services.AddTransient<IMyRepository, MyRepository>();
services.AddTransient<IMain, Main>();
ServiceProvider = services.BuildServiceProvider();
var main = ServiceProvider.GetService<IMain>();
main.Run();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
ServiceProvider?.Dispose();
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
}
}
}
这是生成上述 运行 时间错误的依赖注入 class。注释掉的部分运行成功; 运行时间错误完全是由于使用 TPL:
using Microsoft.Extensions.Configuration;
using MyTest.Library.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyTest.Library
{
public interface IMain
{
void Run();
}
public class Main : IMain
{
private readonly IConfiguration _configuration;
private readonly IMyRepository _myRepository;
public Main(IConfiguration configuration,
IMyRepository myRepository)
{
_configuration = configuration;
_myRepository = myRepository;
}
public void Run()
{
/*
var getFromDatabase = _myRepository.GetFromDatabase();
if (getFromDatabase == null)
{
Console.WriteLine("getFromDatabase == null");
}
else
{
Console.WriteLine($"name={getFromDatabase.name} number={getFromDatabase.number }");
}
*/
List<Task> tasks = new List<Task>();
for (int i = 0; i <= 10; i++)
{
Task task = Task.Run(() =>
{
var getFromDatabase = _myRepository.GetFromDatabase();
if (getFromDatabase == null)
{
Console.WriteLine("getFromDatabase == null");
}
else
{
Console.WriteLine($"name={getFromDatabase.name} number={getFromDatabase.number }");
}
});
tasks.Add(task);
}
try
{
Task.WaitAll(tasks.ToArray());
foreach (var task in tasks)
{
Console.WriteLine($"{task.Status}, {task.Exception?.ToString()}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
这是获取依赖注入的存储库 class,它从主数据库读取:
using Microsoft.EntityFrameworkCore;
using MyTest.Library.Models;
using System.Linq;
namespace MyTest.Library.Data
{
public interface IMyRepository
{
GetFromDatabase GetFromDatabase();
}
public class MyRepository : IMyRepository
{
private readonly MyDbContext _myDbContext;
public MyRepository(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
public GetFromDatabase GetFromDatabase()
{
const string sql = "SELECT TOP 1 name, number FROM dbo.spt_values";
return _myDbContext.GetFromDatabase.FromSql(sql).FirstOrDefault();
}
}
}
这是我的 DbContext class,它被依赖注入:
using Microsoft.EntityFrameworkCore;
using MyTest.Library.Models;
namespace MyTest.Library.Data
{
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
public virtual DbQuery<GetFromDatabase> GetFromDatabase { get; set; }
}
}
我意识到我可以简单地新建一个 DbContext 和 MyRepository 并避免 运行time 错误,但我更愿意使用依赖注入。有什么方法可以使用依赖注入,启动多个任务,并避免这种 运行 时间错误?
一种可能的方法是创建工厂
//...omitted for brevity
services.AddTransient<IMyRepository, MyRepository>();
services.AddTransient<Func<IMyRepository>>(sp => () => sp.GetRequiredService<IMyRepository>());
//...omitted for brevity
并将其注入依赖项。
public class Main : IMain {
private readonly IConfiguration _configuration;
private readonly Func<IMyRepository> factory;
public Main(IConfiguration configuration,
Func<IMyRepository> factory) {
_configuration = configuration;
this.factory = factory;
}
//...omitted for brevity
使用工厂获取您的独立实例以供执行。
//...omitted for brevity
for (int i = 0; i <= 10; i++) {
Task task = Task.Run(() => {
var getFromDatabase = factory().GetFromDatabase();
if (getFromDatabase == null) {
Console.WriteLine("getFromDatabase == null");
} else {
Console.WriteLine($"name={getFromDatabase.name} number={getFromDatabase.number }");
}
});
tasks.Add(task);
}
//...omitted for brevity
我建议将您的存储库设为一次性,并让它在不再处于范围内时释放资源。
我的问题是,如何为我的 DbContext 和 Repository classes 使用依赖注入并在多个任务中使用它们?当我尝试这样做时,出现以下错误;我的想法是我应该 不 得到这个错误,因为我正在使用 AddTransient/ServiceLifetime.Transient 添加 DbContext 和 Repository 服务:
An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
如果您愿意,可以在 this shared Google Drive link 下载演示此问题的完整 .Net Core 控制台项目。在 appsettings.json 文件中,将 "SQLDev01" 更改为您有权访问的任何 SQL 服务器的名称,它应该 运行 没有其他更改。我还将在下面包含足够的代码来说明这个问题。
这是我的 appsettings.json:
{
"ConnectionStrings": {
"MyConnectionString": "Data Source=SqlDev01; Initial Catalog=master; Integrated Security=SSPI;"
}
}
这是我的Program.cs。我正在使用 AddTransient 注册我的自定义 classes 并使用 ServiceLifetime.Transient 注册 DBContext。由于 Transient 每次被请求时都会创建一个新实例,所以我认为即使启动了多个任务也能正常工作:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MyTest.Library;
using MyTest.Library.Data;
using System;
using System.IO;
namespace MyTest.Host
{
class Program
{
public static ServiceProvider ServiceProvider { get; set; }
static void Main(string[] args)
{
try
{
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
IServiceCollection services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("MyConnectionString"))
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
, ServiceLifetime.Transient);
services.AddTransient<IMyRepository, MyRepository>();
services.AddTransient<IMain, Main>();
ServiceProvider = services.BuildServiceProvider();
var main = ServiceProvider.GetService<IMain>();
main.Run();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
ServiceProvider?.Dispose();
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
}
}
}
这是生成上述 运行 时间错误的依赖注入 class。注释掉的部分运行成功; 运行时间错误完全是由于使用 TPL:
using Microsoft.Extensions.Configuration;
using MyTest.Library.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyTest.Library
{
public interface IMain
{
void Run();
}
public class Main : IMain
{
private readonly IConfiguration _configuration;
private readonly IMyRepository _myRepository;
public Main(IConfiguration configuration,
IMyRepository myRepository)
{
_configuration = configuration;
_myRepository = myRepository;
}
public void Run()
{
/*
var getFromDatabase = _myRepository.GetFromDatabase();
if (getFromDatabase == null)
{
Console.WriteLine("getFromDatabase == null");
}
else
{
Console.WriteLine($"name={getFromDatabase.name} number={getFromDatabase.number }");
}
*/
List<Task> tasks = new List<Task>();
for (int i = 0; i <= 10; i++)
{
Task task = Task.Run(() =>
{
var getFromDatabase = _myRepository.GetFromDatabase();
if (getFromDatabase == null)
{
Console.WriteLine("getFromDatabase == null");
}
else
{
Console.WriteLine($"name={getFromDatabase.name} number={getFromDatabase.number }");
}
});
tasks.Add(task);
}
try
{
Task.WaitAll(tasks.ToArray());
foreach (var task in tasks)
{
Console.WriteLine($"{task.Status}, {task.Exception?.ToString()}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
这是获取依赖注入的存储库 class,它从主数据库读取:
using Microsoft.EntityFrameworkCore;
using MyTest.Library.Models;
using System.Linq;
namespace MyTest.Library.Data
{
public interface IMyRepository
{
GetFromDatabase GetFromDatabase();
}
public class MyRepository : IMyRepository
{
private readonly MyDbContext _myDbContext;
public MyRepository(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
public GetFromDatabase GetFromDatabase()
{
const string sql = "SELECT TOP 1 name, number FROM dbo.spt_values";
return _myDbContext.GetFromDatabase.FromSql(sql).FirstOrDefault();
}
}
}
这是我的 DbContext class,它被依赖注入:
using Microsoft.EntityFrameworkCore;
using MyTest.Library.Models;
namespace MyTest.Library.Data
{
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
public virtual DbQuery<GetFromDatabase> GetFromDatabase { get; set; }
}
}
我意识到我可以简单地新建一个 DbContext 和 MyRepository 并避免 运行time 错误,但我更愿意使用依赖注入。有什么方法可以使用依赖注入,启动多个任务,并避免这种 运行 时间错误?
一种可能的方法是创建工厂
//...omitted for brevity
services.AddTransient<IMyRepository, MyRepository>();
services.AddTransient<Func<IMyRepository>>(sp => () => sp.GetRequiredService<IMyRepository>());
//...omitted for brevity
并将其注入依赖项。
public class Main : IMain {
private readonly IConfiguration _configuration;
private readonly Func<IMyRepository> factory;
public Main(IConfiguration configuration,
Func<IMyRepository> factory) {
_configuration = configuration;
this.factory = factory;
}
//...omitted for brevity
使用工厂获取您的独立实例以供执行。
//...omitted for brevity
for (int i = 0; i <= 10; i++) {
Task task = Task.Run(() => {
var getFromDatabase = factory().GetFromDatabase();
if (getFromDatabase == null) {
Console.WriteLine("getFromDatabase == null");
} else {
Console.WriteLine($"name={getFromDatabase.name} number={getFromDatabase.number }");
}
});
tasks.Add(task);
}
//...omitted for brevity
我建议将您的存储库设为一次性,并让它在不再处于范围内时释放资源。