【问题标题】:Return SqlDataReader with closed connection使用关闭的连接返回 SqlDataReader
【发布时间】:2022-04-26 20:19:49
【问题描述】:

我创建了一个 Sql 帮助类来处理我的大部分需求。

其中,我有一个执行 SQL 语句并返回 SqlDataReader 的函数,如下所示:

public static SqlDataReader ExecuteCommand(string cmdText, bool useParameters)
{
    using (var sqlConnection = new SqlConnection(_connectionString.ConnectionString))
    {
        sqlConnection.Open();
        using (var sqlCommand = new SqlCommand(cmdText, sqlConnection))
        {
            if (useParameters && SqlParameterCollection.Count > 0)
            {
                sqlCommand.Parameters.AddRange(SqlParameterCollection.ToArray());
            }

            using (var sqlDataReader = sqlCommand.ExecuteReader())
            {
                return sqlDataReader;
            }
        }
    }
}

显然,这个问题在于它返回一个需要打开连接的 sqlDataReader 并且连接已关闭。

我已经研究过返回一个 SqlDataAdapter,但是在阅读了以下线程 SqlDataAdapter vs SqlDataReader 之后,当您完全不知道它应该加载的数据量。

那么...有什么好的选择?

我唯一能想到的就是循环浏览 SqlDataReader 中的行并执行yield return IEnumerable<IDataRecord>

有没有更好的方法来实现这一点,还是差不多?

【问题讨论】:

    标签: c# ienumerable sqldatareader


    【解决方案1】:

    您可以使用CommandBehavior.CloseConnection,它将闭包卸载给调用者 - 但是这会使您的代码正确地依赖于调用者using 读者。

    就我个人而言,我会尽量减少这种类型的依赖 - 并将实现代码尽可能靠近 DB 代码。实际上,调用者需要原始阅读器的情况并不多——或者至少不应该有。我强烈建议使用“dapper”或 ORM 之类的工具,这样您就可以执行以下操作:

    return connection.Query<T>(cmdText, args).ToList();
    

    这样就不会给调用者留下太多乱七八糟的地方。

    【讨论】:

    • 谢谢你,正是我想要的。
    • 这个用例怎么样:我有一个类可以处理数据仓库 (DWH) 上的转换作业。每个作业的查询都需要在 DWH(一个 SQL Server)上执行,结果需要复制到一个或多个不同的数据库中。这些也可能是 SQL Server,但也可能是不同的数据库服务器。我想从我的 Sql 服务器层获得一个阅读器,但可能需要在 Postgress 或 MariaDB 数据库层中使用它。看起来我需要在某个地方打破脱钩,对吧?
    【解决方案2】:

    您可以为您的连接和阅读器创建一个包装器,它实现了System.IDisposable。包装器负责关闭连接:

    public class DbReaderWrapper : IDbReaderWrapper
        {
            public DbDataReader Reader { get; private set; }
            private readonly DbConnection _connection;
            private bool _disposed = false;
    
            public DbReaderWrapper(DbConnection connection, DbDataReader reader)
            {
                Reader = reader;
                _connection = connection;
            }
    
            ~DbReaderWrapper()
            {
                Dispose(false);
            }
    
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
    
            protected virtual void Dispose(bool disposing)
            {
                if(_disposed)
                {
                    return;
                }
    
                if(disposing)
                {
                    _connection.Dispose();
                }
    
                _disposed = true;
            }
        }
    

    你的ExecuteCommand() 函数看起来像这样:

    public static SqlDataReader ExecuteCommand(string cmdText, bool useParameters)
    {
        var sqlConnection = new SqlConnection(_connectionString.ConnectionString)
        sqlConnection.Open();
        var sqlCommand = new SqlCommand(cmdText, sqlConnection)
    
        if (useParameters && SqlParameterCollection.Count > 0)
        {
            sqlCommand.Parameters.AddRange(SqlParameterCollection.ToArray());
        }
    
        var sqlDataReader = sqlCommand.ExecuteReader()
        return new DbReaderWrapper(sqlConnection, sqlDataReader);
    }
    

    您可以使用using 语句调用此方法:

    using var readerWrapper = sqlHelper.ExecuteCommand(query, true) {
        // Do something with the reader
    }
    

    【讨论】: