SQLDataReader无法抛出异常

3
下面的函数旨在确定查询是否会返回任何行。传入的SQL是查询语句。如果出现错误,函数应返回false。但是当SQL =

时,该函数会抛出一个异常,因为它缺少必要的参数。要解决这个问题,可以添加检查以确保SQL中有足够的参数。
SELECT TOP 1 [AU_ID] 
       FROM [dat].[model_80av2_v1_2941] 
       WHERE [AU_ID] IS NOT NULL AND convert(int, [AU_ID]) <> [AU_ID]

该函数错误地返回了true,因为没有检测到错误。然而,在SQL Management Studio中执行相同的查询会导致错误:
Msg 232, Level 16, State 3, Line 3 Arithmetic overflow error for type int, value = -1000000000000000000000000000000.000000.
显然,该函数应该返回false,因为一个值超出了int数据范围,但错误处理没有检测到错误。为什么呢?从其他帖子中,我理解SqlDataReader reader = cmd.ExecuteReader()应该会导致错误。
private bool GetIfExists(string SQL, out int ErrorNumber, out bool Exists)
{
    bool IsSuccess = true;
    ErrorNumber = 0;
    Exists = false;

    try
    {
        using (SqlConnection cnn = new SqlConnection(_connectionString))
        {
            try
            {
                cnn.Open();

                using (SqlCommand cmd = cnn.CreateCommand())
                {
                    cmd.CommandText = SQL;
                    cmd.CommandTimeout = _commandTimeout;
                    try
                    {
                        using (SqlDataReader reader = cmd.ExecuteReader())
                        {
                            Exists = reader.HasRows;
                        }
                    }
                    catch (SqlException ex)
                    {
                        if (ex.Errors.Count > 0) ErrorNumber = ex.Errors[0].Number;
                        throw;
                    }
                    catch
                    {
                        throw;
                    }
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                cnn.Close();
            }
        }
    }
    catch
    {
        IsSuccess = false;
    }
    return IsSuccess;
}

为什么要这么多的catch throw?另外,使用using语句的目的是让你不必自己调用诸如SqlConnection.Close()之类的东西,而你明确地在这样做。 - Camilo Terevinto
@mjwillis,SQL语句已经在问题中了。 - Peter
@CamiloTerevinto 连接池问题的纯粹恐惧。这可能已经被过度强调了。 - Peter
顺便提一下,你不需要 cnn.Close(); - using 语句会为你处理这个。 - mjwills
1
@Peter,我认为在Exists = reader.HasRows;之后添加reader.NextResult();会引发错误(http://www.dbdelta.com/the-curious-case-of-undetected-sql-exceptions/)。让我知道,我会用答案详细说明。 - Dan Guzman
@Dan Guzman 完全正确! - Peter
2个回答

8
从其他帖子中我了解到,SqlDataReader reader = cmd.ExecuteReader() 应该会导致错误。
总的来说这是正确的,除非在返回结果行给客户端的SQL批处理期间发生错误。异常可能直到所有结果被消耗后才被引发。这是因为SQL Server通过表格数据流协议(TDS)向客户端传送结果。结果集行先于SQL Server返回的异常。只有在消耗了所有之前的结果后,客户端API才能看到并引发错误。
以下是确保引发异常的一种方法来使用和丢弃剩余的结果。
using (SqlDataReader reader = cmd.ExecuteReader())
{
    Exists = reader.HasRows;
    do
    {
        while (reader.Read()) { };
    } while (reader.NextResult());
}

这篇文章包含了更多的情况,SQL异常可能不会如预期般被引发。需要补充的是,在使用相同查询时,你会看到由SQL Server Management Studio引发的错误,因为它将FireInfoMessageEventOnUserErrors SqlConnection属性设置为true,而不是依赖于引发SqlException,这在应用程序代码中并不常见(也不应该这样做)。


感谢您提供简明扼要的答案。 - Peter

0

SQL 管理工具默认将 ARITH ABORT 设置为打开状态,但 C# 不会。

SQL Management Studio settings

看一下 https://dba.stackexchange.com/questions/2500/make-sqlclient-default-to-arithabort-onHow to SET ARITHABORT ON for connections in Linq To SQL

此外,你下面的查询存在问题,因为 TOP 1 将根据使用的排序方式给出不同的结果。为了获得可重复的结果,你真的需要一个 ORDER BY 子句。如果没有它,查询优化器可以决定对数据按 Column A 进行排序,例如从 C# 查询,而对其他查询(例如从 SQL Management Studio)按 Column B 进行排序。

SELECT TOP 1 [AU_ID] 
       FROM [dat].[model_80av2_v1_2941] 
       WHERE [AU_ID] IS NOT NULL AND convert(int, [AU_ID]) <> [AU_ID]

@mjwillis 虽然可能是这样,但该字段或列实际上包含非整数值以及疯狂的-1E30值。然而,没有错误,当然read.HasRows必须返回false?好吧,确实会出现错误,只是没有传递,并且错误陷阱无法检测到任何异常情况。 - Peter
在这种情况下,IsSuccess(返回行上)的值是什么?是true还是false?Exists的值是多少? - mjwills
@mjwillis IsSuccess = true。这是主要的问题。没有检测到错误,因此暗示查询字段中的所有行都是整数,但实际上并不是。 - Peter
@mjwillis 这是雪上加霜,Exists = false。 - Peter
它肯定不会进入 catch (SqlException ex) 块,你加上了 ORDER BY 吗? - mjwills

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