达内培训:.NET 数据访问架构(D)
从存储过程中生成错误
T-SQL提供了一个RAISERROR(注意拼写)函数。你可用此函数生成定置错畚容尬檬误,并将错误返回客户。对于ADO.NET客户,SQL Server。net数据供应器对这些数据错误进行解释,并把它们转化为SqlError对象。
使用RAISERROR函数是简单地方法是将消息文本作为第一个参数包括进来,然后指定严重及状态参数,如下面的代码片段所示: RAISERROR( 'Unknown Product ID: %s', 16, 1, @ProductID )
在这个例子中,替代参数用于将当前产品ID作为错误消息文本的一部分返回,参数2是消息的严重性,参数3是消息状态。
更多信息为了避免对消息文本进行硬编码,你可以利用sp_addmessage系统存储过程或SQL Server 企业管理器将你自己的消息增加到sysmessages表中。然后你就可以使用传递到RAISERROR函数的ID引用消息了。你所定义的消息Ids必须大于50000,如下代码片段所示:
RAISERROR( 50001, 16, 1, ProductID )
关于RAISERROR函数的完整细节,请在SQL Server的在线书目中查询RAISERROR。 正确使用严重性等级
仔细选择错误严重性等级,并要清楚每个级别造成的冲击。错误严重性等级的范围是0-25,并且它用于指出SQL Server 2000所遇到的问题的类型。在客户端代码中,通过在SqlException类的Errors集合中检查SqlError对象的 Class属性,你可以获得错误的严重性。表1 指出了不同严重性等级的意义及所造成的冲击。
表1.错误严重性等级--冲击及意义
严重性等级链接已关闭生成SqlException对象意义10及其以下 NoNo通知型消息,并不表示犯错误状态。11-16NoYes可由用户修改的错误,例如,使用修改后的输入数据重试操作。17-19NoYes资源或系统错误。20-25YesYes致命的系统错误(包括硬件错误)。客户链接被终止。
控制自动化事务
SQL Server .NET数据供应器对它所遇到的任何严重性大于10的错误都抛出SqlException对象。当作为自动化(COM+)事务一部分的组件检测到SqlException对象后,该组件必须确保它能取消事务。这也许是,也许不是自动化过程,并要依赖该方法是否已经对AutoComplete属性作出了标记。
关于在自动化事务上下文中处理对象的更多信息,见本文中的确定事务结果一节。
得到通知型消息
10及其以下严重性等级用于表示通知型消息,并且不会引发SqlException对象的抛出。
要获得通知型消息:创建事件处理程序,并提交给SqlConnection对象所暴露的InfoMessage事件。下面的代码片段显示了事件代理。public delegate void SqlInfoMessageEventHandler( object sender,SqlInfoMessageEventArgs e );
通过传递到你的事件处理处理程序中的SqlInfoMessageEventArgs对象,可以得到消息数据。此对象暴露了Errors属性,该属性包含一组SqlError对象--每个通知消息一个SqlError对象。下面的代码片段演示了如何注册用于记录通知型消息的事件处理程序。
public string GetProductName( int ProductID ){SqlConnection conn = new SqlConnection("server=(local);Integrated Security=SSPI;database=northwind");try{// ReGISter a message event handlerconn.InfoMessage += new SqlInfoMessageEventHandler( MessageEventHandler );conn.Open();// Setup command object and execute it. . .}catch (SqlException sqlex){// log and handle exception. . .}finally{conn.Close();}}// message event handlervoid MessageEventHandler( object sender, SqlInfoMessageEventArgs e ){foreach( SqlError sqle in e.Errors ){// Log SqlError properties. . .}}
性能
本节介绍了一些常见的数据访问方案,对每种方案,以ADO.NET 数据访问代码的形式描述了最优性能和扩展性解决方案。在合适的地方,还对性能,功能及开发最作出了比较。本节考虑了下面的功能方案。获取多行. 获取一个结果集,并在得到的行中重复。
获取一行. 获取具有指定关键字的一行。
获取一项. 从指定的行中得到一项。
确定某项数据的存在性. 检查具有特定关键字的一行是否存在。这是单项查找方案的一种变体,这里返回一个简单的布尔值就足够了。 获取多行
在这个方案中,你要获取一组表格化数据,并在得到的行中重复执行某个操作。例如你得到了一组数据,并以非链接的方式处理,然后(可能通过Web服务)将它作为XML文档传递给客户应用程序。可选的,你也可以以HTML表的形式将这些数据显示出来。
为了帮助确定最合适的数据访问方法,考虑你是否需要(非链接)DataSet 对象的附加灵活性,还是只需要SqlDataReader对象提供的原有性能,这些性能非常适合于B2C Web应用程序的数据表示。图4显示了这两种基本场景。
注意用于填充DataSet的SqlDataAdapter利用SqlDataReader方法数据。
方法比较
当从数据源中获取多行时,你可以使用下面的方法:使用SqlDataAdapter对象生成DataSet 或 DataTabl对象。
利用SqlDataReader对象提供只读的只向前的数据流。
利用XMLReader对象提供只读的只向前的XML数据流。 SqlDataReader 与 DataSet/DataTable间的选择本质上是性能与功能间的选择。SqlDataReader 提供了最优性能,而DataSet提供了额外的功能与灵活性。数据绑定
所有这三个对象都可以作为数据绑定控件的数据源。而DataSet 和 DataTable 可作为更广范围控件的数据源。这是因为DataSet 和 DataTable 实现了(生成Ilist接口)IlistSource接口,而SqlDataReader 实现了Ienumerable接口。许多能进行数据绑定的WinForm控件需要实现了Ilist接口的数据源。
这种不同是因为为每种对象类型设计的场景类型不同。DataSet (它包含 DataTable)是一个丰富的、非链接结构,它适合于Web和桌面(WinForm)应用程序。另一方面,数据阅读器已经为Web应用程序进行了优化,这种应用程序需要优化的、只能向前的数据访问。
检查将要绑定到的特定控件类型的数据源需求。
在应用程序层间传递数据
DataSet提供了可作为XML被任意操纵数据的关系图,并允许数据的非链接缓存拷贝在应用程序层与组件间传递。然而,SqlDataReader提供了更优化的性能,因为它避免了与创建DataSet相关的性能及内存开销。记住,DataSet对象的创建将导致多个子对象--包括DataTable, DataRow 和DataColumn--及作为这些子对象容器的集合对象的创建。
使用DataSet
使用SqlDataAdapter填充的DataSet对象,当:你需要非链接的驻留内存的缓存数据,以便你能将它传递到其它组件或应用程序中的其它层。
你需要内存中的数据关系图以执行XML或非XML操作。
你正在使用的数据来自多个数据源,如多个数据库、表或文件。
你希望更新获得的一些或所有行,并希望利用SqlDataAdapter的批更新功能。
你要对控件绑定数据,而此控件需要支持IList接口的数据源。 更多信息
如果使用SqlDataAdapter生成DataSet 或 DataTable,需注意:不必明确打开或关闭数据库链接。SqlDataAdapter Fill方法打开数据库链接,并在此方法返回前关闭该链接。如果链接原来已经打开,那么此方法仍使链接处于打开状态。
如果出于其它目的需要链接,那么考虑在调用Fill方法前打开链接。这样你就可以避免不必要的打开/关闭操作,提高性能。
尽管能重复使用同一SqlCommand对象多执行同样的命令,但不要重复使用此对象执行不同的命令。 使用SqlDataReader
些劣情况,可以使用通过调用 SqlCommand 对象的ExecuteReader方法得到的SqlDataReader对象:正在处理大量数据时--太多了而不能在单个缓冲区内维护。
希望减少应用程序在内存中的印迹。
希望避免与DataSet对象创建相关的开销。
希望对某控件执行数据绑定操作,而此控件支持实现了IEnumerable接口的数据源。
希望流水线化数据访问,并对其优化。
正在读取包含二进制大对象(BLOB)列的行。你可以使用SqlDataReader对象以可管理的大块为单位从数据库中将BLOB数据拉出来,而不是一次性地将所有数据提取出来。关于处理BLOB数据的更多细节,见本文处理BLOBs一节。 更多信息
如果使用SqlDataReader对象,请注意:在数据阅读器活动期间,底层的数据库链接保持打开,并不能用于其它任何目的。尽可能早地对SqlDataReader对象调用Close方法。
每个链接只能有一个数据阅读器。
通过向ExecuteReader方法传递CommandBehavior.CloseConnection枚举值,可以在使用完数据阅读器后,明确地关闭链接;或者,将链接生命周期绑定到SqlDataReader对象。这预示着当SqlDataReader对象关闭时,链接也将关闭。
在利用阅读器访问数据时,如果你知道列的底层数据类型,那么就应使用类型化存取器方法(如GetInt32 和 GetString),这是因为在读取列数据时,这些方法减少了读取列数据所需的类型转换量。
为避免将不必要的数据从服务器发送到客户端,如果你要关闭阅读器并抛弃所有保留的结果,那么在对阅读器调用Close方法前调用命令对象的Cancel方法。Cancel方法确保了服务器的结果被抛弃,而不会被发送到客户端。相反,对数据阅读器调用Close方法会使阅读器不必要地提取出保留的结果,以清空数据流。
如果要得到从存储过程返回的输出值或返回值,并且你在利用SqlCommand对象的ExecuteReader方法,那么在得到输出或返回值前,必须对阅读器调用Close方法。
使用XmlReader
下列情况下,使用通过调用SqlCommand对象的ExecuteXmlReader方法得到的XmlReader对象:希望将得到的数据作为XML 处理,但不希望引发因创建DataSet对象而造成的额外性能开销,并且不需要数据的非链接缓存。
希望利用SQL Server FOR XML 语法的功能,这种语法允许以灵活的方式从数据库中得到XML片段(即,不带根元素的XML文档)。例如,这种方法使你能够精确指定元素名,是使用元素还是使用以属性为核心的图解,图解是否随XML数据一起被返回,等等。
更多信息
如果使用XmlReader,请注意:在从XmlReader对象中读取数据时,链接必须保持打开。SqlCommand对象的 ExecuteXmlReader方法目前不支持CommandBehavior.CloseConnection枚举值,因此在使用完阅读器后必须明确关闭链接。