当reader使用ado.net包装器类关闭时,调用HasRows的尝试无效

本文关键字:调用 HasRows 无效 ado 使用 reader net 包装 | 更新日期: 2023-09-27 18:11:08

我正在尝试为ado.net编写包装器类,问题是我得到以下错误,我不知道如何解决它。

我读过很多关于这个的问题,但是没有一个使用包装器类,因此没有帮助。

谁能指出我在正确的方向或提供与相同的问题的链接,所以我可以阅读如何解决它。还是我完全走错了路?

public List<LoginDetails> Authenticate(string id)
        {
            const string spName = "dbo.MemberLogin";
            List<SqlParameter> parameters = new List<SqlParameter> { new SqlParameter("@Username", id) };
            var rdr = _iAdoCommandWrapper.ExecuteDataReaderAsync(DbConnectionAbstractClass.ConnectionString, CommandType.StoredProcedure, spName, parameters.ToArray());
            var data = new List<LoginDetails>();
            if (rdr.HasRows)
            {
                while (rdr.Read())
                {
                    data.Add(new LoginDetails
                    {
                        UserName = (string)rdr["MemberUsername"],
                        Password = (string)rdr["MemberPassword"],
                        MemberId = (string)rdr["MemberID"],
                        Role     = (string)rdr["MemberRole"]
                    });
                }
                //-------
                rdr.NextResult();
                while (rdr.Read())
                {
                    data.Add(new LoginDetails
                    {
                        RolesForMember = (string)(rdr["MembersRoles"])
                    });
                }
                //----------
            }
            return data.ToList();// rowsAffected.Result;
        }

包装器

public SqlDataReader ExecuteDataReaderAsync(string connectionString, CommandType cmdType, string spName, params SqlParameter[] cmdParameters)
        {
            //TODO make async once fixed problem
            using (var conn = new SqlConnection(connectionString))
            {
                using (var cmd = new SqlCommand(spName, conn))
                {
                    cmd.CommandType = cmdType;
                    cmd.Parameters.AddRange(cmdParameters);
                    conn.Open();
                    return cmd.ExecuteReader(CommandBehavior.Default);
                }
            }
        }

当reader使用ado.net包装器类关闭时,调用HasRows的尝试无效

DataReader实际上有一个针对这种情况的行为,CommandBehavior.CloseConnection

public SqlDataReader ExecuteDataReaderAsync(string connectionString, CommandType cmdType, string spName, params SqlParameter[] cmdParameters)
{
    // These two are intentionally are not in a using statement, but it is ok, closing 
    // the reader cleans up the resources.
    var conn = new SqlConnection(connectionString))
    var cmd = new SqlCommand(spName, conn))
    cmd.CommandType = cmdType;
    cmd.Parameters.AddRange(cmdParameters);
    conn.Open();
    return cmd.ExecuteReader(CommandBehavior.CloseConnection);
}

当您处置DataReader时,它将关闭SqlConnection,这是处置连接无论如何都会做的(加上引发Disposed事件)。

处理SqlCommand所做的唯一事情是释放对内部变量_cachedMetaData的引用,以允许它提前被GC,并在Component上调用基础Dispose(bool),它所做的唯一事情是引发Disposed事件。

只要您不使用连接或命令或连接的Disposed事件,此解决方案应该适用于您。


如果您确实必须"正确"处理这两个,因为您确实依赖于事件,请使用类似于我在处理CryptoStream时遇到的类似情况时所做的技巧。创建一个包装器,该包装器将在处置读取器时处置连接和命令。

