ADO.NET最佳实践

 概述:

    本文在微软站点资源的基础上加工整理而成,意在介绍在你的ADO.NET应用程序中执行和完成性能优化、稳定性和功能性方面提供最佳的解决方案;同时也包含在ADO.NET中运用已有的数据对象进行开发的最佳实践和帮助你怎样设计ADO.NET应用程序提供建议。

    本文包含以下内容:

    1..NET框架中的data providers;

    2.对照DataSet和DataReader,分别介绍他们的最佳用途;

    3.如何使用DataSet、Commands和Connections;

    4.结合XML;

    5.如果你是ADO程序员,也不妨看看ADO.NET与ADO的区别和联系;

    6.结合一些FAQ,更深一步讨论ADO.NET观点和使用技巧。

    介绍:

    A..NET框架中的data providers

        Data providers在应用程序和数据库之间扮演一个桥梁的角色,它使得你可以从一个数据库返回查询结果、执行命令以及对数据集的更新等。

    B.几种data provider的介绍

        下面表格中数据表明各种data provider以及最佳适用数据库对象

提供者

描述

SQL Server.NET Data Provider

.NET框架中使用System.Data.SqlClient命名空间;

建议在中间层应用程序中使用SQL Server7.0或以后版本;

建议在独立的应用程序中使用MSDE或SQL Server7.0或更高版本;

SQL Server6.5或更早版本,必须使用OLE DB.NET Data Provider中的OLE DB Provider For SQL Server。

OLE DB.NET Data Provider

.NET框架中使用System.Data.OleDb命名空间;

建议在中间层应用程序中使用SQL Server6.5或以前版本,或者任何在.NET框架SDK中指出的支持OLE DB接口清单的OLE DB Provider,OLE DB接口清单将在后面列出;

建议在独立的应用程序中使用Access,中间层应用程序不建议使用Access;

不再支持为ODBC的OLE DB Provider,要访问ODBC,使用ODBC.NET Data Provider。

ODBC.NET Data Provider

.NET框架中使用System.Data.Odbc命名空间;

提供对使用ODBC驱动连接的数据库的访问;

.NET Data Provider For Oracle

.NET框架中使用System.Data.OracleClient命名空间;

提供对Oracle数据库的访问。

Custom.NET Data Provider

提供一套接口,让你可以自定义一个Data Provider;

SQLXML Managed Classes

包含SQLXML Managed Classes的最新版SQLXML3.0,使得你可以访问SQL Server2000或以后版本的XML功能性扩展,比如执行XML模板文件、执行XPath查询和使用Updategrams或Diffgrams更新数据等;在SQLXML 3.0中存储过程和XML模板将会通过SOAP作为一种WEB服务。

        表格中提到的OLE DB接口清单,在这里把它列出

OLE DB 对象

接口

OLE DB Services

IdataInitilize

DataSource

IDBInitialize
IDBCreateSession
IDBProperties
IPersist
IDBInfo*

Session

ISessionProperties
IOpenRowset
IDBSchemaRowset*
ITransactionLocal*
IDBCreateCommand*

Command

IcommandText
ICommandProperties
ICommandWithParameters*
IAccessor (only required if ICommandWithParameters is supported)
ICommandPrepare*

MultipleResults

ImultipleResults

RowSet

Irowset
IAccessor
IColumnsInfo
IColumnsRowset*
IRowsetInfo (only required if DBTYPE_HCHAPTER is supported)

Row

IRow*

Error

IerrorInfo
IErrorRecords
ISQLErrorInfo*

    C.连接SQL Server7.0或更高版本

        使用SQL Server.NET Data Provider连接SQL Server7.0或更高版本是最好的方式,在于它建立与SQL Server的直接连接而中间不需要任何的技术层衔接。如下图一展示了各种访问SQL Server7.0或更高版本的技术比较:

图一(连接访问SQL Server7.0或更高版本的各种技术比较)

        以下例子演示怎样创建和打开一个到SQL Server7.0或更高版本数据库的连接:

‘Visual Basic

Dim nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;" & _                                                   "Initial Catalog=northwind")

nwindConn.Open()

‘C#

SqlConnection nwindConn = new SqlConnection("Data Source=localhost; Integrated Security=SSPI;" +

"Initial Catalog=northwind");

nwindConn.Open();

    D.连接ODBC数据源

        ODBC.NET Data Provider,使用System.Data.Odbc命名空间,拥有为SQL Server和OLE DB的.NET Data Porvider一样的结构,使用ODBC前缀(比如OdbcConnetion)和标准的ODBC连接字符。下面例子演示怎样创建和打开一个到ODBC数据源的连接:

‘Visual Basic

Dim nwindConn As OdbcConnection = New OdbcConnection("Driver={SQL Server};Server=localhost;" & _                                                     "Trusted_Connection=yes;Database=northwind")

nwindConn.Open()

‘C#

OdbcConnection nwindConn = new OdbcConnection("Driver={SQL Server};Server=localhost;" +

"Trusted_Connection=yes;Database=northwind");

nwindConn.Open();

    E.使用DataReaders、DataSets、DataAdapters和DataViews

        ADO.NET使用DataSet和DataReader对象读取数据并存储。DataSet就好比是数据库的直系亲属,拥有数据库的所有表、顺序和数据库的约束(比如表间关系)。DataReader则从数据库读取快速的、只进的的和只读的数据流。使用DataSet,你将会经常使用DataAdapter(或者CommandBuilder)与你的数据库打交道,同时,你也许会使用DataView去排序和过滤数据,DataSet还允许你可以创建一个继承于DataSet的子对象来表现数据中的表、行和列。下面图二显示DataSet对象模型:

 

图二(DataSet对象模型)

下面将要介绍在什么时候使用DataSet或DataReader最恰当,同时也将说明如何使用DataAdapter(包括CommandBuilder)和DataView最优化对数据的访问。

    F.DataSet和DataReader的比较

        在设计你的应用程序时决定究竟使用DataSet还是使用DataReader,主要看在你的应用程序中要实现的功能性级别。

        使用DataSet可以在你的应用程序中做以下事情:

        I.在多个离散的结果表之间导航;

            一个DataSet可以包含多个结果表,这些结果表是不连续的。你可以分开处理这些表,也可以把这些表当作父子关系进行处理。

        II.操作多个数据源(比如从XML文件和电子数据表等不只一个数据库得到的混合数据);

        下面的例子演示从SQL Server2000的Northwind数据库读取一个customers表的清单和从Access2000的Northwind数据库读取一个orders表的清单,然后使用DataRelation在两个表之间建立一个对应关系:

‘Visual Basic

Dim custConn As SqlConnection= New SqlConnection("Data Source=localhost;Integrated Security=SSPI;" & _

"Initial Catalog=northwind;")

Dim custDA As SqlDataAdapter = New SqlDataAdapter("SELECT * FROM Customers", custConn)

Dim orderConn As OleDbConnection = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;" & _                                                       "Data Source=c:\Program Files\Microsoft Office\" & _                                                       "Office\Samples\northwind.mdb;")

Dim orderDA As OleDbDataAdapter = New OleDbDataAdapter("SELECT * FROM Orders", orderConn)

