使用 SqlConnection 对象的 InfoMessage 事件






2.75/5 (4投票s)
本文探讨了 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 或更高的警告和错误。
在我们的应用程序中使用此事件的主要优势非常有趣,让我来叙述一些可以帮助我们解决问题的场景:
- 假设我们在不使用事务的情况下,使用
SqlCommand
对象对单个SqlConnection
对象执行一系列Execute
操作(ExecuteNonQuery
或ExecuteReader
)。问题在于,当其中一个命令由于某种原因未能执行时,我们只能简单地将抛出的异常性质通知用户,并放弃其余的操作。 - 假设我们在事务中执行多个 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);
}
在上面的代码中有几点需要注意:
- 此示例不使用事务对象,它只是逐一执行两个插入操作。
- 由于
FireInfoMessageEventOnUserErrors
属性设置为true
,因此由于第一个 SQL 语句中的错误(在insert
语句中传递了无效的数据类型),第二个 SQL 语句仅被执行。结果是,product 行未被插入;productdetails 行被插入。 - 另一方面,如果
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);
}
在事务场景中,我们可能需要关注代码的以下行为:
- 当
FireInfoMessageEventOnUserErrors
属性设置为false
时,由于第一个 SQL 语句中的错误(在insert
语句中传递了无效的数据类型),会抛出异常,从而停止程序的后续执行。结果是,product 行和 productdetails 行均未被插入。 - 另一方面,当
FireInfoMessageEventOnUserErrors
属性设置为true
时,由于第一个 SQL 语句中的错误(在insert
语句中传递了无效的数据类型),第二个 SQL 语句被执行,覆盖了事务的默认行为。它会尝试回滚已提交的更改,这是不允许的,因此会抛出InvalidOperationException
。但就数据库更改而言,这与我们在非事务执行中看到的情况类似。如果我们删除InvalidOperationException
的catch
块,应用程序将挂起,需要手动停止。
结论
此程序已在最近发布的 Visual Studio 2008 版本中进行测试和运行。在 SqlConnection
中包含 FireInfoMessageEventOnUserErrors
属性对于在非事务模式下执行多个 SQL 命令非常有用。在事务模式下,它的行为异常,迫使开发人员包含额外的异常检查;我希望在下一个 VS 版本发布时,这种奇怪的行为能够得到改进,并做出新的改进。更重要的是,开发人员正在寻找一种更简洁、更好的方法来覆盖 SQL 事务并执行部分事务。
历史
- 2009年6月8日:初始帖子