简介
本文为您提供了在 Microsoft ADONET 应用程序中实现和获得最佳性能可伸缩性以及功能的最佳解决方案同时也讲述了使用 ADONET 中可用对象的最佳实践并提出一些有助于优化 ADONET 应用程序设计的建议
本文包含
* 有关 NET 框架包含的 NET 框架数据提供程序的信息
* DataSet 和 DataReader 之间的比较以及这些对象中每个对象最佳用法的解释
* 解释如何使用 DataSetCommands 和 Connections
* 有关与 XML 集成的信息
* 通用的技巧和问题
使用 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 Studio NET 用于强类型 DataSet 语句结束所带来的好处修改了 DataSet 的架构或关系结构后就可以创建一个强类型 DataSet把行和列作为对象的属性公开而不是作为集合中的项公开例如不公开客户表中行的姓名列而公开 Customer 对象的 Name 属性类型化 DataSet 从 DataSet 类派生因此不会牺牲 DataSet 的任何功能也就是说类型化 DataSet 仍能远程访问并作为数据绑定控件(例如 DataGrid)的数据源提供如果架构事先不可知仍能受益于通用 DataSet 的功能但却不能受益于强类型 DataSet 的附加功能
处理强类型 DataSet 中的空引用
使用强类型 DataSet 时可以批注 DataSet 的 XML 架构定义语言 (XSD) 架构以确保强类型 DataSet 正确处理空引用nullValue 批注使您可用一个指定的值 StringEmpty 代替 DBNull保留空引用或引发异常选择哪个选项取决于应用程序的上下文默认情况下如果遇到空引用就会引发异常
分页
ADONET 可以显式控制从数据源中返回什么样的数据以及在 DataSet 中本地缓存多少数据对查询结果的分页没有唯一的答案但下面有一些设计应用程序时应该考虑的技巧
* 避免使用带有 startRecord 和 maxRecords 值的 DataAdapterFill 重载当以这种方式填充 DataSet 时只有 maxRecords 参数(从 startRecord 参数标识的记录开始)指定的记录数量用于填充 DataSet但无论如何总是返回完整的查询这就会引起不必要的处理用于读取不需要的记录而且为了返回附加记录会耗尽不必要的服务器资源
* 用于每次只返回一页记录的技术是创建 SQL 语句把 WHERE 子句以及 ORDER BY 子句和 TOP 谓词组合起来此技术取决于存在一种可唯一标识每一行的办法当浏览下一页记录时修改 WHERE 子句使之包含所有唯一标识符大于当前页最后一个唯一标识符的记录当浏览上一页记录时修改 WHERE 子句使之返回所有唯一标识符小于当前页第一个唯一标识符的记录两种查询都只返回记录的 TOP 页当浏览上一页时需要以降序为结果排序这将有效地返回查询的最后一页(如果需要显示之前也许要重新排序结果)有关这个技术的一个示例请参阅 Paging Through a Query Result
* 另一项每次只返回一页记录的技术是创建 SQL 语句把 TOP 谓词和嵌入式 SELECT 语句的使用结合在一起此技术并不依赖于存在一种可唯一标识每一行的办法使用这项技术的第一步是把所需页的数量与页大小相乘然后将结果传递给 SQL Query 的 TOP 谓词该查询以升序排列再把此查询嵌入到另一个查询中后者从降序排列的嵌入式查询结果中选择 TOP 页大小实质上返回的是嵌入式查询的最后一页例如要返回查询结果的第三页(页大小是 )应该书写如下所示的命令
SELECT TOP * FROM (SELECT TOP * FROM Customers ORDER BY Id ASC) AS TableORDER 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 方法的表名加上一个从 开始并且增量为 的数字命名例如如果运行下面的代码
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并为其他后面的表创建其他的表映射例如
代码
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 CustomersWHERE ((LastName = @LastName) OR (LastName IS NULL AND @LastName IS NULL))
把 Null 作为参数值传递
对数据库的命令中当把空值作为参数值发送时不能使用 null(Visual Basic庐 NET 中为 Nothing)而需要使用 DBNullValue例如
SqlParameter param = new SqlParameter(@Name SqlDbTypeNVarChar );
paramValue = DBNullValue;
执行事务
ADONET 的事务模型已经更改在 ADO 中当调用 StartTransaction 时调用之后的任何更新操作都被视为是事务的一部分但是在 ADONET 中当调用 ConnectionBeginTransaction 时会返回一个 Transaction 对象需要把它与 Command 的 Transaction 属性联系起来这种设计可以在一个单一连接上执行多个根事务如果未将 CommandTransaction 属性设置为一个针对相关的 Connection 而启动的 Transaction那么 Command 就会失败并引发异常
即将发布的 NET 框架将使您可以在现有的分布式事务中手动登记这对于对象池方案来说很理想在该方案中一个池对象打开一次连接但是在多个独立的事务中都涉及到该对象NET 框架 发行版中这一功能并不可用
使用连接
高性能应用程序与使用中的数据源保持最短时间的连接并且利用性能增强技术例如连接池下面的主题提供一些技巧有助于在使用 ADONET 连接到数据源时获得更好的性能
连接池
用于 ODBC 的 SQL ServerOLE DB 和 NET 框架数据提供程序隐式缓沖连接通过在连接字符串中指定不同的属性值可以控制连接池的行为有关如何控制连接池的行为的详细信息请参阅 Connection Pooling for the SQL Server NET Data Provider 和 Connection Pooling for the OLE DB NET Data Provider
用 DataAdapter 优化连接
DataAdapter 的 Fill 和 Update 方法在连接关闭的情况下自动打开为相关命令属性指定的连接如果 Fill 或 Update 方法打开了连接Fill 或 Update 将在操作完成的时候关闭它为了获得最佳性能仅在需要时将与数据库的连接保持为打开同时减少打开和关闭多操作连接的次数
如果只执行单个的 Fill 或 Update 方法调用建议允许 Fill 或 Update 方法隐式打开和关闭连接如果对 Fill 和/或 Update 调用有很多建议显式打开连接调用 Fill 和/或 Update然后显式关闭连接
另外当执行事务时显式地在开始事务之前打开连接并在提交之后关闭连接例如
代码
public void RunSqlTransaction(SqlDataAdapter da SqlConnection myConnection DataSet ds){ myConnectionOpen();
SqlTransaction myTrans = yConnectionBeginTransaction();
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例如
代码
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 ) 发行版中.
更多有用的技巧
下面是一些编写 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 是否满足设计需求