custConn.Open()

orderConn.Open()

Dim custDS As DataSet = New DataSet()

custDA.Fill(custDS, "Customers")

orderDA.Fill(custDS, "Orders")

custConn.Close()

orderConn.Close()

Dim custOrderRel As DataRelation = custDS.Relations.Add("CustOrders", _                                     custDS.Tables("Customers").Columns("CustomerID"), _                                    custDS.Tables("Orders").Columns("CustomerID"))

Dim pRow, cRow As DataRow

For Each pRow In custDS.Tables("Customers").Rows

 Console.WriteLine(pRow("CustomerID").ToString())

 For Each cRow In pRow.GetChildRows(custOrderRel)

    Console.WriteLine(vbTab & cRow("OrderID").ToString())

 Next

Next

‘C#

SqlConnection custConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind;");

SqlDataAdapter custDA = new SqlDataAdapter("SELECT * FROM Customers", custConn);

OleDbConnection orderConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;" +                                                "Data Source=c:\\Program Files\\Microsoft Office\\Office\\Samples\\northwind.mdb;");

OleDbDataAdapter orderDA = new OleDbDataAdapter("SELECT * FROM Orders", orderConn);

custConn.Open();

orderConn.Open();

DataSet custDS = new DataSet();

custDA.Fill(custDS, "Customers");

orderDA.Fill(custDS, "Orders");

custConn.Close();

orderConn.Close();

DataRelation custOrderRel = custDS.Relations.Add("CustOrders",                              custDS.Tables["Customers"].Columns["CustomerID"],                              custDS.Tables["Orders"].Columns["CustomerID"]);

foreach (DataRow pRow in custDS.Tables["Customers"].Rows)

{

 Console.WriteLine(pRow["CustomerID"]);

   foreach (DataRow cRow in pRow.GetChildRows(custOrderRel))

    Console.WriteLine("\t" + cRow["OrderID"]);

}

        III.层中交换数据或者使用一个XML WEB服务,与DataReader不一样的是DataSet可以被传递给一个远程的客户端;

            下面的例子演示如何创建一个XML WEB服务,其中使用GetCustomers取数据库中customers表数据,使用UpdateCustomers更新数据库中数据:

1.     ‘Visual Basic

2.     <% @ WebService Language = "VB" Class = "Sample" %>

3.     Imports System

4.     Imports System.Data

5.     Imports System.Data.SqlClient

6.     Imports System.Web.Services

7.     <WebService(Namespace:="http://microsoft.com/webservices/")> _

8.     Public Class Sample

9.       Public nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind")

10.   <WebMethod( Description := "Returns Northwind Customers", EnableSession := False )> _

11.   Public Function GetCustomers() As DataSet

12.     Dim custDA As SqlDataAdapter = New SqlDataAdapter("SELECT CustomerID, CompanyName FROM Customers", nwindConn)

13.     Dim custDS As DataSet = New DataSet()

14.     custDA.MissingSchemaAction = MissingSchemaAction.AddWithKey

15.     custDA.Fill(custDS, "Customers")

16.     GetCustomers = custDS

17.   End Function

18.   <WebMethod( Description := "Updates Northwind Customers", EnableSession := False )> _

19.   Public Function UpdateCustomers(custDS As DataSet) As DataSet

20.     Dim custDA As SqlDataAdapter = New SqlDataAdapter()

21.     custDA.InsertCommand = New SqlCommand("INSERT INTO Customers (CustomerID, CompanyName) " & _                                          "Values(@CustomerID, @CompanyName)", nwindConn)

22.     custDA.InsertCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID")

23.     custDA.InsertCommand.Parameters.Add("@CompanyName", SqlDbType.NChar, 15, "CompanyName")

24.     custDA.UpdateCommand = New SqlCommand("UPDATE Customers Set CustomerID = @CustomerID, " & _

25. "CompanyName = @CompanyName WHERE CustomerID = @OldCustomerID", nwindConn)

26.     custDA.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID")

27.     custDA.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NChar, 15, "CompanyName")

28.     Dim myParm As SqlParameter = custDA.UpdateCommand.Parameters.Add("@OldCustomerID", SqlDbType.NChar, 5, "CustomerID")

29.     myParm.SourceVersion = DataRowVersion.Original

30.     custDA.DeleteCommand = New SqlCommand("DELETE FROM Customers WHERE CustomerID = @CustomerID", nwindConn)

31.     myParm = custDA.DeleteCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID")

32.     myParm.SourceVersion = DataRowVersion.Original

33.     custDA.Update(custDS, "Customers")

34.     UpdateCustomers = custDS

35.   End Function

36. End Class

37.  

38. ‘C#

39. <% @ WebService Language = "C#" Class = "Sample" %>

40. using System;

41. using System.Data;

42. using System.Data.SqlClient;

43. using System.Web.Services;

44. [WebService(Namespace="http://microsoft.com/webservices/")]

45. public class Sample

46. {

47.   public SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind");

48.   [WebMethod( Description = "Returns Northwind Customers", EnableSession = false )]

49.   public DataSet GetCustomers()

50.   {

51.     SqlDataAdapter custDA = new SqlDataAdapter("SELECT CustomerID, CompanyName FROM Customers", nwindConn);

52.     DataSet custDS = new DataSet();

53.     custDA.MissingSchemaAction = MissingSchemaAction.AddWithKey;

54.     custDA.Fill(custDS, "Customers");

55.     return custDS;

56.   }

57.   [WebMethod( Description = "Updates Northwind Customers", EnableSession = false )]

58.   public DataSet UpdateCustomers(DataSet custDS)

59.   {

60.     SqlDataAdapter custDA = new SqlDataAdapter();

61.     custDA.InsertCommand = new SqlCommand("INSERT INTO Customers (CustomerID, CompanyName) " +                                          "Values(@CustomerID, @CompanyName)", nwindConn);

62.     custDA.InsertCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID");

63.     custDA.InsertCommand.Parameters.Add("@CompanyName", SqlDbType.NChar, 15, "CompanyName");

64.     custDA.UpdateCommand = new SqlCommand("UPDATE Customers Set CustomerID = @CustomerID, " + "CompanyName = @CompanyName WHERE CustomerID = @OldCustomerID", nwindConn);

65.     custDA.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID");

66.     custDA.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NChar, 15, "CompanyName");

67.     SqlParameter myParm = custDA.UpdateCommand.Parameters.Add("@OldCustomerID", SqlDbType.NChar, 5, "CustomerID");

68.     myParm.SourceVersion = DataRowVersion.Original;

69.     custDA.DeleteCommand = new SqlCommand("DELETE FROM Customers WHERE CustomerID = @CustomerID", nwindConn);

70.     myParm = custDA.DeleteCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID");

71.     myParm.SourceVersion = DataRowVersion.Original;

72.     custDA.Update(custDS, "Customers");

73.     return custDS;

74.   }

}

        IV.数据的再使用(比如排序、搜索或过滤数据);

        V.执行每行的大容量数据处理,处理DataReader挂起的连接服务已不再需要、影响性能的每一行;

        VI.使用诸如XSLT转换或者XPath查询等XML操作的多重数据。

            下面的例子介绍如何使用XmlDataDocument同步DataSet数据和如何使用XSLT样式文件在HTML文件中包含DataSet数据,首先是XSLT样式文件:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="CustomerOrders">

 <HTML>

 <STYLE>

 BODY {font-family:verdana;font-size:9pt}

 TD   {font-size:8pt}

 </STYLE>

    <BODY>

    <TABLE BORDER="1">

      <xsl:apply-templates select="Customers"/>

    </TABLE>

    </BODY>

 </HTML>

