分离 NpgsqlConnection 的关注点

Separating concerns for NpgsqlConnection

我有一个架构,其中多个存储库 classes 实现针对数据库的 运行 不同查询和命令。我想将“连接到数据库”+“运行宁查询”和“向运行提供查询”+“处理结果”的关注点分开。我写了一个 Connection class 然后作为构造函数参数传递给存储库,如下所示:

public class PostgreSqlConnection
{
    private string connectionString;

    public PostgreSqlConnection(string connectionString)
    {
        this.connectionString = connectionString;
    }

    public async Task<NpgsqlDataReader> ExecuteQueryCommand(NpgsqlCommand command)
    {
        using NpgsqlConnection connection = new NpgsqlConnection(this.connectionString);
        await connection.OpenAsync();

        command.Connection = connection;
        command.Prepare();
        return await command.ExecuteReaderAsync();
    }

    public async Task ExecuteNonQueryCommand(NpgsqlCommand command)
    {
        using NpgsqlConnection connection = new NpgsqlConnection(this.connectionString);
        await connection.OpenAsync();
        
        command.Connection = connection;
        command.Prepare();
        await command.ExecuteNonQueryAsync();
    }
}

实例化看起来像这样:

PostgreSqlConnection connection = new PostgreSqlConnection("...connection string");
IRepositoryA repA = new PostgreSqlRepositoryA(connection);
IRepositoryB repB = new PostgreSqlRepositoryB(connection);

抛开代码重复,这是行不通的,因为在查询情况下,连接将在 ExecuteQueryCommand 方法结束时被处理掉,reader 将停止工作。

删除 using 语句可以解决此问题,但据我所知,这不是好的做法。编写一个我可以在存储库中调用的 Dispose / Disconnect 方法也是可行的,但处理连接不是存储库的工作。

我怎样才能将问题分开并妥善处理物品?

我认为这里可以提供帮助的是工作单元模式。基本上,使用 UnitOfWork class 你可以根据需要处理连接、存储库实例和事务(如果你将数据保存到数据库)。您还可以灵活地打开 connection/transaction,然后跨多个存储库执行多个命令,最后您可以提交或回滚事务,或者在纯读取的情况下,您只需使用 IDisposable 关闭连接模式。

UnitOfWork class 将处理连接部分,您的 BaseRepository(抽象 class)将具有您的通用执行方法,它将处理 query/command。具体的存储库(在你的例子中是 A 和 B)将从 BaseRepository 继承,只需准备 commands/queries 并从 BaseRepository 调用 Execute 方法。他们的职责基本上是准备 query/command 并处理 Execute 方法的结果。

请检查代码,因为我没有 Postgres 数据库,无法 100% 测试它。我希望这足以为您提供方向和方法背后的主要思想。

想法是这样的:

  1. 在您管理连接、事务(如果需要)和存储库实例的地方实施UnitOfWork

     public class UnitOfWork : IDisposable
         {
         private PostgreSqlRepositoryA _postgreSqlRepositoryA;
         private PostgreSqlRepositoryB _postgreSqlRepositoryB;
         private NpgsqlConnection _sqlConnection;
         private NpgsqlTransaction _sqlTransaction;
         private bool _disposed;
    
         public UnitOfWork(string connectionString, bool withTransaction)
         {
             _sqlConnection = new NpgsqlConnection(connectionString);
             _sqlConnection.Open();
    
             if (withTransaction)
                 _sqlTransaction = _sqlConnection.BeginTransaction();
         }
    
         public PostgreSqlRepositoryA PostgreSqlRepositoryA 
         { 
             get
             { 
                 if(_postgreSqlRepositoryA == null)
                 {
                     _postgreSqlRepositoryA = new PostgreSqlRepositoryA(_sqlConnection);
                 }
    
                 return _postgreSqlRepositoryA;
             } 
         }
    
         public PostgreSqlRepositoryB PostgreSqlRepositoryB
         {
             get
             {
                 if (_postgreSqlRepositoryB == null)
                 {
                     _postgreSqlRepositoryB = new PostgreSqlRepositoryB(_sqlConnection);
                 }
    
                 return _postgreSqlRepositoryB;
             }
         }
    
         public void Commit()
         {
             // hanlde using try-catch
             if(_sqlTransaction != null)
             {
                 _sqlTransaction.Commit();
             }
         }
    
         public void Rollback()
         {
             if (_sqlTransaction != null)
             {
                 _sqlTransaction.Rollback();
             }
         }
    
         public void Dispose()
         {
             Dispose(true);
             GC.SuppressFinalize(this);
         }
    
         protected virtual void Dispose(bool disposing)
         {
             if (!this._disposed)
             {
                 if (_sqlTransaction != null)
                 {
                     _sqlTransaction.Rollback(); // or throw an Exception for an opened transaction
                 }
    
                 if (_sqlConnection != null)
                 {
                     _sqlConnection.Close();
                     _sqlConnection.Dispose();
                 }
    
                 this._disposed = true;
             }
         }
     }
    
    1. 然后你需要的是一个 BaseRepository 它将保存你的“通用”方法 query/command 执行:

       public abstract class BaseRepository
       {
           protected NpgsqlConnection _sqlConnection;
      
           public BaseRepository(NpgsqlConnection sqlConnection)
           {
               this._sqlConnection = sqlConnection;
           }
      
           public async Task<NpgsqlDataReader> ExecuteQueryCommand(NpgsqlCommand command)
           {
               command.Connection = _sqlConnection;
               command.Prepare();
               return await command.ExecuteReaderAsync();
           }
      
           public async Task ExecuteNonQueryCommand(NpgsqlCommand command)
           {
               command.Connection = _sqlConnection;
               command.Prepare();
               await command.ExecuteNonQueryAsync();
           }
       }
      
  2. 您的具体存储库实现将如下所示(我没有费心处理 reader,您可以添加该部分):

    public class PostgreSqlRepositoryB : BaseRepository
     {
         public PostgreSqlRepositoryB(NpgsqlConnection sqlConnection)
             : base(sqlConnection)
         {}
    
         public async Task<int> GetCountB()
         {
             using (var sqlCommand = new NpgsqlCommand())
             {
                 sqlCommand.CommandText = "select count(1) from TableB";
    
                 var reader = await ExecuteQueryCommand(sqlCommand);
    
                 // TODO: handle reader
             }
         }
     }
    
  3. 最后你会像这样从你的服务或客户端方法中使用它(如果你只是从数据库读取然后设置 withTransactionfalse,你在这种情况下不需要交易):

     using(var uow = new UnitOfWork("place_your_conn_string", withTransaction: true))
     {
         var countB = await uow.PostgreSqlRepositoryB.GetCountB();
    
         uow.PostgreSqlRepositoryB.SaveSomethingToA("123456");
    
         uow.Commit();
     }