实现支持多个连接以便可以注入的数据访问管理器的正确方法是什么?

时间:2019-01-28 11:23:31

标签: c# .net dependency-injection .net-core

我正在.Net Core 2.2中开发WebAPI。我需要一个自定义数据访问对象来查询数据。但是,我需要不同的连接才能完成不同的任务。使用DI和.NetCore IOptions模式实现此目的的最佳方法是什么?

我尝试在DAL对象中包含所有连接,并在调用该方法时指定连接ID。我可以为每个课程选择不同的课程,但是我想这太过分了。

让我们假设我将其放入appsettings.json

"DatabaseMetaConfiguration": {
  "MappingDb": {
    "DNS": "pgc.app.corp.acme.com",
    "Database": "mappings",
    "User": "mappings_user",
    "Pass": "111",
    "SSL": "SSL Mode=Prefer; Trust Server Certificate=true"
  },
  "StatusDb": {
    "DNS": "pgc.app.corp.acme.com",
    "Database": "status",
    "User": "status_user",
    "Pass": "222",
    "SSL": "SSL Mode=Prefer; Trust Server Certificate=true"
  },
  "MasterDb": {
    "DNS": "pgc.app.corp.acme.com",
    "Database": "master",
    "User": "master_user",
    "Pass": "333",
    "SSL": "SSL Mode=Prefer; Trust Server Certificate=true"
  },
  "BookDb": {
    "DNS": "pgc.app-books.corp.acme.com",
    "Database": "book",
    "User": "book_user",
    "Pass": "444",
    "SSL": "SSL Mode=Prefer; Trust Server Certificate=true"
  }
}

然后是POCO选项类:

public class PostgreSqlDatabaseMetaConfigurations : Dictionary<string, PostgreSqlDatabaseMetaConfiguration> { }

public class PostgreSqlDatabaseMetaConfiguration
{
    public string Database { get; set; }
    public string User { get; set; }
    public string Pass { get; set; }
    public string DNS { get; set; }
    public string SSL { get; set; }
    public int Timeout { get; set; }
    public int CommandTimeout { get; set; }
    public string ConnectionString { get { [...] } }
}

以及实际的DAL类:

public class PostgreSqlManager : IDisposable, ISqlManager
{
    private readonly Dictionary<string, NpgsqlConnection> _connections;
    private readonly ILogger<PostgreSqlManager> _logger;

    public PostgreSqlManager(IOptionsMonitor<PostgreSqlDatabaseMetaConfigurations> optionsAccessor, ILogger<PostgreSqlManager> logger)
    {
        _logger = logger;
        var dbConfigurations = optionsAccessor.CurrentValue;
        if (dbConfigurations == null || !dbConfigurations.Any())
            _logger.LogWarning("No connections configured!");

        // Create a dictionary of connections, indexed by the connection names
        _connections = dbConfigurations.ToDictionary(c => c.Key, c => new NpgsqlConnection(c.Value.ConnectionString));
    }

    // So, a sample method would be...

    public async Task<long> ExecuteScalarAsync(string connectionId, string commandText, List<DbParameter> parameters = null, CommandType commandType = CommandType.Text)
    {
        using (var command = GetDbCommand(_connections[connectionId], commandText, parameters, commandType))
        {
            try
            {
                await command.Connection.OpenAsync();
                return (Int64)await command.ExecuteScalarAsync().ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception on ExecuteScalarAsync({0})", commandText);
                throw;
            }
            finally
            {
                command.CloseCommand();
            }
        }
    }
}

最后,在ConfigureServices中,我有以下几行:

services.AddSingleton<ISqlManager, PostgreSqlManager>();
services.Configure<PostgreSqlDatabaseMetaConfigurations>(configuration.GetSection("DatabaseMetaConfiguration"));

(很抱歉,一大段代码,我想弄清楚。)

现在,如果要使用DAL服务类,则需要在控制器中注入ISqlManager。然后,在每个方法调用上,我需要指定所需的连接。

现在这是我的问题。如果我事先知道每个控制器类中需要哪个连接,该如何指定它,以便每个控制器使用我知道它将需要的连接?

更好的是,我可以在D容器中设置DAL类的几个不同实例,每个实例具有不同的连接,并让每个控制器类决定使用哪个实例吗?

感谢阅读。

1 个答案:

答案 0 :(得分:1)

我建议使用AddScoped注册您的PostgreSqlManager。这将在网络请求期间使对象保持活动状态。

然后您可以做类似

的操作
public interface IBookRepository
{
   Book GetByISBN(string isbn);
   Add(Book book)
   Delete(Book book)
}

public class BookRepository: IBookRepository
{
  private ISqlManager _sqlManager;

  public BookRepository(ISqlManager sqlManager)
  {
    _sqlManager = sqlManager;
  }

  public Book GetByISBN(string isbn)
  {
    var rows = _sqlManager.GetRows("BooksDb", "exec uspGetBookByISBN", CreateStringParam(isbn));

    // convert rows into book object(s)

   return books.FirstOrDefault();

  }

}

现在您可以为每个数据库注册一个或多个存储库,并注册例如

services.AddScoped();

然后在控制器中注入所需的内容。

public class MyController: Controller
{
  private IBookRepository _bookRepository;
  private IStatusRepository _statusRepository;
  public MyController(IBookRepository bookRepository, IStatusRepository statusRepository)
  {
    _bookRepository = bookRepository; 
    _statusRepository = statusRepository
  }
}

DI容器将确保只要已注册您的依赖项即可正确解析。