如何正确地在 EFCore 中进行多线程处理?

问题描述 投票:0回答:1

我有 ASP.NETCore WebApi 应用程序,使用 EFCore7 连接到数据库。我了解依赖注入的概念以及使用

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 原则。在这里:

  1. 创建这个扩展:

     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;
         }
     }
    
  2. 更改将在并行过程中使用的方法,如

    _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 创建了一个新实例。

我不会在并行流程中使用事务。

这是正确的做法吗?有更好的解决方案吗?感谢您的帮助!

c# asp.net-core entity-framework-core asp.net-core-webapi dbcontext
1个回答
0
投票

使用 AddDbContext 我可以将特定的 DbContext 注入任何对象 请求的生命周期。

在这种情况下,它在同一请求中共享相同的 dbcontext,并且如 document

中所述

调用 WhenAll(Task[]) 方法不会阻塞调用线程。

你在不同的线程中共享相同的 dbcontext

为每个工作单元使用反射/DbcontextFactory 创建新的 Dbcontext 实例都可以修复错误

您可以在同一文档中查看与使用 dbcontext 工厂相关的文档以及有关避免 dbcontext 线程问题 的更多详细信息

© www.soinside.com 2019 - 2024. All rights reserved.