65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 SqlConnection 对象的 InfoMessage 事件

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.75/5 (4投票s)

2009年6月9日

CPOL

4分钟阅读

viewsIcon

59114

本文探讨了 SqlConnection 对象的 InfoMessage 事件的用法。

引言

本文探讨了 SqlConnection 对象的 InfoMessage 事件的用法,以及它在实际应用中的作用。文章还分析了在使用事务时,该事件存在的一个小歧义。

ADO.NET 的 SqlConnecion 对象有一个 InfoMessage 事件,当 SQL Server 数据库返回任何信息性消息或警告时,该事件就会被触发。确切地说,它对严重性级别低于 10 的错误进行响应,而严重性级别为 11 或更高的错误则会导致抛出异常。此事件的参数是 SqlInfoMessageEventArgs 对象,其中包含 Error 属性。此 Error 对象是一个错误集合,包含错误编号和文本,同时还提供有关错误发生的数据库、存储过程和行号的信息。

InfoMessage 事件与 SqlConnection FireInfoMessageEventOnUserErrors 属性同步工作,该属性接受一个布尔值,即 true false。如果此属性设置为 true,则会处理 InfoMessage 事件,并且我们的应用程序将等待来自 SQL Server 数据库的严重性级别为 11 或更高的警告和错误。

在我们的应用程序中使用此事件的主要优势非常有趣,让我来叙述一些可以帮助我们解决问题的场景:

  1. 假设我们在不使用事务的情况下,使用 SqlCommand 对象对单个 SqlConnection 对象执行一系列 Execute 操作(ExecuteNonQuery ExecuteReader)。问题在于,当其中一个命令由于某种原因未能执行时,我们只能简单地将抛出的异常性质通知用户,并放弃其余的操作。
  2. 假设我们在事务中执行多个 SQL 命令;InfoMessage 事件仍然可以触发,并帮助我们覆盖事务行为。这是最不利的情况,没有人希望在他的/她的应用程序中实现。覆盖事务行为肯定会破坏事务的 ACID 属性。

让我们逐一分析这两种情况。

场景 1

下面的示例使用 sales 数据库中的两个表:product 和 productdetails。下面的代码不使用事务。

    SqlConnection conn = new SqlConnection
		(@"Data Source=.\SQLEXPRESS;AttachDbFilename=
		D:\Users\bala\Documents\sales.mdf;Integrated Security=True;
		Connect Timeout=30;User Instance=True");
    conn.InfoMessage += new SqlInfoMessageEventHandler(conn_InfoMessage);
    conn.FireInfoMessageEventOnUserErrors = true;
    conn.Open();

    try
    {
        //  SQL insert for product row 
        string myDataCmds = "insert into product (productid, prodname, desc) 
        	values ('abc', 'Driver Kit', 'Kit useful for driver functions')";
        SqlCommand comm = new SqlCommand(myDataCmds, conn);
        int rows = comm.ExecuteNonQuery();
        if (rows > 0)
            MessageBox.Show("A row inserted into product table");
        else
            MessageBox.Show("Product not inserted in the product table");
        myDataCmds = "insert into productdetail (productid, model, manufacturer) 
        	values (101, 'ModelA', 'Simpson Co.')";
        comm = new SqlCommand(myDataCmds, conn);
        rows = comm.ExecuteNonQuery();
        if (rows > 0)
            MessageBox.Show("A row inserted into productdetail table");
        else
            MessageBox.Show("Detail not inserted in the productdetail table");
    }
    catch (Exception e)
    {
        MessageBox.Show("Exception thrown: " + e.Message);
    }
            
    conn.Close();
}

static void conn_InfoMessage(object sender, SqlInfoMessageEventArgs e)
{
    MessageBox.Show("InfoMessage Handled Error Level-" + 
	e.Errors[0].Class.ToString() + ":" + e.Message);           
}

