C# 分离NpgsqlConnection的关注点

C# 分离NpgsqlConnection的关注点,c#,npgsql,C#,Npgsql,我有一个体系结构,其中多个存储库类实现了针对数据库运行的不同查询和命令。我想将“连接到数据库”+“运行查询”和“提供查询以运行”+“处理结果”这两个问题分开。我编写了一个连接类,然后将其作为构造函数参数传递给存储库,如下所示: public class PostgreSqlConnection { private string connectionString; public PostgreSqlConnection(string connectionString) {

我有一个体系结构,其中多个存储库类实现了针对数据库运行的不同查询和命令。我想将“连接到数据库”+“运行查询”和“提供查询以运行”+“处理结果”这两个问题分开。我编写了一个连接类,然后将其作为构造函数参数传递给存储库,如下所示:

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();
    }
}
撇开代码重复不谈,这不起作用,因为在查询案例中,连接将在ExecuteQueryCommand方法的末尾被处理,读取器将停止工作

使用语句删除
可以解决这个问题,但从我收集的信息来看,这不是一个好的做法。编写一个
Dispose
/
Disconnect
方法,我可以在存储库中调用该方法,这也会起作用,但处理连接不是存储库的工作


我如何将关注点分开并正确地处理项目?

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

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

请检查代码,因为我没有Postgres数据库,我不能100%测试它。我希望这足以给你一个方向和方法背后的主要思想

以下是想法:

  • 在管理连接、事务(如果需要)和存储库实例的位置实施
    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;
             }
         }
     }
    
  • 然后,您需要的是一个
    BaseRepository
    ,它将保存查询/命令执行的“通用”方法:

     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();
         }
     }
    

  • 你的回答帮助我解决了这个问题,但我的回答与实际模式有点不同。这只是一个细节,但我认为您的代码不会在
    NpgsqlCommand
    上调用
    Dispose
    (如果您处理连接,我甚至不确定是否需要Dispose?)。我应该发布我的解决方案吗?我不经常使用stackoverflow我上面给你的代码只是给你方向,你可以根据需要编辑或改进它。但是,我已经更新了
    UnitOfWork
    代码,以使一次性模式更好,而且
    NpsqlCommand
    现在正在使用
    using
    ,因此当回购方法返回时将对其进行处理。这样,它被明确地处理,你是安全的。
     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();
         }
     }
    
    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
             }
         }
     }
    
     using(var uow = new UnitOfWork("place_your_conn_string", withTransaction: true))
     {
         var countB = await uow.PostgreSqlRepositoryB.GetCountB();
    
         uow.PostgreSqlRepositoryB.SaveSomethingToA("123456");
    
         uow.Commit();
     }