sealed class CleaningDataReader : IDataReader
{
    private readonly IDataReader _reader;
    private readonly IDisposable[] _itemsToDispose;
    public CleaningDataReader(IDataReader reader, params IDisposable[] itemsToDispose)
    {
        if(reader == null)
            throw new ArgumentNullException("reader");
        _reader = reader;
        _itemsToDispose = itemsToDispose;
    }
    public void Dispose()
    {
        _reader.Dispose();
        if (_itemsToDispose != null)
        {
            foreach (var item in _itemsToDispose)
            {
                if(item != null)
                    item.Dispose();
            }
        }
    }
    public void Close()
    {
        _reader.Close();
    }
    public int Depth
    {
        get { return _reader.Depth; }
    }
    public int FieldCount
    {
        get { return _reader.FieldCount; }
    }
    public bool GetBoolean(int i)
    {
        return _reader.GetBoolean(i);
    }
    public byte GetByte(int i)
    {
        return _reader.GetByte(i);
    }
    public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
    {
        return _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
    }
    public char GetChar(int i)
    {
        return _reader.GetChar(i);
    }
    public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
    {
        return _reader.GetChars(i, fieldoffset, buffer, bufferoffset, length);
    }
    public IDataReader GetData(int i)
    {
        return _reader.GetData(i);
    }
    public string GetDataTypeName(int i)
    {
        return _reader.GetDataTypeName(i);
    }
    public DateTime GetDateTime(int i)
    {
        return _reader.GetDateTime(i);
    }
    public decimal GetDecimal(int i)
    {
        return _reader.GetDecimal(i);
    }
    public double GetDouble(int i)
    {
        return _reader.GetDouble(i);
    }
    public Type GetFieldType(int i)
    {
        return _reader.GetFieldType(i);
    }
    public float GetFloat(int i)
    {
        return _reader.GetFloat(i);
    }
    public Guid GetGuid(int i)
    {
        return _reader.GetGuid(i);
    }
    public short GetInt16(int i)
    {
        return _reader.GetInt16(i);
    }
    public int GetInt32(int i)
    {
        return _reader.GetInt32(i);
    }
    public long GetInt64(int i)
    {
        return _reader.GetInt64(i);
    }
    public string GetName(int i)
    {
        return _reader.GetName(i);
    }
    public int GetOrdinal(string name)
    {
        return _reader.GetOrdinal(name);
    }
    public DataTable GetSchemaTable()
    {
        return _reader.GetSchemaTable();
    }
    public string GetString(int i)
    {
        return _reader.GetString(i);
    }
    public object GetValue(int i)
    {
        return _reader.GetValue(i);
    }
    public int GetValues(object[] values)
    {
        return _reader.GetValues(values);
    }
    public bool IsClosed
    {
        get { return _reader.IsClosed; }
    }
    public bool IsDBNull(int i)
    {
        return _reader.IsDBNull(i);
    }
    public object this[int i]
    {
        get { return _reader[i]; }
    }
    public object this[string name]
    {
        get { return _reader[name]; }
    }
    public bool NextResult()
    {
        return _reader.NextResult();
    }
    public bool Read()
    {
        return _reader.Read();
    }
    public int RecordsAffected
    {
        get { return _reader.RecordsAffected; }
    }
}
使用

public IDataReader ExecuteDataReaderAsync(string connectionString, CommandType cmdType, string spName, params SqlParameter[] cmdParameters)
{
    // These two are intentionally are not in a using statement, but it is ok, closing 
    // the reader cleans up the resources.
    var conn = new SqlConnection(connectionString))
    var cmd = new SqlCommand(spName, conn))
    cmd.CommandType = cmdType;
    cmd.Parameters.AddRange(cmdParameters);
    conn.Open();
    var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
    return new CleaningDataReader(reader, cmd, conn);
}

我仍然会使用第一种方法,除非你真的需要触发dispose事件。

编辑:我同意Scott Chamberlain的解决方案。看起来不错。

您需要将SqlConnection/SqlCommand初始化移出ExecuteDataReaderAsync方法(使包装器变得毫无意义)或逐步遍历所有数据,存储值。

这是一种方法:

public List<Dictionary<string, object>> ExecuteDataReaderAsync(string connectionString, CommandType cmdType, string spName, params SqlParameter[] cmdParameters) {
        //TODO make async once fixed problem
        var records = new List<Dictionary<string, object>>();
        using (var conn = new SqlConnection(connectionString)) {
            conn.Open();
            using (var cmd = new SqlCommand(spName, conn)) {
                cmd.CommandType = cmdType;
                cmd.Parameters.AddRange(cmdParameters);
                using (var rdr = cmd.ExecuteReader(CommandBehavior.Default)) {
                    while (rdr.Read()) {
                       var record = new Dictionary<string, object>();
                       for (int fieldIndex = 0; fieldIndex < rdr.FieldCount; fieldIndex++) {
                          record.Add(rdr.GetName(fieldIndex), rdr.GetValue(fieldIndex));
                       }
                       records.Add(record);
                    }
                }
            }
        }
        return results;
    }

不幸的是,我现在不能测试这个。但它应该是接近正确的