我有一个 ASP.NET Core Web API 应用程序,使用 Entity Framework Core 7 连接到数据库。我了解依赖注入的概念以及使用
AddDbContext
我可以在请求的生命周期内将特定的 DbContext 注入任何对象的事实。
而且效果很好UNTIL我想并行执行一些数据库请求。这是一个负责从数据库中获取数据的示例类:
public class CarData : ICarData
{
private readonly CarContext _car;
private readonly IMapper _mapper;
public CarData(CarContext car, IMapper mapper)
{
_car = car;
_mapper = mapper;
}
public bool CarExists(int carId)
{
return _car.Cars.Any(b => b.CarId == carId);
}
public async Task<CarDetailsOutput> GetCarDetails(int carId)
{
return (await GetCarsDetails(new List<int> { carId })).FirstOrDefault();
}
public async Task<List<CarDetailsOutput>> GetCarsDetails(List<int> carIds)
{
var cars = await _car.Cars
.Where(i => carIds.Contains(i.CarId))
.ToListAsync();
var output = _mapper.Map<IEnumerable<CarDetailsOutput>>(cars).ToList();
return output;
}
}
如果您有需要一些汽车数据的服务,您可以注入
ICarData
并重新使用它。但是,如果您想与任何也使用 GetCarsDetails
的函数并行调用 CarContext
- 它将失败。例如,你不能这样做:
public async Task<ItemDetailsOutput> DoSomeLogic(List<int> carIds)
{
var task1 = _carData.GetCarsDetails(carIds);
var task2 = _shopData.GetSomeInformation();
var task3 = _saleData.GetSomeInformation();
var task4 = _offerData.GetSomeInformation();
await Task.WhenAll(task1, task2, task3, task4);
// Do some more stuff
}
如果这些任务中的任何两个使用
CarContext
这将不起作用。
我有一个解决方案,但我不确定它是否违反任何 SOLID 原则。在这里:
创建此扩展:
public static class DbContextExtensions
{
public static TDbContext CreateNew<TDbContext>(this TDbContext db) where TDbContext : DbContext
{
var connectionString = db.Database.GetDbConnection().ConnectionString;
var options = CreateBuilderOptions<TDbContext>(connectionString);
return (TDbContext)Activator.CreateInstance(typeof(TDbContext), options);
}
private static DbContextOptions<TEntity> CreateBuilderOptions<TEntity>(string connectionString) where TEntity : DbContext
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var builder = new DbContextOptionsBuilder<TEntity>();
builder.UseSharedOptions(connectionString)
.UseInternalServiceProvider(serviceProvider);
return builder.Options;
}
}
更改将在并行过程中使用的方法,如
_carData.GetCarsDetails
:
public async Task<List<CarDetailsOutput>> GetCarsDetails(List<int> carIds)
{
await using var newCarContext = _car.CreateNew();
var cars = await newCarContext.Cars
.Where(i => carIds.Contains(i.CarId))
.ToListAsync();
var output = _mapper.Map<IEnumerable<CarDetailsOutput>>(cars).ToList();
return output;
}
有了这个,我就不用去想哪里注入factory,哪里注入DbContext了。我只注入所需的 DbContexts。但是,当我知道该方法将在并行过程中使用时,我从最初注入的 DbContext 创建了一个新实例。
我不会在并行流程中使用事务。
这行得通吗?这是否违反任何 SOLID 原则?
使用 AddDbContext 我可以将特定的 DbContext 注入任何对象 请求的生命周期。
在这种情况下,它在同一请求中共享相同的 dbcontext,并且如 document
中所述调用 WhenAll(Task[]) 方法不会阻塞调用线程。
你在不同的线程中共享相同的 dbcontext(不是线程安全的)
为每个工作单元创建带有反射/DbcontextFactory 的 Dbcontext 的新实例都可以修复错误(它只是在如何在每个工作单元中创建一个新实例方面有所不同,哪个解决方案是最好的 - 它是基于意见的,你只需要确保不与不同的工作单元共享同一个 dbcontext 实例)
您可以在同一文档中查看与使用 dbcontext 工厂相关的文档以及有关避免 dbcontext 线程问题 的更多详细信息