当使用ExecuteNonQuery()时,我如何获取出现的错误信息?

25

我以这种方式执行命令:

var Command = new SqlCommand(cmdText, Connection, tr);

Command.ExecuteNonQuery();

命令中有一个错误,但是.NET没有抛出任何错误消息。 我怎样才能知道命令没有正常执行,并且如何获取异常?


没有 NonQuery 方法 - 如果您使用实际名称,它会更清晰...编辑... - Marc Gravell
@Marc 可能是他自己的定制方法? - Shadow The Spring Wizard
1
@Shadow 那我们肯定无法调试它。 - Marc Gravell
@Marc 希望你是对的,你的回答听起来是正确的。 - Shadow The Spring Wizard
8个回答

19

仅当错误的严重程度为16或更高时,您才会在C#中获得异常。如果您使用PRINT,您将不会在.NET中收到异常。

如果您可以编辑触发错误代码,则会在C#中引发SqlException:

RAISERROR('Some error message', 16, 1)

你可以通过访问 SqlException.Errors 集合来获取每个单独的错误。

顺便提一下 - 如果你在 RAISERROR 后没有直接使用 RETURN,SQL Server 会继续运行命令。如果你不返回,可能会收到多个错误。


19

.NET确实会在严重性为16或以上时引发错误消息(因为它会抛出异常),此消息将包含在.Message中。如果您使用较低严重性的RAISERROR(或使用PRINT),则需要订阅连接上的InfoMessage事件


