动态数据确定连接字符串时,创建DbContext的好模式是什么?

时间:2019-05-31 19:50:04

标签: c# multi-tenant ef-core-2.2

我从另一个Web API获取数据集。它可能包含来自不同客户端的多个记录。我必须转换这些数据并将它们写入客户端的数据库。每个客户端都有自己的数据库。客户端数据库的架构完全相同。

我创建了一个DbContext工厂,该工厂根据应用当前正在使用的客户端来创建DbContext的新实例。

public ClientDbContext CreateClientDbContext(string clientNumber)
{
      var optionsBuilder = new DbContextOptionsBuilder<ClientDbContext>();
      var clientConnection = string.Format(_configuration.GetConnectionString("clientConnection"), clientNumber);
      optionsBuilder.UseSqlServer(clientConnection);

      clientDbContext clientDbContext = new ClientDbContext(optionsBuilder.Options);

      return clientDbContext;
}

我以这种方式使用工厂:

foreach (var case in caseList)
{
    var clientDbContext = await _clientDbContextFactory.CreateClientDbContext(case.ClientNumber);
    _clientRepository = new ClientRepository(clientDbContext);
    var updatedCase = /// transform case here
    await _clientRepository.CreateCases(updatedCase);
}

为什么有这样做的最佳选择? 几行数据可能具有相同的客户端,所以我想重用相同的ClientDbContext。

2 个答案:

答案 0 :(得分:1)

您可以将创建ClientContext的逻辑移到另一个负责任的类中(按照SOLID原则),类似于DbContextFactory,并在其中存储为每个客户创建的DbContext。像这样:

public class DbContextFactory
{
    private readonly IConfiguration _configuration;
    private readonly Dictionary<string, ClientDbContext> _clientContexts = new Dictionary<string, ClientDbContext>();

    public DbContextFactory(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public ClientDbContext GetOrCreateClientContext(string clientNumber)
    {
        // if you have context already created - return it
        if (_clientContexts.ContainsKey(clientNumber))
            return _clientContexts[clientNumber];

        var optionsBuilder = new DbContextOptionsBuilder<ClientDbContext>();
        var clientConnection = string.Format(_configuration.GetConnectionString("clientConnection"), clientNumber);
        optionsBuilder.UseSqlServer(clientConnection);

        var clientDbContext = new ClientDbContext(optionsBuilder.Options);
        _clientContexts[clientNumber] = clientDbContext;

        return clientDbContext;
    }
}

然后在您的工作程序类中,您可以按ClientNumber对数据进行分组,为每个客户端创建(或已经创建)DbContext和存储库,然后进行数据更新。

public class Worker
{
    private readonly DbContextFactory _factory;

    public Worker(DbContextFactory factory)
    {
        _factory = factory;
    }

    public async Task DoWorkAsync()
    {
        // group by ClientNumber
        var groupedCases = caseList.GroupBy(x => x.ClientNumber);

        foreach (var groupedCase in groupedCases)
        {
            // For each client create context and repository
            var clientContext = _factory.GetOrCreateClientContext(groupedCase.Key);
            var clientRepository = new ClientRepository(clientContext);

            foreach (var @case in groupedCases)
            {
                var updatedCase = // transform case here
                await clientRepository.CreateCases(updatedCase);
            }
        }
    }
}

您可以使用依赖注入或仅创建以下类:

var factory = new DbContextFactory(yourConfiguration);
var worker = new Worker(factory);
await worker.DoWorkAsync();

答案 1 :(得分:0)

分批处理案例,因为这使您可以在继续下一个客户端连接之前重用客户端连接。像这样:

var batches = caseList.GroupBy(x => x.ClientNumber);

foreach (var batch in batches)
{
    var clientDbContext = await _clientDbContextFactory.CreateClientDbContext(batch.ClientNumber);
    _clientRepository = new ClientRepository(clientDbContext);

    foreach (var item in batch)
    {
        var updatedCase = /// transform case here
        await _clientRepository.CreateCases(updatedCase);
    }
}

您也可以从一批任务中受益,但是您应该对此进行概要分析,看看是否可以从中获得任何收益。看起来可能像这样:

var tasksInBatch = new List<Task>();
foreach (var item in batch)
{
    var updatedCase = /// transform case here
    tasksInBatch.Add(_clientRepository.CreateCases(updatedCase));
}

await Task.WhenAll(tasksInBatch);