ADO
NET作为微软最新的数据访问技术
已经在企业开发中得到了广泛的应用
对于一线的开发人员来说
掌握基本的概念和技术之后
提高应用水平和解决实际问题的最有效手段
莫过于相互交流彼此的最佳时间经验经验
在这篇文章中
两位ADO
NET专家向读者毫无保留地
详尽地介绍了很多实用经验
简介 本文为您提供了在Microsoft ADONET应用程序中实现和获得最佳性能可伸缩性以及功能的最佳解决方案同时也讲述了使用ADONET中可用对象的最佳实践并提出一些有助于优化ADONET应用程序设计的建议
NET框架数据提供程序
NET框架中的数据提供程序(Data PRovider)在应用程序和数据源之间起到桥梁作用NET框架数据提供程序能够从数据源中返回查询结果对数据源执行命令将DataSet中的更改传播给数据源本文包括有关哪个NET框架数据提供程序是最适合您需要的一些技巧
使用哪个NET框架数据提供程序?
为了使您的应用程序获得最佳性能请使用最适合您的数据源的NET框架数据提供程序有许多数据提供程序可供您的应用程序选用
连接到SQL Server 或更高版本 为了在连接到Microsoft SQL Server 或更高版本时获得最佳性能请使用SQL Server NET数据提供程序SQL Server NET数据提供程序的设计目的就在于不通过任何附加技术层就可以直接访问SQL Server
连接到ODBC数据源 ODBC NET数据提供程序可在MicrosoftDataODBC命名空间中找到它的体系结构与用于SQL Server和OLE DB的NET数据提供程序相同ODBC NET数据提供程序遵循命名约定以ODBC为前缀(例如OdbcConnection)并使用标准ODBC连接字符串
使用DataReaderDataSetDataAdapter和DataView ADONET提供以下两个对象用于检索关系数据并将其存储在内存中DataSet和DataReaderDataSet提供一个内存中数据的关系表示形式一整套包括一些表在内的数据(这些表包含数据对数据进行排序并约束数据)以及表之间的关系DataReader提供一个来自数据库的快速仅向前只读数据流
当使用DataSet时经常会利用DataAdapter(也可能是CommandBuilder)与数据源进行交互当使用DataSet时也可以利用DataView对DataSet中的数据应用排序和筛选也可以从DataSet继承创建强类型DataSet用于将表行和列作为强类型对象属性公开
下列主题包括的信息涉及使用DataSet或DataReader的最佳时机如何优化访问它们所包含数据以及如何优化使用DataAdapter(包括CommandBuilder)和DataView的技巧
DataSet与DataReader 当设计应用程序时要考虑应用程序所需功能的等级以确定使用DataSet或者是DataReader
要通过应用程序执行以下操作就要使用DataSet
) 在结果的多个离散表之间进行导航
) 操作来自多个数据源(例如来自多个数据库一个xml文件和一个电子表格的混合数据)的数据
) 在各层之间交换数据或使用XML Web服务与DataReader不同的是DataSet能传递给远程客户端
) 重用同样的记录集合以便通过缓存获得性能改善(例如排序搜索或筛选数据)
) 每条记录都需要执行大量处理对使用DataReader返回的每一行进行扩展处理会延长服务于DataReader的连接的必要时间这影响了性能
) 使用XML操作对数据进行操作例如可扩展样式表语言转换(XSLT转换)或XPath查询
对于下列情况要在应用程序中使用DataReader ) 不需要缓存数据
) 要处理的结果集太大内存中放不下
) 一旦需要以仅向前只读方式快速访问数据
注填充DataSet时DataAdapter使用DataReader因此使用DataAdapter取代DataSet提升的性能表现为节省了DataSet占用内存和填充DataSet需要的循环一般来说此性能提升只是象征性的因此设计决策应以所需功能为基础
使用强类型DataSet的好处 DataSet的另一个好处是可被继承以创建一个强类型DataSet强类型DataSet的好处包括设计时类型检查以及Microsoft Visual StudioNET用于强类型DataSet语句结束所带来的好处修改了DataSet的架构或关系结构后就可以创建一个强类型DataSet将行和列作为对象的属性公开而不是作为集合中的项公开例如不公开客户表中行的姓名列而公开Customer对象的Name属性类型化DataSet从DataSet类派生因此不会牺牲DataSet的任何功能也就是说类型化DataSet仍能远程访问并作为数据绑定控件(例如DataGrid)的数据源提供如果架构事先不可知仍能受益于通用DataSet的功能但却不能受益于强类型DataSet的附加功能
处理强类型DataSet中的空引用 使用强类型DataSet时可以使用DataSet的XML架构定义语言(XSD)架构来确保强类型DataSet可以正确处理空引用nullValue标识符使您可用一个指定的值StringEmpty代替DBNull保留空引用或引发异常选择哪个选项取决于应用程序的上下文默认情况下如果遇到空引用就会引发异常
刷新DataSet中的数据 如果想用服务器上的更新值刷新DataSet中的值就使用DataAdapterFill如果有在DataTable上定义的主键DataAdapterFill会根据主键进行新行匹配并且当更改到现有行时应用服务器上的值即使刷新之前修改了这些数据刷新行的RowState仍被设置为Unchanged注意如果没有为DataTable定义主键DataAdapterFill就用可能重复的主键值添加新行
如果想用来自服务器的当前值刷新表并同时保留对表中的行所做的任何更改必须首先用DataAdapterFill填充表并填充一个新的DataTable然后用preserveChanges值true将DataTable合并到DataSet之中
在DataSet中搜索数据 在DataSet中查询与特定条件相匹配的行时可以利用基于索引的查找提高搜索性能当将PrimaryKey值赋给DataTable时会创建一个索引当给DataTable创建DataView时也会创建一个索引下面是一些利用基于索引进行查找的技巧
) 如果对组成DataTable的PrimaryKey的列进行查询要使用DataTableRowsFind而不是DataTableSelect
) 对于涉及到非主键列的查询可以使用DataView为数据的多个查询提高性能当将排序顺序应用到DataView时就会建立一个搜索时使用的索引DataView公开Find和FindRows方法以便查询基础DataTable中的数据
) 如果不需要表的排序视图仍可以通过为DataTable创建DataView来利用基于索引的查找注意只有对数据执行多个查询操作时这样才会带来好处如果只执行单一查询创建索引所需要的处理就会降低使用索引所带来的性能提升
DataView构造 如果创建了DataView并且修改了SortRowFilter或RowStateFilter属性DataView就会为基础DataTable中的数据建立索引创建DataView对象时要使用DataView构造函数它用SortRowFilter和RowStateFilter值作为构造函数参数(与基础DataTable一起)结果是创建了一次索引创建一个空DataView并随后设置SortRowFilter或RowStateFilter属性会导致索引至少创建两次
ADONET可以显式控制从数据源中返回什么样的数据以及在DataSet中本地缓存多少数据对查询结果的分页没有唯一的答案但下面有一些设计应用程序时应该考虑的技巧
) 避免使用带有startRecord和maxRecords值的DataAdapterFill重载当以这种方式填充DataSet时只有maxRecords参数(从startRecord参数标识的记录开始)指定的记录数量用于填充DataSet但无论如何总是返回完整的查询这就会引起不必要的处理用于读取不需要的记录而且为了返回附加记录会耗尽不必要的服务器资源
) 用于每次只返回一页记录的技术是创建SQL语句将WHERE子句以及ORDER BY子句和TOP谓词组合起来此技术取决于存在一种可唯一标识每一行的办法当浏览下一页记录时修改WHERE子句使之包含所有唯一标识符大于当前页最后一个唯一标识符的记录当浏览上一页记录时修改WHERE子句使之返回所有唯一标识符小于当前页第一个唯一标识符的记录两种查询都只返回记录的TOP页当浏览上一页时需要以降序为结果排序这将有效地返回查询的最后一页(如果需要显示之前也许要重新排序结果)
) 另一项每次只返回一页记录的技术是创建SQL语句将TOP谓词和嵌入式SELECT语句的使用结合在一起此技术并不依赖于存在一种可唯一标识每一行的办法使用这项技术的第一步是将所需页的数量与页大小相乘然后将结果传递给SQL Query的TOP谓词该查询以升序排列再将此查询嵌入到另一个查询中后者从降序排列的嵌入式查询结果中选择TOP页大小实质上返回的是嵌入式查询的最后一页例如要返回查询结果的第三页(页大小是)应该书写如下所示的命令
SELECT TOP * FROM
(SELECT TOP * FROM Customers ORDER BY Id ASC) AS Table
ORDER BY Id DESC
注意从查询中返回的结果页以降序显示如果需要应该重新排序
) 如果数据不经常变动可以在DataSet中本地维护一个记录缓存以此提高性能例如可以在本地DataSet中存储页有用的数据并且只有当用户浏览超出缓存第一页和最后一页时才从数据源中查询新数据
用架构填充DataSet 当用数据填充DataSet时DataAdapterFill方法使用DataSet的现有架构并使用从SelectCommand返回的数据填充它如果在DataSet中没有表名与要被填充的表名相匹配Fill方法就会创建一个表默认情况下Fill仅定义列和列类型
通过设置DataAdapter的MissingSchemaAction属性可以重写Fill的默认行为例如要让Fill创建一个表架构并且还包括主键信息唯一约束列属性是否允许为空最大列长度只读列和自动增量的列就要将DataAdapterMissingSchemaAction指定为MissingSchemaActionAddWithKey或者在调用DataAdapterFill前可以调用DataAdapterFillSchema来确保当填充DataSet时架构已到位
对FillSchema的调用会产生一个到服务器的额外行程用于检索附加架构信息为了获得最佳性能需要在调用Fill之前指定DataSet的架构或者设置DataAdapter的MissingSchemaAction
使用CommandBuilder的最佳实践 假设SelectCommand执行单一表SELECTCommandBuilder就会以DataAdapter的SelectCommand属性为基础自动生成DataAdapter的InsertCommandUpdateCommand和DeleteCommand属性下面是为获得最佳性能而使用CommandBuilder的一些技巧
) CommandBuilder的使用应该限制在设计时或即席方案中生成DataAdapter命令属性所必需的处理会影响性能如果预先知道INSERT/UPDATE/DELETE语句的内容就显式设置它们一个比较好的设计技巧是为INSERT/UPDATE/DELETE命令创建存储过程并显式配置DataAdapter命令属性以使用它们
) CommandBuilder使用DataAdapter的SelectCommand属性确定其他命令属性的值如果DataAdapter的SelectCommand本身曾经更改过确保调用RefreshSchema以更新命令属性
) 如果DataAdapter命令属性为空(命令属性默认情况下为空)CommandBuilder仅仅为它生成一条命令如果显式设置了命令属性CommandBuilder不会重写它如果希望CommandBuilder为以前已经设置过的命令属性生成命令就将命令属性设置为空
批处理SQL语句 很多数据库支持将多条命令合并或批处理成一条单一命令执行例如SQL Server使您可以用分号;分隔命令将多条命令合并成单一命令能减少到服务器的行程数并提高应用程序的性能例如可以将所有预定的删除在应用程序中本地存储起来然后再发出一条批处理命令调用从数据源删除它们
虽然这样做确实能提高性能但是当对DataSet中的数据更新进行管理时可能会增加应用程序的复杂性要保持简单可能要在DataSet中为每个DataTable创建一个DataAdapter
用多个表填充DataSet
如果使用批处理SQL语句检索多个表并填充DataSet第一个表用指定给Fill方法的表名命名后面的表用指定给Fill方法的表名加上一个从开始并且增量为的数字命名例如如果运行下面的代码
Visual Basic
Dim da As SqlDataAdapter = New SqlDataAdapter(SELECT * FROM Customers; SELECT * FROM Orders; myConnection)
Dim ds As DataSet = New DataSet()
daFill(ds Customers)
//C#
SqlDataAdapter da = new SqlDataAdapter(SELECT * FROM Customers; SELECT * FROM Orders; myConnection);
DataSet ds = new DataSet();
daFill(ds Customers);
来自Customers表的数据放在名为Customers的DataTable中来自Orders表的数据放在名为Customers的DataTable中
填充完DataSet之后可以很容易地将Customers表的TableName属性改为Orders但是后面的填充会导致Customers表被重新填充而Orders表会被忽略并创建另外一个Customers表为了对这种情况作出补救创建一个DataTableMapping将Customers映射到Orders并为其他后面的表创建其他的表映射例如
Visual Basic
Dim da As SqlDataAdapter = New SqlDataAdapter(SELECT * FROM Customers; SELECT * FROM Orders; myConnection)
daTableMappingsAdd(Customers Orders)
Dim ds As DataSet = New DataSet()
daFill(ds Customers)
//C#
SqlDataAdapter da = new SqlDataAdapter(SELECT * FROM Customers; SELECT * FROM Orders; myConnection);
daTableMappingsAdd(Customers Orders);
DataSet ds = new DataSet();
daFill(ds Customers);
使用DataReader 下面是一些使用DataReader获得最佳性能的技巧同时还回答了一些关于使用DataReader的常见问题
) 在访问相关Command的任何输出参数之前必须关闭DataReader
) 完成读数据之后总是要关闭DataReader如果使用Connection只是用于返回DataReader那么关闭DataReader之后立刻关闭它
另外一个显式关闭Connection的方法是将CommandBehaviorCloseConnection传递给ExecuteReader方法以确保相关的连接在关闭DataReader时被关闭如果从一个方法返回DataReader而且不能控制DataReader或相关连接的关闭则这样做特别有用
) 不能在层之间远程访问DataReaderDataReader是为已连接好的数据访问设计的
) 当访问列数据时使用类型化访问器例如GetStringGetInt等这使您不用进行将GetValue返回的Object强制转换成特定类型所需的处理
) 一个单一连接每次只能打开一个DataReader在ADO中如果打开一个单一连接并且请求两个使用只进只读游标的记录集那么ADO会在游标生存期内隐式打开第二个未池化的到数据存储区的连接然后再隐式关闭该连接对于ADONET秘密完成的动作很少如果想在相同的数据存储区上同时打开两个DataReaders就必须显式创建两个连接每个DataReader一个这是ADONET为池化连接的使用提供更多控制的一种方法
) 默认情况下DataReader每次Read时都要将整行加载到内存这允许在当前行内随机访问列如果不需要这种随机访问为了提高性能就将CommandBehaviorSequentialaccess传递给ExecuteReader调用这将DataReader的默认行为更改为仅在请求时将数据加载到内存注意CommandBehaviorSequentialAccess要求顺序访问返回的列也就是说一旦读过返回的列就不能再读它的值了
) 如果已经完成读取来自DataReader的数据但仍然有大量挂起的未读结果就在调用DataReader的Close之前先调用Command的Cancel调用DataReader的Close会导致在关闭游标之前检索挂起的结果并清空流调用Command的Cancel会放弃服务器上的结果这样DataReader在关闭的时候就不必读这些结果如果要从Command返回输出参数还要调用Cancel放弃它们如果需要读取任何输出参数不要调用Command的Cancel只要调用DataReader的Close即可
二进制大对象(BLOB) 用DataReader检索二进制大对象(BLOB)时应该将CommandBehaviorSequentialAccess传递给ExecuteReader方法调用因为DataReader的默认行为是每次Read都将整行加载到内存又因为BLOB值可能非常大所以结果可能由于单个BLOB而使大量内存被用光SequentialAccess将DataReader的行为设置为只加载请求的数据然后还可以使用GetBytes或GetChars控制每次加载多少数据
记住使用SequentialAccess时不能不按顺序访问DataReader返回的不同字段也就是说如果查询返回三列其中第三列是BLOB并且想访问前两列中的数据就必须在访问BLOB数据之前先访问第一列的值然后访问第二列的值这是因为现在数据是顺序返回的并且DataReader一旦读过该数据该数据就不再可用
使用命令 ADONET提供了几种命令执行的不同方法以及优化命令执行的不同选项下面包括一些技巧它们是关于选择最佳命令执行以及如何提高执行命令的性能
使用OleDbCommand的最佳实践
不同NET框架数据提供程序之间的命令执行被尽可能标准化了但是数据提供程序之间仍然存在差异下面给出一些技巧可微调用于OLE DB的NET框架数据提供程序的命令执行
) 按照ODBC CALL语法使用CommandTypeText调用存储过程使用CommandTypeStoredProcedure只是秘密地生成ODBC CALL语法
) 一定要设置OleDbParameter的类型大小(如果适用)以及精度和范围(如果参数类型是numeric或decimal)注意如果不显式提供参数信息OleDbCommand会为每个执行命令重新创建OLE DB参数访问器
使用SqlCommand的最佳实践
使用SqlCommand执行存储过程的快速提示如果调用存储过程将SqlCommand的CommandType属性指定为StoredProcedure的CommandType这样通过将该命令显式标识为存储过程就不需要在执行之前分析命令
使用Prepare方法
对于重复作用于数据源的参数化命令CommandPrepare方法能提高性能Prepare指示数据源为多次调用优化指定的命令要想有效利用Prepare需要彻底理解数据源是如何响应Prepare调用的对于一些数据源(例如SQL Server )命令是隐式优化的不必调用Prepare对于其他(例如SQL Server )数据源Prepare会比较有效
显式指定架构和元数据 只要用户没有指定元数据信息ADONET的许多对象就会推断元数据信息下面是一些示例
) DataAdapterFill方法如果DataSet中没有表和列DataAdapterFill方法会在DataSet中创建表和列
) CommandBuilder它会为单表SELECT命令生成DataAdapter命令属性
) CommandBuilderDeriveParameters它会填充Command对象的Parameters集合
但是每次用到这些特性都会有性能损失建议将这些特性主要用于设计时和即席应用程序中在可能的情况下显式指定架构和元数据其中包括在DataSet中定义表和列定义DataAdapter的Command属性以及为Command定义Parameter信息
ExecuteScalar和ExecuteNonQuery 如果想返回像Count(*)Sum(Price)或Avg(Quantity)的结果那样的单值可以使用CommandExecuteScalarExecuteScalar返回第一行第一列的值将结果集作为标量值返回因为单独一步就能完成所以ExecuteScalar不仅简化了代码还提高了性能要是使用DataReader就需要两步才能完成(即ExecuteReader+取值)
使用不返回行的SQL语句时例如修改数据(例如INSERTUPDATE或DELETE)或仅返回输出参数或返回值请使用ExecuteNonQuery这避免了用于创建空DataReader的任何不必要处理
测试Null
如果表(在数据库中)中的列允许为空就不能测试参数值是否等于空相反需要写一个WHERE子句测试列和参数是否都为空下面的SQL语句返回一些行它们的LastName列等于赋给@LastName参数的值或者LastName列和@LastName参数都为空
SELECT * FROM Customers
WHERE ((LastName = @LastName) OR (LastName IS NULL AND @LastName IS NULL))
将Null作为参数值传递 对数据库的命令中当将空值作为参数值发送时不能使用null(Visual Basic NET中为Nothing)而需要使用DBNullValue例如
Visual Basic
Dim param As SqlParameter = New SqlParameter(@Name SqlDbTypeNVarChar )
paramValue = DBNullValue
//C#
SqlParameter param = new SqlParameter(@Name SqlDbTypeNVarChar );
paramValue = DBNullValue;
执行事务
ADONET的事务模型已经更改在ADO中当调用StartTransaction时调用之后的任何更新操作都被视为是事务的一部分但是在ADONET中当调用Connection BeginTransaction时会返回一个Transaction对象需要将它与Command的Transaction属性联系起来这种设计可以在一个单一连接上执行多个根事务如果未将CommandTransaction属性设置为一个针对相关的Connection而启动的Transaction那么Command就会失败并引发异常
即将发布的NET框架将使您可以在现有的分布式事务中手动登记这对于对象池方案来说很理想在该方案中一个池对象打开一次连接但是在多个独立的事务中都涉及到该对象NET框架发行版中这一功能并不可用
使用连接 高性能应用程序与使用中的数据源保持最短时间的连接并且利用性能增强技术例如连接池下面的主题提供一些技巧有助于在使用ADONET连接到数据源时获得更好的性能
连接池 用于ODBC的SQL ServerOLE DB和NET框架数据提供程序隐式缓沖连接通过在连接字符串中指定不同的属性值可以控制连接池的行为
用DataAdapter优化连接 DataAdapter的Fill和Update方法在连接关闭的情况下自动打开为相关命令属性指定的连接如果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)
myConnectionOpen()
Dim myTrans As SqlTransaction = myConnectionBeginTransaction()
myCommandTransaction = myTrans
Try
daUpdate(ds)
myTransCommit()
ConsoleWriteLine(Update successful)
Catch e As Exception
Try
myTransRollback()
Catch ex As SqlException
If Not myTransConnection Is Nothing Then
ConsoleWriteLine(An exception of type & exGetType()ToString() & was encountered while attempting to roll back the transaction)
End If
End Try
ConsoleWriteLine(An exception of type & eGetType()ToString() & was encountered)
ConsoleWriteLine(Update failed)
End Try
myConnectionClose()
End Sub
//C#
public void RunSqlTransaction(SqlDataAdapter da SqlConnection myConnection DataSet ds)
{
myConnectionOpen();
SqlTransaction myTrans = myConnectionBeginTransaction();
myCommandTransaction = myTrans;
try
{
daUpdate(ds);
myCommandTransactionCommit();
ConsoleWriteLine(Update successful);
}
catch(Exception e)
{
try
{
myTransRollback();
}
catch (SqlException ex)
{
if (myTransConnection != null)
{
ConsoleWriteLine(An exception of type + exGetType() + was encountered while attempting to roll back the transaction);
}
}
ConsoleWriteLine(eToString());
ConsoleWriteLine(Update failed);
}
myConnectionClose();
}
始终关闭Connection和DataReader 完成对Connection或DataReader对象的使用后总是显式地关闭它们尽管垃圾回收最终会清除对象并因此释放连接和其他托管资源但垃圾回收仅在需要时执行因此确保任何宝贵的资源被显式释放仍然是您的责任并且没有显式关闭的Connections可能不会返回到池中例如一个超出作用范围却没有显式关闭的连接只有当连接池大小达到最大并且连接仍然有效时才会被返回到连接池中
注不要在类的Finalize方法中对ConnectionDataReader或任何其他托管对象调用Close或Dispose最后完成的时候仅释放类自己直接拥有的非托管资源如果类没有任何非托管资源就不要在类定义中包含Finalize方法
在C#中使用Using语句 对于C#程序员来说确保始终关闭Connection和DataReader对象的一个方便的方法就是使用using语句using语句在离开自己的作用范围时会自动调用被使用的对象的Dispose例如
//C#
string connString = Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;;
using (SqlConnection conn = new SqlConnection(connString))
{
SqlCommand cmd = connCreateCommand();
cmdCommandText = SELECT CustomerId CompanyName FROM Customers;
connOpen();
using (SqlDataReader dr = cmdExecuteReader())
{
while (drRead())
ConsoleWriteLine({}\t{} drGetString() drGetString());
}
}
Using语句不能用于Microsoft Visual Basic NET
避免访问OleDbConnectionState属性 如果连接已经打开OleDbConnectionState属性会对DBPROP_CONNECTIONSTATUS属性的DATASOURCEINFO属性集执行本地OLE DB调用IDBPropertiesGetProperties这可能会导致对数据源的往返行程也就是说检查State属性的代价可能很高所以仅在需要时检查State属性如果需要经常检查该属性监听OleDbConnection的StateChange事件可能会使应用程序的性能好一些
与XML集成 ADONET在DataSet中提供了广泛的XML集成并公开了SQL Server 及其更高版本提供的部分XML功能还可以使用SQLXML 广泛地访问SQL Server 及其更高版本中的XML功能下面是使用XML和ADONET的技巧和信息
DataSet和XML DataSet与XML紧密集成并提供如下功能
) 从XSD架构中加载DataSet的架构或关系型结构
) 从XML加载DataSet的内容
) 如果没有提供架构可以从XML文档的内容推断出DataSet的架构
) 将DataSet的架构写为XSD架构
) 将DataSet的内容写为XML
) 同步访问使用DataSet的数据的关系表示以及使用XmlDataDocument的数据的层次表示
注可以使用这种同步将XML功能(例如XPath查询和XSLT转换)应用到DataSet中的数据或者在保留原始XML保真度的前提下为XML文档中数据的全部或其中一个子集提供关系视图
架构推断 从XML文件加载DataSet时可以从XSD架构加载DataSet架构或者在加载数据前预定义表和列如果没有可用的XSD架构而且不知道为XML文件的内容定义哪些表和列就可以在XML文档结构的基础上对架构进行推断
架构推断作为迁移工具很有用但应只限于设计阶段应用程序这是由于推断处理有如下限制
) 对架构的推断会引入影响应用程序性能的附加处理
) 所有推断列的类型都是字符串
) 推断处理不具有确定性也就是说它是基于XML文件内容的而不是预定的架构因此对于两个预定架构相同的XML文件由于它们的内容不同结果得到两个完全不同的推断架构
用于XML查询的SQL Server
如果正从SQL Server FOR XML返回查询结果可以让用于SQL Server的NET框架数据提供程序使用SqlCommandExecuteXmlReader方法直接创建一个XmlReader
SQLXML托管类 NET框架中有一些类公开用于SQL Server 的XML的功能这些类可在MicrosoftDataSqlXml命名空间中找到它们添加了执行XPath查询和XML模板文件以及将XSLT转换应用到数据的能力
SQLXML托管类包含在用于Microsoft SQL Server 的XML (SQLXML )发行版中可通过链接XML for Microsoft SQL Server Web Release (SQLXML )
更多有用的技巧 下面是一些编写ADONET代码时的通用技巧
避免自动增量值沖突
就像大多数数据源一样DataSet使您可标识那些添加新行时自动对其值进行递增的列在DataSet中使用自动增量的列时如果自动增量的列来自数据源可避免添加到DataSet的行和添加到数据源的行之间本地编号沖突
例如考虑一个表它的主键列CustomerID是自动增量的两个新的客户信息行添加到表中并接收到自动增量的CustomerID值和然后只有第二个客户行被传递给DataAdapter的方法Update新添加的行在数据源接收到一个自动增量的CustomerID值与DataSet中的值不匹配当DataAdapter用返回值填充表中第二行时就会出现约束沖突因为第一个客户行已经使用了CustomerID值
要避免这种情况建议在使用数据源上自动增量的列以及DataSet上自动增量的列时将DataSet中的列创建为AutoIncrementStep值等于并且AutoIncrementSeed值等于另外还要确保数据源生成的自动增量标识值从开始并且以正阶值递增因此DataSet为自动增量值生成负数与数据源生成的正自动增量值不沖突另外一个选择是使用GUID类型的列而不是自动增量的列生成GUID值的算法应该永远不会使数据源中生成的GUID值与DataSet中生成的GUID值一样
如果自动增量的列只是用作唯一值而且没有任何意义就考虑使用GUID代替自动增量的列它们是唯一的并且避免了使用自动增量的列所必需的额外工作
检查开放式并发沖突 按照设计由于DataSet是与数据源断开的所以当多个客户端在数据源上按照开放式并发模型更新数据时需要确保应用程序避免沖突
在测试开放式并发沖突时有几项技术一项技术涉及在表中包含时间戳列另外一项技术是验证一行中所有列的原始值是否仍然与通过在SQL语句中使用WHERE子句进行测试时在数据库中找到的值相匹配
多线程编程 ADONET对性能吞吐量和可伸缩性进行优化因此ADONET对象不锁定资源并且必须只用于单线程一个例外是DataSet它对多个阅读器是线程安全的但是在写的时候需要将DataSet锁定
仅在需要的时候才用COM Interop访问ADO
ADONET的设计目的是成为许多应用程序的最佳解决方案但是有些应用程序需要只有使用ADO对象才有的功能例如ADO多维(ADOMD)在这些情况下应用程序可以用COM Interop访问ADO注意使用COM Interop访问具有ADO的数据会导致性能降低在设计应用程序时首先在实现用COM Interop访问ADO的设计之前先确定ADONET是否满足设计需求