</xsl:template>

<xsl:template match="Customers">

    <TR><TD>

      <xsl:value-of select="ContactName"/>, <xsl:value-of select="Phone"/><BR/>

    </TD></TR>

      <xsl:apply-templates select="Orders"/>

</xsl:template>

<xsl:template match="Orders">

 <TABLE BORDER="1">

    <TR><TD valign="top"><B>Order:</B></TD><TD valign="top"><xsl:value-of select="OrderID"/></TD></TR>

    <TR><TD valign="top"><B>Date:</B></TD><TD valign="top"><xsl:value-of select="OrderDate"/></TD></TR>

    <TR><TD valign="top"><B>Ship To:</B></TD>

        <TD valign="top"><xsl:value-of select="ShipName"/><BR/>

        <xsl:value-of select="ShipAddress"/><BR/>

        <xsl:value-of select="ShipCity"/>, <xsl:value-of select="ShipRegion"/> <xsl:value-of select="ShipPostalCode"/><BR/>

        <xsl:value-of select="ShipCountry"/></TD></TR>

 </TABLE>

</xsl:template>

</xsl:stylesheet>

            接着下面的代码演示如何填充DataSet的数据和运用XSLT样式:

‘Visual Basic

Imports System

Imports System.Data

Imports System.Data.SqlClient

Imports System.Xml

Imports System.Xml.Xsl

Public Class Sample

 Public Shared Sub Main()

    Dim nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Initial Catalog=northwind;Integrated Security=SSPI")

    nwindConn.Open()

    Dim myDataSet As DataSet = New DataSet("CustomerOrders")

    Dim custDA As SqlDataAdapter = New SqlDataAdapter("SELECT * FROM Customers", nwindConn)

    custDA.Fill(myDataSet, "Customers")

    Dim ordersDA As SqlDataAdapter = New SqlDataAdapter("SELECT * FROM Orders", nwindConn)

    ordersDA.Fill(myDataSet, "Orders")

    nwindConn.Close()

    myDataSet.Relations.Add("CustOrders",_                            myDataSet.Tables("Customers").Columns("CustomerID"),_                            myDataSet.Tables("Orders").Columns("CustomerID")).Nested = true

    Dim xmlDoc As XmlDataDocument = New XmlDataDocument(myDataSet)

    Dim xslTran As XslTransform = New XslTransform

    xslTran.Load("transform.xsl")

    Dim writer As XmlTextWriter = New XmlTextWriter("xslt_output.html", System.Text.Encoding.UTF8)

    xslTran.Transform(xmlDoc, Nothing, writer)

    writer.Close()

 End Sub

End Class

‘C#

using System;

using System.Data;

using System.Data.SqlClient;

using System.Xml;

using System.Xml.Xsl;

public class Sample

{

 public static void Main()

 {

    SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Initial Catalog=northwind;Integrated Security=SSPI;");

    nwindConn.Open();

    DataSet custDS = new DataSet("CustomerDataSet");

    SqlDataAdapter custDA = new SqlDataAdapter("SELECT * FROM Customers", nwindConn);

    custDA.Fill(custDS, "Customers");

    SqlDataAdapter ordersDA = new SqlDataAdapter("SELECT * FROM Orders", nwindConn);

    ordersDA.Fill(custDS, "Orders");

    nwindConn.Close();

    custDS.Relations.Add("CustOrders",

                         custDS.Tables["Customers"].Columns["CustomerID"],

                         custDS.Tables["Orders"].Columns["CustomerID"]).Nested = true;

    XmlDataDocument xmlDoc = new XmlDataDocument(custDS);

    XslTransform xslTran = new XslTransform();

    xslTran.Load("transform.xsl");

    XmlTextWriter writer = new XmlTextWriter("xslt_output.html", System.Text.Encoding.UTF8);

    xslTran.Transform(xmlDoc, null, writer);

    writer.Close();

 }

}

使用DataReader可以在你的应用程序中做以下事情:

        I.不需要缓存数据;

        II.处理太大而不能存储的数据;

        III.需要以只进、只读和快速方式一次性访问数据的。

    G.使用一个自定义的强有力的DataSet类型的好处

        通过创建一个继承于DataSet的子对象,你可以在运行期间执行类型检查和声明。当你有了一个确定的计划或者为你的DataSet有相关的结构,你就可以创建一个用行和列表述一个对象的DataSet。比如,你表露一个消费者对象的名字属性来取代表露消费者表的一行中的名字列。有关此节详细信息,请参考微软站点上的文章:Working with a Typed DataSet

    H.在自定义的DataSet中处理无效数据

        通过XSD语言检查你的DataSet确保你的DataSet适当地处理无效引用。nullValue注释使你把BBNull替换成别的字符,String.Empty;或者保留无效引用,抛出错误提示,提示将取决于你应用程序的上下文,默认情况是引用了无效字符。

    I.在DataSet中刷新数据

        如果你要从数据库刷新你的DataSet,使用DataAdapter.Fill,如果你的DataTable拥有主键,DataAdapter.Fill将根据主键匹配新的行,同时从数据库取值运用到已存在的行。除非已刷新行在再次刷新前被修改,否者它的RowState将会被设置为UnChanged。注意的是如果DataTable没有设置主键,你的DataSet有可能出现重复的值。如果你想从数据库刷新一个表并保留任何表中行的更改,那么你就要首先填充一个新表,然后利用preserveChanges等于true来合并那个DataTable到你的DataSet中去。

    J.在DataSet中搜索数据

        当你在一个DataSet中查询特殊标准的行时,利用索引查询将会增加你的查询性能。当你给一个DataTable设计主键时,索引同时也创建了。当你为一个DataTable创建DataView时,索引也同时创建了。以下是使用索引查询的一些情况:

        I.如果查询与DataTable中标识主键的列顺序相反,使用DataTable.Rows.Find代替DataTable.Select;

        II.如果查询包括无主键的列,你可以使用DataView为数据的多重查询改善性能。当你在DataView中使用排序时,查询的同时就会创建一个索引。DataView使用Find和FindRows方法查询DataTable中的数据;

        IV.假如你不需要表的排序视图,你也可以利用DataView为DataTable创建一个索引查询。注意的是这仅仅在你执行多重查询时才有优势,如果你只是执行一个简单查询,使用此方法将会降低你的查询效率。

    K.DataView的结构

        前面也讲过,在给DataTable创建DataView和Sort、RowFilter或者RowStateFilter属性发生更改的同时潜在的也给DataTable创建了索引。创建DataView对象时,如果Sort、RowFilter和RowStateFilter属性也同时指定,那么索引将只创建一次;如果创建一个空的DataView,那么索引至少被创建两次。

    L.页面调度

        ADO.NET使你可以很清楚地控制从你的数据库返回什么样的数据和有多少数据存储到一个DataSet。以下没有单一的介绍调度一个查询结果,但是当你设计你的应用程序时应该考虑到以下情况:

        I.避免在使用DataAdapter.Fill时,在startRecord和maxRecords值上溢出。

        II.解决这类问题的办法是使用WHERE语句、ORDER BY语句和TOP断言。

        III.还有一种解决办法是使用TOP断言和嵌套的SELECT声明。比如如下代码:

     SELECT TOP 10 * FROM