等等,所以SQL Server实际上会使用ExecuteNonQuery()消耗TDS(因此看到错误),但是ExecuteScalar()不会消耗它?我本来希望ExecuteNonQuery()根本不会消耗任何东西,尽管我实际上更喜欢它这样做。 - Nelson Rothermel
@NelsonRothermel 我会期望这两者都会产生错误,只要严重程度足够高。但是在使用ExecuteReader时存在一种边界情况:如果(例如)您有多个选择查询,并且在第3和第4个网格之间引发错误,但是您在第一个网格部分处理读取器:那么错误可能会被忽略。 - Marc Gravell
我知道ExecuteReader存在问题。然而,我的研究(https://dev59.com/z0jSa4cB1Zd3GeqPCA3t,https://dev59.com/G3E95IYBdhLWcg3wPbZ7#2400019,http://www.sqlservercentral.com/Forums/Topic1070081-392-1.aspx#bm1070131)表明,如果错误在第一个结果集之后,`ExecuteScalar`也不会捕获错误。我想确定最好的方法是尝试一下。 - Nelson Rothermel
另外,根据我的测试,你不需要多个结果集就能错过一个错误。一个简单的 select ...; raiserror(...) 需要你执行 DataReader.NextResult() 才能捕获到错误。 - Nelson Rothermel
@NelsonRothermel 啊,对了 - 在你的ExecuteScalar讨论中,你没有提到多个网格。我假设(也许不正确),如果你正在使用ExecuteScalar,那么你只执行一个select,并且它在查询的末尾(因为显然,如果你只做一件事,为什么会在此之后抛出异常呢?) - Marc Gravell

2

受M Hassan、Stefan Steiger和Mark Gravell在该线程中工作的启发,这里是一个最小的概念验证示例,说明了这里正在发生什么:

private static void DoSql()
{
    // Errors of severity level of 10 or less 
    // will NOT bubble up to .Net as an Exception to be caught in the usual way
    const string sql = @"RAISERROR('A test error message of low severity', 10, 1)";

    using (SqlConnection conn = new SqlConnection(myConnString))
    {
        conn.Open();

        // Hook up my listener to the connection message generator
        conn.InfoMessage += new SqlInfoMessageEventHandler(MySqlMessageHandler);

        using (SqlCommand cmd = new SqlCommand(sql, conn))
        {
            cmd.ExecuteNonQuery();
            // code happily carries on to this point
            // despite the sql Level 10 error that happened above
        }
    }
}


private static void MySqlMessageHandler(object sender, SqlInfoMessageEventArgs e)
{
    // This gets all the messages generated during the execution of the SQL, 
    // including low-severity error messages.
    foreach (SqlError err in e.Errors)
    {
        // TODO: Something smarter than this for handling the messages
        MessageBox.Show(err.Message);
    }
}

2

只有高严重性错误才会在ExecuteNonQuery中抛出。我观察到OdbcCommand.ExecuteNonQuery()方法还有另一种情况。SqlCommand.ExecuteNonQuery()也可能是如此。如果CommandText属性中包含的SQL语句是单个语句(例如:INSERT INTO table (col1,col2) VALUES (2,'ABC');),并且以上语句中存在外键或主键违规,ExecuteNonQuery将抛出异常。然而,如果您的CommandText是一个批处理,其中有多个由分号分隔的SQL语句(例如:多个INSERTS或UPDATES),如果其中一个失败,ExecuteNonQuery不会抛出异常。您需要显式检查方法返回的受影响记录数。仅在try{}Catch{}中放置代码是没有帮助的。


1

在使用Oracle ODP.Net的WCF服务中,我发现这对我很有效 -

            try
            {
                cmd.Connection = conn;
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (OracleException oex)
            {
                string errmsg = oex.Message;
                Logger.Instance.WriteLog(@"Some error --> " + errmsg);
                throw new Exception(errmsg);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                cleanup...
            }

1

当命令完成时,使用try catch结合这段代码,返回错误:

SqlCommand.EndExecuteNonQuery(result) 

这是我的完整课程代码:

Imports System.Data.SqlClient
Imports System.DirectoryServices.ActiveDirectory

Class clsExecuteAsync


    Public Event EnProceso(Identificador As Integer, Mensaje As String)
    Public Event Finalizado(IDentificador As Integer, Mensaje As String)
    Public Event CancelarProcesoEnEjecucion(Identificador As Integer, ByRef Cancel As Boolean)



    Dim Cancelar As Boolean

    Sub CancelarProceso()
        Cancelar = True
    End Sub


    Function test() As Boolean
        ' This is a simple example that demonstrates the usage of the
        ' BeginExecuteNonQuery functionality.
        ' The WAITFOR statement simply adds enough time to prove the
        ' asynchronous nature of the command.
        Dim commandText As String = "UPDATE Production.Product SET ReorderPoint = ReorderPoint + 1 " & "WHERE ReorderPoint Is Not Null;" & "WAITFOR DELAY '0:0:3';" & "UPDATE Production.Product SET ReorderPoint = ReorderPoint - 1 " & "WHERE ReorderPoint Is Not Null"
        Return (RunCommandAsynchronously(0, commandText, GetConnectionString()))
        Console.WriteLine("Press ENTER to continue.")
        Console.ReadLine()

    End Function

    Function ExecuteAsync(Identificador As Integer, Sql As String, Optional CadenaConexion As String = "") As String
        If CadenaConexion = "" Then
            CadenaConexion = clsIni.LeeIni("Provider")
        End If

        Return RunCommandAsynchronously(Identificador, Sql, CadenaConexion)

    End Function

    Function RunCommandAsynchronously(Identificador As Integer, commandText As String, connectionString As String) As String
        ' Given command text and connection string, asynchronously execute
        ' the specified command against the connection. For this example,
        ' the code displays an indicator as it is working, verifying the
        ' asynchronous behavior.
        Dim Resultado As String = ""
        Try

            Dim connection As SqlConnection
            Dim SqlCommand As SqlCommand

            connection = New SqlConnection(connectionString)
            Dim count As Integer = 0

            'testint to catch the error, but not run for me
            AddHandler connection.InfoMessage, AddressOf ErrorEnConexion


            SqlCommand = New SqlCommand(commandText, connection)
            connection.Open()
            Dim result As IAsyncResult = SqlCommand.BeginExecuteNonQuery()
            While Not result.IsCompleted
                Console.WriteLine("Waiting ({0})", count = count + 1)
                ' Wait for 1/10 second, so the counter
                ' does not consume all available resources
                ' on the main thread.
                System.Threading.Thread.Sleep(100)
                RaiseEvent EnProceso(Identificador, commandText)
                Application.DoEvents()

                If Cancelar Then
                    Cancelar = False

                    'cancelar 
                    Dim Cancel As Boolean = False
                    RaiseEvent CancelarProcesoEnEjecucion(Identificador, Cancel)

                    If Cancel = False Then
                        Resultado = "Cancelado"
                        GoTo SALIR
                    End If

                End If

            End While

            'Console.WriteLine("Command complete. Affected {0} rows.", Command.EndExecuteNonQuery(Result))

            '   MsgBox("El comando se ejecutó. " & SqlCommand.EndExecuteNonQuery(result), MsgBoxStyle.Information)

            'detect error: this code lunch and error: Cath with try cacth code
            SqlCommand.EndExecuteNonQuery(result)

            RaiseEvent Finalizado(Identificador, SqlCommand.EndExecuteNonQuery(result))

            Resultado = "OK"

        Catch ex As SqlException
            Console.WriteLine("Error ({0}): {1}", ex.Number, ex.Message)
            Resultado = ex.Message


        Catch ex As InvalidOperationException
            Console.WriteLine("Error: {0}", ex.Message)
            Resultado = ex.Message
        Catch ex As Exception
            ' You might want to pass these errors
            ' back out to the caller.
            Console.WriteLine("Error: {0}", ex.Message)
            Resultado = ex.Message
        End Try


SALIR:
        Return Resultado

    End Function

    Private Sub ErrorEnConexion(sender As Object, e As SqlInfoMessageEventArgs)
        MsgBox(e.Message)

    End Sub


    Private Function GetConnectionString() As String
        ' To avoid storing the connection string in your code,
        ' you can retrieve it from a configuration file.
        ' If you have not included "Asynchronous Processing=true" in the
        ' connection string, the command is not able
        ' to execute asynchronously.
        Return "Data Source=(local);Integrated Security=SSPI;" & "Initial Catalog=AdventureWorks; Asynchronous Processing=true"

    End Function


End Class

0

请尝试以下方法。

附注:仅仅因为您使用了事务,并不意味着您可以忽略异常处理和回滚。

 public static void MessageEventHandler( object sender, SqlInfoMessageEventArgs e ) {
         foreach( SqlError error in e.Errors ) {
            Console.WriteLine("problem with sql: "+error);
            throw new Exception("problem with sql: "+error);
         }
      }
      public static int executeSQLUpdate(string database, string command) {
         SqlConnection connection = null;
         SqlCommand sqlcommand = null;
         int rows = -1;
         try {
            connection = getConnection(database);
            connection.InfoMessage += new SqlInfoMessageEventHandler( MessageEventHandler );
            sqlcommand = connection.CreateCommand();
            sqlcommand.CommandText = command;
            connection.Open();
            rows = sqlcommand.ExecuteNonQuery();
          } catch(Exception e) {
            Console.Write("executeSQLUpdate: problem with command:"+command+"e="+e);
            Console.Out.Flush();
            throw new Exception("executeSQLUpdate: problem with command:"+command,e);
         } finally {
            if(connection != null) { connection.Close(); }
         } 
         return rows;
      }

这是正确的事务处理方式:

//public static void ExecuteInTransaction(Subtext.Scripting.SqlScriptRunner srScriptRunner)
        public override void ExecuteInTransaction(string strSQL)
        {

            System.Data.Odbc.OdbcTransaction trnTransaction = null;

            try
            {


                System.Threading.Monitor.Enter(m_SqlConnection);
                if (isDataBaseConnectionOpen() == false)
                    OpenSQLConnection();

                trnTransaction = m_SqlConnection.BeginTransaction();

                try
                {
                    /*
                    foreach (Subtext.Scripting.Script scThisScript in srScriptRunner.ScriptCollection)
                    {
                        System.Data.Odbc.OdbcCommand cmd = new System.Data.Odbc.OdbcCommand(scThisScript.ScriptText, m_sqlConnection, trnTransaction);
                        cmd.ExecuteNonQuery();
                    }
                    */

                    // pfff, mono C# compiler problem...
                    // System.Data.Odbc.OdbcCommand cmd = new System.Data.Odbc.OdbcCommand(strSQL, m_SqlConnection, trnTransaction);
                    System.Data.Odbc.OdbcCommand cmd = this.m_SqlConnection.CreateCommand();
                    cmd.CommandText = strSQL;

                    cmd.ExecuteNonQuery();

                    trnTransaction.Commit();
                } // End Try
                catch (System.Data.Odbc.OdbcException exSQLerror)
                {
                    Log(strSQL);
                    Log(exSQLerror.Message);
                    Log(exSQLerror.StackTrace);
                    trnTransaction.Rollback();
                } // End Catch
            } // End Try
            catch (Exception ex)
            {
                Log(strSQL);
                Log(ex.Message);
                Log(ex.StackTrace);
            } // End Catch
            finally
            {
                strSQL = null;
                if(m_SqlConnection.State != System.Data.ConnectionState.Closed)
                    m_SqlConnection.Close();
                System.Threading.Monitor.Exit(m_SqlConnection);
            } // End Finally


        } // End Sub ExecuteInTransaction

1
我非常不同意你的错误处理选择;在这里使用usinglock会更加合适;此外,你没有重新抛出异常,而且在每个级别记录日志是过度的(这就是.StackTrace的作用)。这样做会导致静默失败,我认为这非常糟糕。还有,为什么要锁定连接?如果两个线程甚至可以看到相同的连接,那么你的做法是错误的,我认为。如果有什么问题,那对我来说看起来像是如何处理错误的完美示例。 - Marc Gravell
1
@Quandary - 对于数据访问层(DAL),这就是连接池存在的原因。通过锁定,你实际上将整个应用程序限制为一个有用的工作线程 - 在我看来,这真的非常糟糕。在一个繁忙的网站上(比如说,像这个),我们有大量并行数据访问。如果我只使用一个连接,Jeff会开枪打死我... - Marc Gravell
@Marc Gravell:你永远不能记录足够的日志。如果你正在使用asp.net并想在每个页面上实例化DAL类,那将是可怕的。更不用说通过访问web.config来读取连接字符串信息每次都会带来速度损失。正如你在注释部分看到的,这些方法都是静态的。由于诸如Flash和AJAX之类的技术是异步的,所以你需要锁定...此外,VB.NET没有锁,因此需要监视器,并且这是一个自动代码转换。但是,锁定会更好。;) PS:重新抛出异常已被删除,以免向用户呈现YSODs。 - Stefan Steiger
@Quandary a: 可读性非常重要,b: 它比这更复杂 - 有更好(更健壮)的重载,编译器会自动使用;例如,对Enter()的调用可能失败 - 这意味着您没有获取锁 - 然后尝试Exit()(即使您没有获取锁)。但更重要的是,为什么要写额外的代码呢? - Marc Gravell
1
@Quandry - conn.Open(); 就在那里了。实际上,我们使用一个 EnsureOpen() 扩展方法来内部处理,它使用 IDisposable 并在 dispose 时打开(如果没有打开)并关闭它(如果它打开了)。这是一个很方便的包装实用程序方法。虽然我们重复使用连接的程度与我试图说明的主要观点无关,但我并没有专注于这一点。 - Marc Gravell
显示剩余6条评论

0

你可以使用 try/catch 捕获 SqlException 异常

 try
  {
       //.......
    Command.ExecuteNonQuery();      
   }
    catch (SqlException ex)
     {   
       log (SqlExceptionMessage(ex).ToString());
     }

以下方法可以捕获SqlException的详细信息,这些信息可以记录或显示给用户

  public StringBuilder SqlExceptionMessage(SqlException ex)
    {
        StringBuilder sqlErrorMessages = new StringBuilder("Sql Exception:\n");

        foreach (SqlError error in ex.Errors)
        {
            sqlErrorMessages.AppendFormat("Mesage: {0}\n", error.Message)
                .AppendFormat("Severity level: {0}\n", error.Class)
                .AppendFormat("State: {0}\n", error.State)
                .AppendFormat("Number: {0}\n", error.Number)
                .AppendFormat("Procedure: {0}\n", error.Procedure)
                .AppendFormat("Source: {0}\n", error.Source)
                .AppendFormat("LineNumber: {0}\n", error.LineNumber)
                .AppendFormat("Server: {0}\n", error.Server)
                .AppendLine(new string('-',error.Message.Length+7));

        }
        return sqlErrorMessages;
    }

生成的消息看起来像这样:
 Sql Exception:
 Mesage: Error converting data type nvarchar to datetime.
 Severity level: 16
 State: 5
 Number: 8114
 Procedure: Sales by Year
 Source: .Net SqlClient Data Provider
 LineNumber: 0
 Server: myserver
 -------------------------------------------------------

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