1

假设我们有一个方法,其代码如下:

Dictionary<int, int> results = new Dictionary<int, int>();

try
{
    using (SqlConnection sqlConn = new SqlConnection("some connection string"))
    {
        SqlCommand sqlCmd = new SqlCommand("stored procedure's name here", sqlConn);
        sqlCmd.CommandType = CommandType.StoredProcedure;
        //sqlCmd.Parameters.Add lines here
        sqlConn.Open();

        using (SqlDataReader sqlDR = sqlCmd.ExecuteReader())
        {
            while (sqlDR.Read())
            {
                results.Add((int)sqlDR["keyColumnName"], (int)sqlDR["valueColumnName"]);
            }
        }
    }
}
catch { }

return results;

存储过程是一个带有子选择的select语句,两者都来自同一张表,返回多行,不超过几百,通常更少。假设 SP 按键列对结果进行分组(因此字典中没有重复键问题)并返回ints(因此转换没有问题),如果发生任何其他错误,是否可以让它只返回部分结果?

我很清楚那里是一个空的 catch 块 - 如果它不是空的,我可能不会问这个问题。我也知道这段代码可以返回一个空字典。我想知道异常是否会中断读取,SqlDataReader从而导致结果既不空也不完整。

我还被告知,在两个语句之外从SqlDataReader.Read一次加载查询结果DataTable.Load(DataReader)然后从 DataTable 填充结果using将避免获得部分结果(也就是说,如果它们在上面的代码中完全可能的话)。会吗?DataTable.Load真的有不同的工作方式吗SqlDataReader.Read

4

2 回答 2

3

对的,这是可能的。结果在查询执行时“创建”,并在创建时发送回客户端。读者将在这些结果出现时阅读它们并将它们添加到字典中。当引擎中发生错误时,如果发生,则中止服务器端的执行并将错误信息发送回,SqlClient 通过引发异常做出反应。阅读了解 SQL Server 如何执行查询以获取更多详细信息。

所以在你的代码中绝对有可能默默地返回一个不为空但也不完整的结果。除了空的 catch 块之外,这个问题只是编写非异常安全代码的一般反模式中的一个示例,从某种意义上说,在异常情况下,它会使应用程序处于部分更改的状态,并将只会在稍后执行时触发更多错误。有一本关于这个主题的优秀书籍,Exceptional C++,尽管原则适用于 C++。

通常的解决方法是改变一个临时状态,然后在不能引发异常的操作中将当前状态与所需状态交换。在您的情况下,这意味着读入字典,然后仅在最后,完全读取结果后分配给返回值:

Dictionary<int, int> results = new Dictionary<int, int>();

try
{
    Dictionary<int, int> temp = new Dictionary<int, int>();
    using (SqlConnection sqlConn = new SqlConnection("some connection string"))
    {
        using (SqlDataReader sqlDR = sqlCmd.ExecuteReader())
        {
            while (sqlDR.Read())
            {
                temp.Add((int)sqlDR["keyColumnName"], (int)sqlDR["valueColumnName"]);
            }
        }
    }
    results = temp;
}
catch { }

return results;

另一种方法是补偿catch块中的动作。在您的情况下,这意味着清除results. 但我非常不喜欢这种方法,因为它需要使状态突变动作与补偿动作保持同步,并且随着时间的推移,如果它们分开,某些动作将不再得到补偿(撤消)。

勤奋的读者会注意到,这两种方法的代码等效于数据库理论中实现原子性的两种方法:影子分页预写日志中的回滚。这并非巧合,因为您要实现的是原子性(所有状态更改都发生或不发生)。

于 2013-10-31T09:35:57.327 回答
1

数据读取器 API 是一个流式 API,所以是的:如果存在连接问题,它可能随时发生 - 包括在结果网格的中间。但是,在DataTable此处使用 a 绝对没有任何优势,因为在这种情况下,这将与您现有的代码完全相同 - 如果它不适合您的catch{}. 只有catch{}导致“结果既不为空也不完整”的问题:没有catch{},您将收到有关问题的通知(通过异常)。

于 2013-10-31T09:32:54.593 回答