(SELECT TOP 30 * FROM Customers ORDER BY Id ASC) AS Table1 ORDER BY Id DESC

IV.如果你的日期不是经常改变,你可以使用DataSet的存储功能改善执行性能,比如你可以存储相当10页的数据到你的DataSet,然后当用户访问超过在存储区的FirstPage和LastPage时才查询数据库以获得新的数据。

    M.有计划地填充DataSet

        当使用数据填充DataSet时,DataAdapter.Fill方法使用DataSet已有的计划和SelectCommand返回的数据对DataSet进行填充。如果DataSet中没有与之对应的表将会失败,Fill创建一个表,默认情况下,Fill仅仅定义列和列的类型。你可以通过设置DataAdapter的MissingSchemaAction属性重载默认的Fill方法。举例,要使Fill方法创建表时总是包含主键信息、唯一约束、列属性、是否允许空值、列的最大长度、只读列和自动增量列,指定DataAdapter.MissingSchemaAction为MissingSchemaAction.AddWithKey。作为选择,你也可以在调用DataAdapter.Fill之前调用DataAdpater.FillSchema来保证填充DataSet时计划到位。调用FillSchema会给数据库增加额外的负担来输出Schema信息,所以最好的建议是指定DataSet的计划,或者在调用Fill之前设置DataAdapter的MissingSchemaAction。

    N.使用CommandBuilder

        CommandBuilder自动地生成基于DataAdapter的SelectCommand的DataAdapter的InsertCommand、UpdateCommand和DeleteCommand属性。提供SelectCommand执行一个简单的SELECT,以下信息介绍使用CommandBuilder的最佳处理。

        I.在设计阶段不要使用CommandBuilder,否者产生DataAdapter Command属性的进程将会受到干扰。如果你预先知道你的UPDATE、INSERT和DELETE声明的内容,你应该清楚地指定。一个最好的设计方案是为你的UPDATE、INSERT和DELETE创建存储过程,并在DataAdapter的Command属性中设置和使用它们。

        II.CommandBuilder使用SelectCommand决定其他Command属性的值。如果DataAdapter的SelectCommand本身发生变化,应该使用RefreshSchema去刷新Command的属性。

        III.只要DataAdapter的Command属性为空,CommandBuilder就仅仅创建一个Command,即使你明确地指定Command的属性值,CommandBuilder也不会重写,所以如果你想创建一个Command并保留以前的属性设置,那么就把Command的属性设置为null。

    O.SQL的批声明和处理

        很多的数据库都支持在一条命令中使用综合查询或批处理或多条子命令。比如SQL Server中使用“;”。在一条命令中使用综合的多重命令可以有效地减少与数据库之间交互的次数并在你的应用程序中提高效率。比如在你的应用程序中使用批处理完成所有的删除(delete)任务等等。

        使用批处理确实提高了效率,但同时也在你的应用程序中管理更新DataSet数据时增加了复杂性。要使得复杂性变简单化,你就要在你的DataSet中为每个DataTable创建一个DataAdapter。

    P.使用多个表填充一个DataSet

        如果你是用批处理从多个表返回数据并把这些数据填充到一个DataSet,fill方法将会使用第一个表的表名命名第一个表,以后的表命名将会采用在第一个表的表名基础上加上一个递增的数字。举例,下面的代码将逐步说明fill方法的工作原理:

        ‘Visual Basic

        Dim da As SqlDataAdapter = New SqlDataAdapter(“select * from customers;select * from orders;”,myConnection)

        Dim ds As DataSet = New DataSet()

        da.fill(ds,”customers”)

        ‘C#

        SqlDataAdapter da = new SqlDataAdapter(“select * from customers;select * from orders;”,myConnection);

        DataSet ds = new DataSet();

        da.fill(ds,”customers”);

        如上面代码所示,customers表数据将会存放在一个命名为customers的DataTable中,而orders表数据将会放在一个命名为customers1的DataTable中。当然你也可以在数据填充结束后很容易地修改customers1表属性(TableName)为orders。然而,在以后的数据填充时,只会影响customers表中数据,而orders表将会忽略并同时创建一个新的命名为customers1的表。要解决这个问题,你就要在customers1和orders之间建立一个DataTableMapping映射。其他表也如此。举例说明:

        ‘Visual Basic

Dim da As SqlDataAdapter = New SqlDataAdapter("SELECT * FROM Customers; SELECT * FROM Orders;", myConnection)

da.TableMappings.Add("Customers1", "Orders")

Dim ds As DataSet = New DataSet()

da.Fill(ds, "Customers")

‘C#

SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Customers; SELECT * FROM Orders;", myConnection);

da.TableMappings.Add("Customers1", "Orders");

DataSet ds = new DataSet();

