是否有可能只获得错误的部分存储过程结果?

时间:2013-10-31 09:24:49

标签: c# sql sql-server stored-procedures sqldatareader

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

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组按键列结果(因此字典中没有重复的键问题)并返回int s(因此转换没有问题),如果发生任何其他错误,是否可以让它仅返回部分结果?

我很清楚那里有一个空的挡块 - 如果它不是空的,我可能不会问这个问题。我也知道这段代码可以返回一个空字典。我想知道异常是否会破坏SqlDataReader的读数,以便结果既不空也不完整。

我还被告知,使用SqlDataReader.ReadDataTable.Load(DataReader)切换到加载查询结果,然后在using个语句之外填充DataTable的结果将避免获得部分结果(即,如果它们在上面的代码中是可能的话)。会不会呢? DataTable.LoadSqlDataReader.Read的确有何不同?

2 个答案:

答案 0 :(得分:3)

是的,有可能。结果在查询执行时“创建”,并在创建时发送回客户端。读者将在阅读这些结果时将其添加到词典中。当引擎发生错误并发生错误时,如果发生错误,那么服务器端的执行将中止,错误信息将被发回,并且SqlClient会通过引发异常来做出反应。请阅读Understanding How SQL Server executes a query了解详情。

因此,在您的代码中,绝对可以以静默方式返回非空但不完整的结果。除了空的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。但我非常不喜欢这种方法,因为它需要保持状态变异动作与补偿动作同步并且随着时间的推移,如果它们分开,一些动作不再被补偿(撤消)。

勤奋的读者会注意到,这两种方法相当于数据库理论中实现原子性的两种方法:shadow-pagingwrite-ahead logging中的回滚。这并非巧合,因为您要实现的是原子性(要么所有状态发生变化,要么都不发生)。

答案 1 :(得分:1)

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