【问题标题】:Invalid attempt to call HasRows when reader is closed with ado.net wrapper class使用 ado.net 包装类关闭阅读器时调用 HasRows 的尝试无效
【发布时间】:2015-09-04 13:31:31
【问题描述】:

我正在尝试为 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);
                }
            }
        }

【问题讨论】:

  • rdr 依赖于 SqlCommand(它依赖于 SqlConnection)。但在退出ExecuteDataReaderAsync 方法之前,这两个对象都会被处理掉。
  • @Ulric 我该如何解决这个问题
  • 菲尔普森 见下文...再次。
  • @Ulric 抱歉耽搁了,我的网络中断了

标签: c# ado.net


【解决方案1】:

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 事件,此解决方案应该适合您。


如果您确实必须“正确”处置这两个,因为您确实依赖事件,请使用类似于我在处置 I ran in to a similar situation 时所做的技巧 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);
}

除非您真的需要触发 Disposed 事件,否则我仍然会采用第一种方法。

【讨论】:

    【解决方案2】:

    编辑:我会选择 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;
        }
    

    很遗憾,我目前无法对此进行测试。但它应该接近正确。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-26
      • 1970-01-01
      • 2011-03-28
      • 2012-09-23
      相关资源
      最近更新 更多