da.Fill(ds, "Customers");

    Q.使用DataReader

        下面是使用DataReader的一些技巧和一些问题的回答:

        I.在使用相关Command访问任何输出参数之前必须关闭DataReader;

        II.在读取数据结束后应当关闭DataReader。如果你的Connection仅仅是用来返回DataReader,那么在关闭DataReader之后你也应该立即关闭它。另外一个关闭Connection的方法是传递CommandBehavior.CloseConnection给ExecuteReader方法。此方法在你从一个方法中返回DataReader并且对这个DataReader没有关闭控制权或者关联的连接时使用时非常有用的。

        III.DataReader是为已连接的数据存取设计;

        IV.使用GetString、GetInt32等返回特殊数据类型数据;

        V.一个连接只允许使用一个DataReader。在ADO中,如果你只创建一个连接并使用两个recordsets,一个只读游标和一个只进游标,但实际上,ADO已经为你创建了一个隐式的连接,并在不用的时候隐式地关闭它。ADO.NET中是不行的,你必须为每个DataReader创建一个Connection,这也是为了让你在使用Connection时给予更多的控制信息。

        VI.默认下,DataReader每次读取时把行中所有的数据装载到内存中。并允许你随机存取当前行中数据。如果随即存取没有必要(没有必要把所有数据都装载到内存),并想提高执行效率,给ExecuteReader方法传递CommandBehavior.SequentialAccess,这样就会改变DataReader的默认动作为仅仅装载请求的数据到内存。注意的是这种方法要求你顺序地存取行中列数据,一旦你略过某一列,以后你将再不会读取到该列的数据。

        VII.如果当你在完成从一个DataReader读取数据后,仍然还有大量未读不需要的数据,这就要在调用DataReader的Close命令之前调用Cancel命令。调用DataReader的Close命令会导致把不需要的数据装载进来并在关闭游标之前清空数据。而调用Cancel命令就会丢弃这部分数据,从而DataReader在关闭之前就不会读取它们。如果你正在从你的命令返回输出参数,调用Cancel命令同样会丢弃它们。如果你需要读取任何输出参数,你就不要使用Cancel,而直接使用Close。

    R.BLOBs对象

        当你使用DataReader读取二进制数据时,你应该传递CommandBehavior.SequentialAccess给ExecuteReader方法调用。因为DataReader的默认情况是每次读取数据时把每行中所有的数据都存储到内存,而二进制数据又非常的大,结果就会使大量的内存空间被一个单一的BLOB占用。SequentialAccess使得你的DataReader默认行为为仅读取需要的数据。然后你就可以使用GetBytes或者GetChars决定一次读取多少数据。

        记住的是使用SequentialAccess后,你不能次序颠倒地访问DataReader中不同的字段。就是说,如果你的查询返回三列,其中第三列是BLOB数据类型,如果你想访问第三列数据,那么你就必须先访问第一列,然后是第二列,再然后才是第三列的BLOB数据。这是因为此时返回的数据是有序的,而且一旦你跳过某一列,再回过头来读取这一列是不行的。

    S.使用命令

        ADO.NET提供了执行命令的几种不同方法,同时也提供了优化执行命令的几种不同参数。下面将要介绍的是选择最佳执行命令的技巧和改善一个可执行命令的性能。

        I.OleDbCommand最佳实践

        .NET 框架中各种数据提供者之间的执行命令标准几乎是一样的。但是也有不同,下面是执行OleDbCommand的一些技巧:

            使用CommandType.Text调用存储过程,使用CommandType.StoredProcedure生成;

            确定设置OleDbParameter的类型、大小(如果要求)和精度(如果是数字或者小数),注意的是,如果你不明确设置OleDbParameter,OleDbCommand将会为你的执行命令重新生成OleDbParameter。

        II.SqlCommand最佳实践

            使用SqlCommand快速执行存储过程:如果你要调用一个存储过程,指定SqlCommand的CommandType为存储过程的CommandType。这样在执行命令时,就会提交此命令是调用存储过程,从而达到快速执行。

        III.使用已准备的方法

            Command.Prepare方法优化你的参数化执行命令。Prepare结构为多重调用最优化指定命令。要使用Prepare,你首先得理解你的数据库是怎样相应Prepare调用。SQL Server 2000中,Command已经被隐式优化和Prepare不是必须的;在SQL Server7.0或其它数据库中使用Prepare是有效的。

        IV.明确地指定计划和元数据

            ADO.NET中的很多对象都要推断元数据信息,只要用户不指定它,举例如下:

            如果在DataSet中不存在,DataAdapter.Fill方法就会创建表和列信息;

            CommandBuilder为独立表的Select命令生成DataAdpater命令参数;

            CommandBuilder.DeriveParameters组装一个命令对象的参数信息;

        如果什么时候都使用上面讲的方法,可能会降低执行性能。推荐在设计阶段和广告段应用程序中使用。可能的情况下,一般都要指定计划和元数据。这些包括指定DataSet的表和列、指定DataAdapter的Command属性和指定Command的参数信息。

        V.ExecuteScalar和ExecuteNonQuery

            如果你想只返回一个简单值,比如Count(*)、Sum(Price)或者Avg(Quantity),你可以使用ExecuteScalar,它帮助你一步到位得到你想要的值,从而避免使用DataReader的两步计算(ExecuteReader+GetValue);

            当你不想返回行信息,比如修改数据(INSERT、UPDATE、DELETE)或者仅需要输出参数或者返回值,使用ExecuteNonQuery,它去掉不必要的处理创建一个空的DataReader。

        VI.空值检查

            如果在你的表中某列允许空值,你可以使用Where语句进行空值检查,下面举例说明:

        select * from customers where ((LastName=@LastName) or (LastName IS NULL and @LastName IS NULL))

            上面语句检查了列是否为空和参数是否为空。

        VII.传递null参数值

            当你在命令中传递null参数值给数据库时,你不能使用null(Nothing在vb中),应该使用DBNull.Value。举例:

        ‘vb

        Dim param As SqlParameter = New SqlParameter(“@Name”,SqlDbType.NVarChar,20)

        param.Value = DBNull.Value

        ‘C#

        SqlParameter param = new SqlParameter(“@Name”,SqlDbType.NVarChar,20);

        param.Value = DBNull.Value;

        VIII.使用事务处理

            ADO.NET中的事务处理模型已经改变,在ADO中,一旦StartTransaction被调用,任何事务下的更新都被认为是事务的一部分。然而,在ADO.NET中,当Connection.BeginTransaction被调用,返回一个命令关联的事务对象(事务属性是由命令的事务属性指定的)。这样保证让你在一个Connection中执行多个事务。如果命令的Command.Transaction属性与开始的事务不一致,命令就不会完成并抛出错误。

        IX.使用Connections

            高效率的应用程序应该使用最少的时间与数据库建立连接,比如使用Connection Pooling等。下面将介绍如何使用ADO.NET建立高效率应用的一些数据库方面的技巧。

        Connection Pooling

            在SQL Server、OLE DB和.NET框架结构中的Data Provider中,都提供了隐式的连接池连接支持。你可以在ConnectionString中指定不同的参数值控制连接池的行为。比如下面的例子使OLE DB的连接池无效并自动地进行事务处理:

        Provider=SQLOLEDB;OLE DB Services=-4;Data Source=localhost;Integrated Security=SSPI;

            在SQL Server.NET Data Provider中提供了以下参数设置控制连接池的行为:Connection Lifttime、Connection Reset、Enlist、Max Pool Size、Min Pool Size和Pooling。

        使用DataAdapter最优化连接

            使用DataAdpater的Fill和Update方法时会自动地打开相应的连接。如果Fill或者Update打开一个连接,在它操作完成后它会关闭此连接。最好的执行方式是在你需要的时候才建立连接。同时减少多个操作时打开和关闭连接的次数。

            推荐你在仅仅执行一个Fill或者Update时,允许Fill或者Update方法隐式地打开和关闭连接;如果你要执行多个Fill或者Update,建议你显式地建立连接、执行Fill或者Update操作然后显式地关闭连接。

            额外地,在我们执行事务处理时,在开始事务之前应该显式地建立连接,并在事务结束后显式地关闭连接。举例:

‘Visual Basic

Public Sub RunSqlTransaction(da As SqlDataAdapter, myConnection As SqlConnection, ds As DataSet)

 myConnection.Open()

 Dim myTrans As SqlTransaction = myConnection.BeginTransaction()

 myCommand.Transaction = myTrans

 

 Try

    da.Update(ds)

    myTrans.Commit()

    Console.WriteLine("Update successful.")

 Catch e As Exception

    Try

      myTrans.Rollback()

    Catch ex As SqlException

      If Not myTrans.Connection Is Nothing Then

        Console.WriteLine("An exception of type " & ex.GetType().ToString() & _

                          " was encountered while attempting to roll back the transaction.")

      End If

    End Try

    Console.WriteLine("An exception of type " & e.GetType().ToString() & " was encountered.")

    Console.WriteLine("Update failed.")

 End Try

 myConnection.Close()