在上面的代码中有几点需要注意:

  1. 此示例不使用事务对象,它只是逐一执行两个插入操作。
  2. 由于 FireInfoMessageEventOnUserErrors 属性设置为 true,因此由于第一个 SQL 语句中的错误(在 insert 语句中传递了无效的数据类型),第二个 SQL 语句仅被执行。结果是,product 行未被插入;productdetails 行被插入。
  3. 另一方面,如果 FireInfoMessageEventOnUserErrors 属性设置为 false,由于第一个 SQL 语句中的错误(在 insert 语句中传递了无效的数据类型),会抛出异常,从而停止程序的后续执行。结果是,product 行和 productdetails 行均未被插入。

场景 2

为了在上面的示例中包含事务功能,我们可能需要像下面这样在代码中添加提交和回滚操作。正如预期的那样,程序结果也有变化。

    SqlConnection conn = new SqlConnection
	(@"Data Source=.\SQLEXPRESS;AttachDbFilename=D:\Users\bala\Documents\sales.mdf;
	Integrated Security=True;Connect Timeout=30;User Instance=True");
    conn.InfoMessage += new SqlInfoMessageEventHandler(conn_InfoMessage);
    conn.FireInfoMessageEventOnUserErrors = true;
    conn.Open();
    SqlTransaction tran = conn.BeginTransaction();

    try
    {
        //  SQL insert for product row 
        string myDataCmds = "insert into product (productid, prodname, desc) 
        	values ('abc', 'Driver Kit', 'Kit useful for driver functions')";
        SqlCommand comm = new SqlCommand(myDataCmds, conn);
        comm.Transaction = tran;
        int rows = comm.ExecuteNonQuery();
        if (rows > 0)
            MessageBox.Show("A row inserted into product table");
        else
            MessageBox.Show("Product not inserted in the product table");
        myDataCmds = "insert into productdetail (productid, model, manufacturer) 
        	values (101, 'ModelA', 'Simpson Co.')";
        comm = new SqlCommand(myDataCmds, conn);
        comm.Transaction = tran;
        rows = comm.ExecuteNonQuery();
        if (rows > 0)
            MessageBox.Show("A row inserted into productdetail table");
        else
            MessageBox.Show("Detail not inserted in the productdetail table");
        tran.Commit();
    }
    catch (InvalidOperationException ioe)
    {
        MessageBox.Show("Exception thrown but transaction performed partially! ");
    }
    catch (Exception e)
    {
        MessageBox.Show("Exception thrown: " + e.Message);
        tran.Rollback();
    }
            
    conn.Close();
}

static void conn_InfoMessage(object sender, SqlInfoMessageEventArgs e)
{
    MessageBox.Show("InfoMessage Handled Error Level-" + 
    	e.Errors[0].Class.ToString() + ":" + e.Message);
}

在事务场景中,我们可能需要关注代码的以下行为:

  1. FireInfoMessageEventOnUserErrors 属性设置为 false 时,由于第一个 SQL 语句中的错误(在 insert 语句中传递了无效的数据类型),会抛出异常,从而停止程序的后续执行。结果是,product 行和 productdetails 行均未被插入。
  2. 另一方面,当 FireInfoMessageEventOnUserErrors 属性设置为 true 时,由于第一个 SQL 语句中的错误(在 insert 语句中传递了无效的数据类型),第二个 SQL 语句被执行,覆盖了事务的默认行为。它会尝试回滚已提交的更改,这是不允许的,因此会抛出 InvalidOperationException 。但就数据库更改而言,这与我们在非事务执行中看到的情况类似。如果我们删除 InvalidOperationException catch 块,应用程序将挂起,需要手动停止。

结论

此程序已在最近发布的 Visual Studio 2008 版本中进行测试和运行。在 SqlConnection 中包含 FireInfoMessageEventOnUserErrors 属性对于在非事务模式下执行多个 SQL 命令非常有用。在事务模式下,它的行为异常,迫使开发人员包含额外的异常检查;我希望在下一个 VS 版本发布时,这种奇怪的行为能够得到改进,并做出新的改进。更重要的是,开发人员正在寻找一种更简洁、更好的方法来覆盖 SQL 事务并执行部分事务。

历史

  • 2009年6月8日:初始帖子
© . All rights reserved.