如何在不违反 SOLID 原则的情况下在 Entity Framework Core 中进行多线程处理?

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

我有一个 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 原则。在这里:

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

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

这行得通吗?这是否违反任何 SOLID 原则?

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

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

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

中所述

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

你在不同的线程中共享相同的 dbcontext(不是线程安全的)

为每个工作单元创建带有反射/DbcontextFactory 的 Dbcontext 的新实例都可以修复错误(它只是在如何在每个工作单元中创建一个新实例方面有所不同,哪个解决方案是最好的 - 它是基于意见的,你只需要确保不与不同的工作单元共享同一个 dbcontext 实例)

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

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