End Sub

‘C#

public void RunSqlTransaction(SqlDataAdapter da, SqlConnection myConnection, DataSet ds)

{

 myConnection.Open();

 SqlTransaction myTrans = myConnection.BeginTransaction();

 myCommand.Transaction = myTrans;

 try

 {

    da.Update(ds);

    myCommand.Transaction.Commit();

    Console.WriteLine("Update successful.");

 }

 catch(Exception e)

 {

    try

    {

      myTrans.Rollback();

    }

    catch (SqlException ex)

    {

      if (myTrans.Connection != null)

      {

        Console.WriteLine("An exception of type " + ex.GetType() +

                          " was encountered while attempting to roll back the transaction.");

      }

    }

    Console.WriteLine(e.ToString());

    Console.WriteLine("Update failed.");

 }

 myConnection.Close();

}

        X.总是关闭Connections和DataReaders

            在你使用完Connection或者DataReader对象后,你应该明确地关闭它们。系统中的碎片整理程序只是在最后需要的时候才进行整理,而一些很耗资源的连接还得由你自己来释放。同时如果你不明确地关闭连接,此连接就有可能不返回连接池,除非连接池的Max Pool Size已经达到并且此连接还仍然有效。

            注意:在你的类的Finalize方法中不要使用Close或者Dispose运用到一个Connection或者一个DataReader或者任何被管理对象上。在一个Finalizer中,仅仅释放你的类直接拥有的无法管理的资源。如果你的类不拥有任何无法管理的资源,就不要在你的类使用Finalize方法。

        XI.在C#中使用Using声明

            在C#中,一个非常便利的保证关闭你使用过的Connection和DataReader对象的方法是使用Using声明。当对象超出了它的使用范围,Using声明就会自动地释放该对象。举例:

‘C#

string connString = "Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;";

 

using (SqlConnection conn = new SqlConnection(connString))

{

 SqlCommand cmd = conn.CreateCommand();

 cmd.CommandText = "SELECT CustomerId, CompanyName FROM Customers";

 

 conn.Open();

 

 using (SqlDataReader dr = cmd.ExecuteReader())

 {

    while (dr.Read())

      Console.WriteLine("{0}\t{1}", dr.GetString(0), dr.GetString(1));

 }

}

            Using声明在Visual Basic.Net中不可用。

        XII.避免访问OleDbConnection.State属性

            如果你需要经常检查State属性,最好在OleDbConnection上监听StateChange事件。下面的代码演示当OleDbConnection.State发生变化时使用StateChange向控制台发送一条消息:

‘Visual Basic

AddHandler nwindConn.StateChange, New StateChangeEventHandler(AddressOf OnStateChange)

Protected Shared Sub OnStateChange(sender As Object, args As StateChangeEventArgs)

 Console.WriteLine("The current Connection state has changed from {0} to {1}.", _

                    args.OriginalState, args.CurrentState)

End Sub

‘C#

nwindConn.StateChange += new StateChangeEventHandler(OnStateChange);

protected static void OnStateChange(object sender, StateChangeEventArgs args)

{

 Console.WriteLine("The current Connection state has changed from {0} to {1}.",

                    args.OriginalState, args.CurrentState);

}

 T.与XML结合

        ADO.NET在DataSet中提供对XML的广泛支持,同时在SQL Server2000或以后版本中的XML功能性扩展也能在ADO.NET中得到充分运用。你可以使用SQLXML访问在SQL Server2000和以后版本中提供的XML功能性扩展。下面是使用XML和ADO.NET的一些技巧信息。

        I.DataSet和XML

        DataSet和XML的完美整合,可以使你完成以下事情:

            从XSD计划中载入一个DataSet的计划或相关结构;

            下面的例子说明一个XSD文件的结构,其中MyDataSet就是我们的DataSet元素,它下面包含一个customers复合类型元素,有了它我们就可以映射创建一个这样的表:Customers (CustomerID,CompanyName,Phone),同时也定义我们的DataSet的计划或者结构:

<xs:schema id="SomeID"

             xmlns=""

             xmlns:xs="http://www.w3.org/2001/XMLSchema"

             xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">

    <xs:element name="MyDataSet" msdata:IsDataSet="true">

      <xs:complexType>

        <xs:choice maxOccurs="unbounded">

          <xs:element name="customers" >

            <xs:complexType >

              <xs:sequence>

                <xs:element name="CustomerID" type="xs:integer"

                             minOccurs="0" />

                <xs:element name="CompanyName" type="xs:string"

                             minOccurs="0" />

                <xs:element name="Phone" type="xs:string" />

              </xs:sequence>

            </xs:complexType>

           </xs:element>

        </xs:choice>

      </xs:complexType>

    </xs:element>

 </xs:schema>

            从XML文件中载入一个DataSet的内容;

            要从XML文件填充DataSet的内容,请使用DataSet对象的ReadXml方法。下面的例子说明如何从一个XML文件读取数据到一个DataSet:

‘Visual Basic

Dim myDS As DataSet = New DataSet

myDS.ReadXml("input.xml", XmlReadMode.ReadSchema)

‘C#

DataSet myDS = new DataSet();

myDS.ReadXml("input.xml", XmlReadMode.ReadSchema);

            当没有提供计划时从一个XML文件的内容中推断一个DataSet的计划;

            要从一个XML文件载入DataSet的计划信息,你可以使用DataSet对象的ReadXmlSchema方法。如果没有提供计划,你还可以使用InferXmlSchema从XML文件推断DataSet的计划,下面的例子介绍如何通过InferXmlSchema从一个XML文件推断出DataSet的计划:

‘Visual Basic

Dim myDS As DataSet = New DataSet

myDS.InferXmlSchema("input_od.xml", New String[] {"urn:schemas-microsoft-com:officedata"})

‘C#

DataSet myDS = new DataSet();

myDS.InferXmlSchema("input_od.xml", new string[] "urn:schemas-microsoft-com:officedata");

            象XSD格式计划一样写一个DataSet的计划;

            下面的例子展示如何通过ReadXmlSchema从一个XSD文件载入DataSet的计划:

‘Visual Basic

Dim myDS As DataSet = New DataSet

myDS.ReadXmlSchema("schema.xsd")

‘C#

DataSet myDS = new DataSet();

myDS.ReadXmlSchema("schema.xsd");

            象XML格式文件一样读写一个DataSet的内容。

            利用DiffGrams从DataSet中读写内容,下面的例子显示在提交更改之前更新表中一行数据的结果,其中CustomerID为ALFKI的那一行数据被修改但是还没有更新:

