C# 当读取器已关闭时,调用Read的尝试无效。

23

在进行C#语言的3层项目时,我遇到了“尝试在关闭阅读器时调用Read无效”的错误。 我的目标是通过将两个表连接在一起检索地址数据列,并在下拉列表中显示。 以下是我的数据访问层:

public List<Distribution> getDistributionAll()
{
    List<Distribution> distributionAll = new List<Distribution>();
    string address;
    SqlDataReader dr = FoodBankDB.executeReader("SELECT b.addressLineOne FROM dbo.Beneficiaries b INNER JOIN dbo.Distributions d ON d.beneficiary = b.id");

    while (dr.Read())
    {
        address = dr["addressLineOne"].ToString();
        distributionAll.Add(new Distribution(address));
    }

    return distributionAll;
}

这是我的FoodBankDB类:

public class FoodBankDB
{
    public static string connectionString = Properties.Settings.Default.connectionString;
    public static SqlDataReader executeReader(string query)
    {
        SqlDataReader result = null;
        System.Diagnostics.Debug.WriteLine("FoodBankDB executeReader: " + query);
        SqlConnection connection = new SqlConnection(connectionString);
        SqlCommand command = new SqlCommand(query, connection);
        connection.Open();
        result = command.ExecuteReader();
        connection.Close();
        return result;
    }
}

我将它们分成两个类,以便每当我的连接字符串更改时,我可以通过更改FoodBankDB类来轻松修改整个项目。

这是我的业务逻辑层:

public List<Distribution> getAllScheduledDistribution()
{
    List<Distribution> allDistribution = new List<Distribution>();
    Distribution distributionDAL = new Distribution();
    allDistribution = distributionDAL.getDistributionAll();
    return allDistribution;
}

最后但同样重要的是,我的演示层:

List<Distribution> scheduledList = new List<Distribution>();
scheduledList = packBLL.getAllScheduledDistribution();
ddlScheduleList.DataSource = scheduledList;
ddlScheduleList.DataTextField = "address";
ddlScheduleList.DataValueField = "address";
ddlScheduleList.DataBind();

如果我没有把数据访问层和连接字符串类分开,它就能正常工作。有人知道如何解决这个错误吗?

提前致谢。

更新部分

public static string GetConnectionString()
{
    return connectionString;
}
2个回答

35

无法正常工作,因为您在返回读取器之前关闭了连接。只有在连接打开时才能使用读取器:

result = command.ExecuteReader();
connection.Close();

return result; // here the reader is not valid

一般来说,你不应该将读取器返回给业务层。读取器只应该在数据访问层中使用,并且应该被使用后立即关闭连接。

相反,你应该返回一个可以在连接关闭后工作的对象,例如DataSetDataTable,或者是DTO的集合。例如:

public List<Distribution> getDistributionAll()
{
    List<Distribution> distributionAll = new List<Distribution>();

    using (var connection = new SqlConnection(FoodBankDB.GetConnectionString())) // get your connection string from the other class here
    {
        SqlCommand command = new SqlCommand("SELECT b.addressLineOne FROM dbo.Beneficiaries b INNER JOIN dbo.Distributions d ON d.beneficiary = b.id", connection);
        connection.Open();
        using (var dr = command.ExecuteReader())
        {
            while (dr.Read())
            {
                string address = dr["addressLineOne"].ToString();

                distributionAll.Add(new Distribution(address));
            }
        }
    }

    return distributionAll;
}

1
您介意展示一些解决这个问题的例子吗?因为我有点困惑。 - user2531590
1
请看上面的例子。希望没有小错误,因为我现在无法运行代码。 - Szymon
我应该将GetConnectionString()作为更新部分编码吗?还是我做错了? - user2531590
是的,根据上面的代码,您需要编写GetConnectionString()。或者,您可以直接使用FoodBankDB.connectionString:using (var connection = new SqlConnection(FoodBankDB.connectionString)) .... - Bora
是的,没错。我按照你说的要求把它分开做了。 - Szymon

9

上面的示例是一个很好的例子...但是您也可以通过下面的代码实现,该代码在调用datareader.close()方法时会自动关闭连接实例...

reader = Sqlcmd.ExecuteReader(CommandBehavior.CloseConnection); 

抱歉在一个非常老的帖子上发帖,但我想知道是否绝对必要调用 datareader.close() 来关闭连接?在使用 CommandBehavior.CloseConnection 初始化实例后,它不会自动处理吗? - hiFI
@hiFI 不会的。因为Datareader需要在打开连接时工作,而且可能有多个结果集,所以您需要显式地关闭Datareader……上述参数的好处是它将调用相关的连接也来关闭。 - Moumit
不管怎样,你的解决方案真是救星!这比将连接作为“out”参数传递要容易得多。谢谢你。 - hiFI

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接