SqlDataReader的Connection属性为Null

Joh*_*van 5 c# idisposable using sqlconnection sqldatareader

我遇到了一个奇怪的问题,我可以通过调用存储过程返回结果,但代码追溯失败.

public IEnumerable<T> ExecuteStoredProcedure<T>(string storedProcedureName, IDataMapper<T> mapper, IDictionary<string, object> parameters)
{
    using (var connection = new SqlConnection(connectionString))
    {
        using (var cmd = new SqlCommand(storedProcedureName, connection))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            foreach (var key in parameters.Keys)
            {
                cmd.Parameters.AddWithValue(key, parameters[key]);
            }
            connection.Open();
            SqlDataReader reader = cmd.ExecuteReader();
            //return MapRecordsToDTOs(reader, mapper);

            //let's test:
            IEnumerable<T> result = MapRecordsToDTOs(reader, mapper);
            var x = (new List<T>(result)).Count;
            System.Diagnostics.Debug.WriteLine(x);
            return result;
        }
    }
}


private static IEnumerable<T> MapRecordsToDTOs<T>(SqlDataReader reader, IDataMapper<T> mapper)
{
    if (reader.HasRows)
    {
        while (reader.Read())
        {
            System.Diagnostics.Debug.WriteLine(reader["Id"]); //what's going on...
            yield return mapper.MapToDto((IDataRecord)reader);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

调用此代码表明变量x始终表示我希望从对存储过程的调用中看到的行数.

另外,我的调试输出显示了我期望看到的ID值.

但是,在返回这些结果后,我An exception of type 'System.InvalidOperationException' occurred in System.Data.dll but was not handled in user code从行中得到错误if (reader.HasRows)(即已经执行过的错误).我调用此请求的浏览器显示HTTP Error 502.3 - Bad Gateway.

错误的屏幕截图

HasRows行为的屏幕截图

我怀疑原因是系统分别计算调试的值IDX返回实际用户输出的方式.因此,它执行一个惰性操作来获取IEnumerable值,它必须返回它们; 只有这一点,using语句才会调用dispose方法,因此读者的连接是null(这是我reader在调试时检查变量的属性时所看到的).

有没有人在此之前看到这样的行为/它是一个错误; 还是我错过了一些明显的东西?


附加代码:

public interface IDataMapper<T>
{
    T MapToDto(IDataRecord record);
}

public class CurrencyMapper: IDataMapper<CurrencyDTO>
{
    const string FieldNameCode = "Code";
    const string FieldNameId = "Id";
    const string FieldNameName = "Name";
    const string FieldNameNum = "Num";
    const string FieldNameE = "E";
    const string FieldNameSymbol = "Symbol";

    public CurrencyMapper() { }

    public CurrencyDTO MapToDto(IDataRecord record)
    {
        var code = record[FieldNameCode] as string;
        var id = record[FieldNameId] as Guid?;
        var name = record[FieldNameName] as string;
        var num = record[FieldNameNum] as string;
        var e = record[FieldNameE] as int?;
        var symbol = record[FieldNameSymbol] as char?;
        return new CurrencyDTO(id, code, num, e, name, symbol);
    }
}

public class CurrencyRepository
{

    const string SPReadAll = "usp_CRUD_Currency_ReadAll";

    readonly SqlDatabase db;
    public CurrencyRepository()
    {
        db = new SqlDatabase(); //stick to SQL only for the moment for simplicity
    }
    public IEnumerable<CurrencyDTO> GetCurrencyCodes()
    {
        var mapper = new CurrencyMapper();
        return db.ExecuteStoredProcedure(SPReadAll, mapper);
    }
}

public class CurrencyDTO
{

    readonly Guid? id;
    readonly string code;
    readonly string num;
    readonly int? e;
    readonly string name;
    readonly char? symbol;

    public CurrencyDTO(Guid? id,string code,string num,int? e,string name, char? symbol)
    {
        this.id = id;
        this.code = code;
        this.num = num;
        this.e = e;
        this.name = name;
        this.symbol = symbol;
    }

    public Guid? Id { get { return id; } }
    public string Code { get { return code; } }
    public string Num { get { return num; } }
    public int? E { get { return e; } }
    public string Name { get { return name; } }
    public char? Symbol { get { return symbol; } }
}
Run Code Online (Sandbox Code Playgroud)

Joh*_*van 2

我暂时实施了解决此问题的解决方法。

这有效:

private static IEnumerable<T> MapRecordsToDTOs<T>(SqlDataReader reader, IDataMapper<T> mapper)
{
    var list = new List<T>(); //use a list to force eager evaluation
    if (reader.HasRows)
    {
        while (reader.Read())
        {
            list.Add(mapper.MapToDto((IDataRecord)reader));
        }
    }
    return list.ToArray();
}
Run Code Online (Sandbox Code Playgroud)

与原来相反:

private static IEnumerable<T> MapRecordsToDTOs<T>(SqlDataReader reader, IDataMapper<T> mapper)
{
    if (reader.HasRows)
    {
        while (reader.Read())
        {
            yield return mapper.MapToDto((IDataRecord)reader);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

不同之处在于我移动了受迭代器影响的代码,使其仅迭代列表中的结果;并且不依赖编译器明智地理解与IDisposable对象相关的要求。

据我了解,编译器应该能够为我处理这个问题(此处确认: https: //stackoverflow.com/a/13504789/361842),所以我怀疑这是编译器中的错误。

此处报告:https ://connect.microsoft.com/VisualStudio/feedback/details/3113138

此处的其他演示代码: https://gist.github.com/JohnLBevan/a910d886df577e442e2f5a9c2dd41293/