<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">

 <CustomerDataSet>

    <Customers diffgr:id="Customers1" msdata:rowOrder="0" diffgr:hasChanges="modified">

      <CustomerID>ALFKI</CustomerID>

      <CompanyName>New Company</CompanyName>

    </Customers>

    <Customers diffgr:id="Customers2" msdata:rowOrder="1" diffgram:hasErrors="true">

      <CustomerID>ANATR</CustomerID>

      <CompanyName>Ana Trujillo Emparedados y helados</CompanyName>

    </Customers>

    <Customers diffgr:id="Customers3" msdata:rowOrder="2">

      <CustomerID>ANTON</CustomerID>

      <CompanyName>Antonio Moreno Taquerí­a</CompanyName>

    </Customers>

    <Customers diffgr:id="Customers4" msdata:rowOrder="3">

      <CustomerID>AROUT</CustomerID>

      <CompanyName>Around the Horn</CompanyName>

    </Customers>

 </CustomerDataSet>

 <diffgr:before>

    <Customers diffgr:id="Customers1" msdata:rowOrder="0">

      <CustomerID>ALFKI</CustomerID>

      <CompanyName>Alfreds Futterkiste</CompanyName>

    </Customers>

 </diffgr:before>

 <diffgr:errors>

    <Customers diffgr:id="Customers2" diffgr:Error="An optimistic concurrency violation has occurred for this row."/>

 </diffgr:errors>

</diffgr:diffgram>

        注意:你可以在你的DataSet中使用XPath查询和XSLT转换来同步运用XML的功能性,或者提供一个相关的视图,或者创建一个XML文档数据的一个副本。

        II.计划接口

            当你从一个XML文件载入一个DataSet时,你可以从XSD计划载入DataSet的计划,或者你可以在载入数据之前预先确定表和列。如果这里没有XSD计划或者你又不知道那个表和列是XML文件内容确定的,那么你可以使用基于XML文档结构推断计划。

            计划接口是一个很有用的移植工具,但是它应该限制在设计阶段的应用程序中,仅仅因为以下几点:

            推断计划将会提出额外的处理从而影响应用程序性能的提高;

            所有的列将会是一个数据类型:string;

            推断过程具有不确定性。那就是说,它是基于XML文件的,而不是基于有意的计划。

        III.SQL SERVER 的FOR XML查询

            如果你想返回如SQL SERVER的FOR XML查询结果,你可以用SQL Server.NET Data Provider直接使用SqlCommand.ExecuteXmlReader方法创建一个XmlReader。

        IV.SQLXML管理类

            在.NET框架中SQLXML管理类使用Microsoft.Data.SqlXml命名空间。它使得你可以执行Xpath查询和XML模板文件,如同运用XSLT转换数据一样。最新版本是SQLXML3.0。

    U.更多有用技巧

        I.避免自动增量值冲突

            像大多数数据库一样,DataSet让你在增加新的数据时标识为自动增量的列自动填充增量值。使用自动增量时,应当避免本地DataSet的增量值与数据库的增量值相冲突。要避免这种情况,推荐在数据库和DataSet同时使用自动增量时,在你的DataSet中创建AutoIncrementStep为-1和AutoIncrementSeed为0的自动增量列,同时保证你的数据库中的列从1开始正方向递增。这样就保证一个负方向的增量不会与一个正方向的增量相冲突。另外一种方法是使用Guid代替自动增量。在DataSet中产生的Guid永远不会与数据库中产生的Guid一样。如果你的自动增量列只是简单地用作唯一值,并且不表示任何含义,建议你使用Guids代替自动增量。它们是唯一的并避免使用自动增量产生的额外工作。

        II.处理乐观并发错误

            因为DataSet与数据库是分离的,所以你应该在你的应用程序中避免当多个客户更新数据库数据时发生冲突。这里有几种处理乐观并发错误的解决方案。一是在你的表中增加一个时间戳列。二是校验一行中所有列的数据是否与你在SQL声明中使用Where子句找到的数据静态匹配。下面的例子说明如何使用where条件处理乐观并发错误:

‘Visual Basic

 Dim nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind")

 Dim custDA As SqlDataAdapter = New SqlDataAdapter("SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID", nwindConn)

 ' The Update command checks for optimistic concurrency violations in the WHERE clause.

 custDA.UpdateCommand = New SqlCommand("UPDATE Customers (CustomerID, CompanyName) VALUES(@CustomerID, @CompanyName) " & _

                                        "WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", nwindConn)

 custDA.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID")

 custDA.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30, "CompanyName")

 ' Pass the original values to the WHERE clause parameters.

 Dim myParm As SqlParameter

 myParm = custDA.UpdateCommand.Parameters.Add("@oldCustomerID", SqlDbType.NChar, 5, "CustomerID")

 myParm.SourceVersion = DataRowVersion.Original

 myParm = custDA.UpdateCommand.Parameters.Add("@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName")

 myParm.SourceVersion = DataRowVersion.Original

 ' Add the RowUpdated event handler.

 AddHandler custDA.RowUpdated, New SqlRowUpdatedEventHandler(AddressOf OnRowUpdated)

 Dim custDS As DataSet = New DataSet()

 custDA.Fill(custDS, "Customers")

 ' Modify the DataSet contents.

 custDA.Update(custDS, "Customers")

 Dim myRow As DataRow

 For Each myRow In custDS.Tables("Customers").Rows

    If myRow.HasErrors Then Console.WriteLine(myRow(0) & vbCrLf & myRow.RowError)

 Next

Private Shared Sub OnRowUpdated(sender As object, args As SqlRowUpdatedEventArgs)

 If args.RecordsAffected = 0

    args.Row.RowError = "Optimistic Concurrency Violation Encountered"

    args.Status = UpdateStatus.SkipCurrentRow

 End If

End Sub

‘C#

 SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind");

 SqlDataAdapter custDA = new SqlDataAdapter("SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID", nwindConn);

 // The Update command checks for optimistic concurrency violations in the WHERE clause.

 custDA.UpdateCommand = new SqlCommand("UPDATE Customers (CustomerID, CompanyName) VALUES(@CustomerID, @CompanyName) " +

                                        "WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", nwindConn);

 custDA.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID");

 custDA.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30, "CompanyName");

 // Pass the original values to the WHERE clause parameters.

 SqlParameter myParm;

 myParm = custDA.UpdateCommand.Parameters.Add("@oldCustomerID", SqlDbType.NChar, 5, "CustomerID");

 myParm.SourceVersion = DataRowVersion.Original;

 myParm = custDA.UpdateCommand.Parameters.Add("@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName");

 myParm.SourceVersion = DataRowVersion.Original;

 // Add the RowUpdated event handler.

 custDA.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);

 DataSet custDS = new DataSet();

 custDA.Fill(custDS, "Customers");

 // Modify the DataSet contents.

 custDA.Update(custDS, "Customers");

 foreach (DataRow myRow in custDS.Tables["Customers"].Rows)

 {

    if (myRow.HasErrors)

      Console.WriteLine(myRow[0] + "\n" + myRow.RowError);

 }

protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs args)

{

 if (args.RecordsAffected == 0)

 {

    args.Row.RowError = "Optimistic Concurrency Violation Encountered";

    args.Status = UpdateStatus.SkipCurrentRow;

 }

}

        III.协作设计

            在你写期间,你应当锁定DataSet。

        IV.仅当需要的时候才使用COM对象访问ADO

            ADO.NET设计为大量应用程序最好的解决方案。然而一些应用程序需要只有ADO对象才能提供的功能,比如ADOMD。这种情况下可以使用COM对象访问ADO,注意的是使用COM对象访问ADO数据会影响应用程序的执行效率。所以在设计应用程序时,首先应该考虑在使用COM对象访问ADO数据之前,看看ADO.NET是否就满足你的设计要求。

    V.ADO.NET和ADO的比较

        I.ADO.NET在ADO设计模型的基础上演变和发展而来,它并不取代COM程序员的ADO,更多地,它是为.NET程序员访问相关数据源、XML和应用程序数据设计。ADO.NET支持多样化的发展要求,包括创建数据库客户端和供应用程序、工具、语言、WEB浏览器等使用的中间层业务对象。ADO.NET与ADO有许多相似的地方。

        II.ADO为COM程序员提供了高效的、强大的与数据库打交道的各种接口。ADO能得到广泛的运用是因为它支持任何的自动化控制语言(比如VC、VB和脚本语言等)的调用。ADO基础上升级而来的ADO.NET提供更好的交互平台和可升级的数据访问。在ADO.NET中创建一个新的数据访问API集能提供较之于ADO接口几个优越的地方,如下所述:

            改进了与XML的结合

            随着XML在应用程序中扮演着越来越重要的角色,与XML结合的ADO.NET就应运而生。为了持续和装载数据以及数据的XML格式,ADO.NET依赖XML在多层之间或客户机之间远程传递数据。ADO.NET中使用的特殊XML表述形式提供在任何网络中十分便利地传输数据的方法,包括数据安全边界。同时,ADO.NET使用XML工具执行确认、分级查询和数据和数据之间的转换。

            综合.NET框架

            ADO结构如Recordset并不使用常见的设计结构,相反它模拟成一种数据导向。举例,ADO中的用来导航和得到数据的游标,它的功能性就与其它的比如数组和集合数据结构不同。然而,在ADO.NET中,因为存储的数据能通过公共的.NET框架结构暴露,包括数组和集合,所以你可以使用一些公共的方法与你的相关数据打交道。

            改良对离散业务模型的支持

            ADO使用Recordset提供有限的对离散访问的支持。ADO.NET介绍一个新的对象DataSet,它作为相关数据的一个公共的、存储的表现形式,在任何时候都被设计为离散的,它与外部数据并不保持持久的连接,它是包装、存储、交换、延续和装载数据的好方法。也就是说任何对数据的操作都是在本地进行,而不直接与真实的数据库打交道。

            数据访问行为的控制是清楚的

            ADO中包括在应用程序中并不总是要求和指定的隐含行为会限制应用程序的性能。而在ADO.NET中提供良好的定义和预先的行为、执行和语义要素组件使得你可以在一个高优化的方式下定位到一个普通的情节上。

            改善设计阶段的支持

            ADO源自执行阶段隐含的数据信息,而这种信息是基于花费昂贵代价才获得的元数据。在ADO.NET中的元数据只是在设计阶段起一个杠杆作用,从而提供执行阶段更好的性能和更好的稳定性。

        III.ADO设计

            为了更好地理解ADO.NET模型和设计思想,回顾一下ADO的概念是有用的。ADO使用一个单一的对象Recordset与所有数据类型打交道。Recordset被用来处理从数据库返回的只进流数据、翻卷服务器上数据或者翻卷一批存储结果集。数据上的改变会立即运用到数据库上或运用到使用乐观查询和更新操作的一批数据上。当你创建一个Recordset时你就明确了你所作的任务,Recordset结果行为的改变主要取决于你要求的Recordset参数。因为ADO使用一个单一的能在很多场合使用的Recordset对象,这使得你的应用程序中的对象模型很简单。然而,也很难写一个公用的、可预言的和最优化的代码,那是因为行为、执行和一个单一对象描述的语义要得到改变很大程度上取决于对象是如何创建和对象访问的是什么数据。

        IV.ADO.NET设计

            ADO.NET是考虑到开发者在访问和使用数据时共同面对的任务和问题而设计。宁可使用一个单一对象执行大量任务,还不如如ADO.NET中指定每个对象的功能性因素去完成对应的每个任务。ADO中的Recordset功能性被分解成ADO.NET中以下的几个清楚对象:DataReader,提供快速的、只进的和只读的访问去查询结果;DataSet,存储数据;DataAdapter,在DataSet和数据源之间架起一道桥梁;ExecuteNonQuery,不返回行;ExecuteScalar,返回一个单一值而不是一个行集。下面是一些详细说明:

            只进、只读数据流

            应用程序,特别是中间层应用程序,经常要程序化地处理一系列结果,要求在他们读的时候没有用户交互和没有更新或回滚结果。在ADO中,执行这类数据时使用Recordset的只进游标和只读锁。在ADO.NET中,DataReader优化了这种数据的执行性能,它通过提供一个非缓冲、只进和只读的数据流从数据库得到数据。

            返回单一值

            在ADO中要得到一个单一值,你需要通过创建一个Recordset—〉读取结果—〉得到单一值—〉关闭Recordset这样一个过程。在ADO.NET中你就可以使用Command对象的ExecuteScalar方法不需要额外的操作来获得单一值。

            离散数据访问

            ADO使用客户端游标定位离散数据的访问,而在ADO.NET中DataSet可以很清楚地实现离散数据的访问。DataSet能从一个多样的不同的数据源提供一个公有的、完全离散的数据表现形式,是因为DataSet是完全独立于数据源的。它不管你数据是从数据库来的,还是从XML文件来的,抑或是从应用程序中得到的。一个简单的DataSet可以装载从多个不同数据库或非数据库源的数据。然后使用DataRelation在多个表之间建立一个连接,尽管Recordset的MsDataShape提供者可以实现分级结构查询,但是DataSet提供更高的稳定性处理离散数据。同时DataSet提供以XML文件格式在远程客户端和服务器之间传输数据。

            从数据库得到数据和更新数据

            ADO.NET提供更好的执行阶段性能和可见性。举例,当使用ADO的Recordset对象进行批更新时,你必须为每个需要改变结果的行使用UPDATE、INSERT或DELETE声明。ADO产生这些声明,在执行阶段,是需要付出昂贵代价的获得元数据的。而在ADO.NET中,指定UPDATE、INSERT或DELETE命令就如同自定义业务逻辑(比如一个存储过程)一样,你可以使用DataAdapter实现这一切。DataAdapter在DataSet和数据源之间架起一道桥梁。让你在执行阶段就不是如ADO的Recordset一样需要在数据源中收集元数据信息。从而改善应用程序的执行性能。

        V.数据类型

            在ADO中,所有的结果返回一个Variant数据类型,在ADO.NET中,你可以得到列本身的数据类型。数据类型可以在System.Data.SqlTypes名称空间定义。

    W.有关ADO和ADO.NET的详细介绍,请参考微软上的资料:ADO.NET for the ADO Programmer

    总结:

    通过本文,希望与大家共同交流和学习,有不当之处请大家指正,谢谢!

 

posted @ 2007-02-04 11:42  fengye515  阅读(507)  评论(0编辑  收藏  举报