惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Engineering at Meta
Engineering at Meta
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Martin Fowler
Martin Fowler
雷峰网
雷峰网
Recent Announcements
Recent Announcements
博客园 - 叶小钗
F
Full Disclosure
C
Check Point Blog
A
About on SuperTechFans
L
LangChain Blog
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
博客园 - 司徒正美
C
Cybersecurity and Infrastructure Security Agency CISA
C
CXSECURITY Database RSS Feed - CXSecurity.com
Cisco Talos Blog
Cisco Talos Blog
Hugging Face - Blog
Hugging Face - Blog
Recorded Future
Recorded Future
MongoDB | Blog
MongoDB | Blog
Project Zero
Project Zero
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Know Your Adversary
Know Your Adversary
D
Docker
U
Unit 42
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Exploit Database - CXSecurity.com
L
Lohrmann on Cybersecurity
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Latest news
Latest news
V
Visual Studio Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LINUX DO - 最新话题
Google DeepMind News
Google DeepMind News
小众软件
小众软件
NISL@THU
NISL@THU
GbyAI
GbyAI
H
Heimdal Security Blog
Jina AI
Jina AI
I
InfoQ
PCI Perspectives
PCI Perspectives
P
Privacy International News Feed
P
Proofpoint News Feed
N
News and Events Feed by Topic
C
CERT Recently Published Vulnerability Notes
aimingoo的专栏
aimingoo的专栏
SecWiki News
SecWiki News
B
Blog RSS Feed
阮一峰的网络日志
阮一峰的网络日志

博客园 - 蓝箭GZ

[转载]AgileEAS.NET 敏捷软件开发平台 致力于中小企业快速应用开发 原创:分享通过简单设置提速3D游戏!(如果内存大,请禁用虚拟内存。) 原创:EXT2.0通过ASP动态生成json数据并分页! 原创:分享大内存的XP系统终极优化经验! 原创:VS2005开发的一些实用经验一(VB.NET--WINFORM) 转贴:Vbs脚本编程简明教程! 终于找到一款非常强大的程序代码编辑器PrimalScript v4.0.484汉化版! 空牙-游戏音乐合成视频 转:怀念街机的那段传奇历史 原创:ASP.NET2.0中使用AJAX1.0控件的一点经验 原创:基于VB的ASP.NET2.0的事务及错误处理 原创:Flash cs3 等软件不能在win2003sp1上安装的问题解决(安装遇到错误无法继续, 无法访问Windows Installer服务......) 原创:简单的ASP无组件上传原理实践总结! 转贴:检测系统颜色与使用字体 原创:我理解的ActiveX组件和对象 (转贴) Microsoft XML 分析器 (MSXML) 的版本列表 (转贴) Microsoft .NET Framework 的版本列表 关于网上的一篇很好的文章(RECORDSET对象详解)所存在的错误! (转贴) 数据库设计三大范式应用实例剖析
(转贴) ADO.NET的开发场景及传统ADO的处理
蓝箭GZ · 2008-02-13 · via 博客园 - 蓝箭GZ

 

作者: John Papa

出处: MSDN开发精选

当转为使用ADO.NET时,您将需要了解如何应对以前知道用ADO处理而现在必须用ADO.NET解决的场景。就像使用Visual BasicC++ASP开发的N层解决方案经常要依赖ADO来满足数据访问需要一样,Windows?窗体、Web窗体和Web服务也要依赖ADO.NET。我曾经从使用传统ADO开发的角度讨论了如何使用ADO.NET来处理一些数据访问的场景。其中的一些主题包括将行集保留为XML、处理只进流水游标和执行Command对象的多种方式。在文中,我将继续讨论使用ADO.NET的开发场景,以及使用传统的ADO技术是如何处理的。我将从经常使用的传统ADO的只进、静态、键集和动态游标的情况开始讨论。我还要讨论如何处理并发性问题,以及断开连接的行集如何从ADO演变到ADO.NET。然后,我会说明如何将使用传统的ADO处理批量更新的代码转换为使用ADO.NET(用DataAdapter及其四个命令对象)的代码。   分散recordset的功能  在ADO.NET中,ADO Recordset的大多数功能都分到了三个主要对象中:DataReaderDataSetDataAdapter(参见图1)。


  ADO.NET DataReader对象被设计成服务器端的只进、只读游标。ADO.NET DataSet对象是行集断开连接的存储工具。它存储记录,但不持有与数据源的连接,事实上,它并不关心其行集从哪个数据源派生。DataSet在内存中存储时是一个二进制对象,但是它可以轻松地从XML序列化和序列化为XML。这与ADO Recordset对象从其相关联的Connection对象断开连接(通过RecordsetActiveConnection属性)时将CursorType设置为adOpenStatic、将CursorLocation设置为adUseClient是类似的。ADO.NET DataAdapter对象是连接与DataSet对象之间的桥梁,它可以通过一个连接从数据源加载一个DataSet,并用DataSet中存储的已更改的内容更新数据源。ADO Recordset的行为取决于其属性的设置(包括CursorTypeCursorLocation属性)。在ADO.NET中构建了不同的对象来处理这些特定的情况,而无需用一个对象来应对所有情况。  流水游标  传统的ADO公开了四个不同类型的游标,可以改变ADO Recordset对象的运作方式。ADO Recordset对象的行为方式因其CursorType属性的设置情况互不相同,可能有非常大的差异。例如,通过将CursorType设置为adOpenForwardOnlyRecordset可保持与其数据源的连接,而且必须按只进方向进行遍历。然而,当您将CursorType属性设置为adOpenDynamic时,Recordset可向前或者向后遍历,甚至可以使游标跳到某个特定行。通过其CursorTypeCursorLocation属性,ADO Recordset对象采用了一种将许多解决方案包装到单个对象中的方式。而ADO.NET采用的方法则不相同,它设计由不同的对象和方法来处理各种特定情况。在传统的ADO中,只进、只读游标是通过将CursorType设置为adOpenForwardOnly、将CursorLocation设置为adUseServer(这也是默认设置)实现的,这将使Recordset对象采用只进的服务器端游标形式。MoveNext方法将Recordset重定位到下一行,而MovePrevious方法则根本不允许使用,但是可以调用RecordsetMoveFirst方法。这有些容易引起误解,因为该方法并不将Recordset重定位到当前行集的开始;相反,它调用原来的SQL语句,从头开始重新填充Recordset,因此会再次移动到第一个记录。每次执行传统ADO RecordsetMoveFirst方法时(CursorType设置为adOpenForwardOnly),通过打开SQL事件探查器工具,观察SQL的执行情况,可以很容易地看到这一点。在需要逐个遍历成千上万(乃至更多)行中的每行或者在需要小一些的行集但是只需遍历一次(可能为了加载到一个选取列表)时,这种游标是非常常用的:

'-- Forward-only Firehose Cursor in ASP and ADO
Set oRs.ActiveConnection = oCn
oRs.LockType = adLockReadOnly
oRs.CursorType = adOpenForwardOnly
oRs.CursorLocation = adUseServer
oRs.Open sSQL


  最接近这种传统的ADO流水游标的等效物是ADO.NET DataReader对象。和传统的只进ADO Recordset一样,DataReader在打开的同时保持着与其数据源的连接,而且只能以前进的方向进行遍历。但是,它们还是有区别的。区别之一是,专门为某个data provider(如SQL Server?SqlDataReader类)或者ODBC数据源(OdbcDataReader类))编写单独的DataReader类型。ADO.NET DataReader对象非常高效,这是因为它是专门为实现只进、只读游标这一目的而构建的。传统ADO的只进游标是通过相同的Recordset对象(作为一个断开连接的Recordset甚至是一个数据源敏感的Recordset)实现的。而DataReader只是为了作为轻型流水游标而设计的。

//-- ASP.NET and ADO.NET in C#
SqlDataReader oDr = oCmd.ExecuteReader();


  数据敏感游标  传统ADO的只进游标现在由DataReader对象处理。但是,对基础数据库中的更改非常敏感的键集和动态服务器端游标(CursorType = adOpenKeySetCursorType = adOpenDynamic)又怎么样呢?当前版本的ADO.NET并没有公开多方向、可滚动、可更新的服务器端游标。但是,ADO.NET确实提供了许多方式避免使用这些类型的服务器端游标,因为有其他推荐的技术可以考虑。除了使用DataSet结合DataAdapter来检索和更新数据以外,还有一些好的替代方案在客户端使用可滚动的服务器端游标。例如,可以使用存储过程,在运行服务器端SQL处理时这是非常高效的。还可以通过服务器端只进游标用DataReader检索数据,然后用Command对象分别进行更新。但是,经常有比使用可更新的服务器端游标更高效的修改数据的方式。  在一个N层环境中,服务器端可滚动游标的问题之一在于,它们需要在服务器上保持状态。因此,如果应用程序在中间层使用服务器端可滚动游标,客户端层就需要维持与可滚动游标所在的业务层的连接。这样,在同样使用可滚动游标的客户端应用程序屏幕存在期间,业务对象都需要保留。各种文档中已经很好地记录了这些可伸缩性问题,但是,在一些情况下服务器端游标还是很有价值的,如需要只进流水游标的情况。例如,在应用程序需要注重数据并发性问题的时候就要用到服务器端游标。使用传统的ADORecordset可以用一个动态游标打开,这种游标使Recordset能够获悉所有其他用户进行的插入、更改和删除操作。这种服务器端游标对于其他用户所做的更改非常敏感,可以在应用程序出现并发性问题的时候用于采取主动措施。例如,传统ADO中的动态游标经常用于这样的场合:应用程序准备在数据库中保存更改,但是又需要确保首先知道是否有其他人更改了同一条记录。当用户更改动态Recordset中一个记录值的时候,该值将自动在服务器端动态Recordset中更新:

'-- Dynamic Cursor in ASP and ADO
Set oRs.ActiveConnection = oCn
oRs.LockType = adLockOptimistic
oRs.CursorType = adOpenDynamic
oRs.CursorLocation = adUseServer
oRs.Open sSQL


服务器端游标与并发性  并发性问题在许多企业级应用程序中是合理而且常见的问题,但是使用服务器端游标以解决这一问题的代价却可能非常之高。那么在ADO.NET中有什么替代方式处理并发性问题呢?常见的技术是允许应用程序对数据库提交更改,然后在遇到并发性问题时引发特殊类型的异常。有一个特殊类型的异常名为DBConcurrencyException,它是从Exception基类派生而来的。因为DataSet将每列的原始值和拟更改值存储到一行中,它知道如何将值与数据库进行比较以自动寻找并发性问题。例如,假定用户将firstname字段的值由Lloyd更改为Lamar,并将更改提交给数据库,存储在DataSet中的更改将通过DataAdapterUpdate方法传递给数据库。在数据真正保存之前,如果数据库中的名字不再是Lloyd,现在变成了Lorenzo,将会引发一个DBConcurrencyException异常。此异常可以以最适合应用程序需要的任何方式进行捕获和处理,例如,可以采取最后者优先的规则,使用户可以选择取消更改、改写或者强制更新或其他技术。

public DataSet SaveData(DataSet oDs)
{
 string sMethodName = "[public void SaveData(DataSet oDs)]"; //==========================================================
 //-- Establish local variables
 //==========================================================
 string sProcName;
 string sConnString = "Server=(local);Database=Northwind;Integrated
 Security=SSPI";
 SqlDataAdapter oDa = new SqlDataAdapter();
 SqlTransaction oTrn = null;
 SqlConnection oCn = null;
 SqlCommand oInsCmd = null;
 SqlCommand oUpdCmd = null;
 SqlCommand oDelCmd = null; try
 {
  //==========================================================
  //-- Set up the Connection
  //==========================================================
  oCn = new SqlConnection(sConnString);  //==========================================================
  //-- Open the Connection and create the Transaction
  //==========================================================
  oCn.Open();
  oTrn = oCn.BeginTransaction();  //==========================================================
  //-- Set up the INSERT Command
  //==========================================================
  sProcName = "prInsert_Order";
  oInsCmd = new SqlCommand(sProcName, oCn, oTrn);
  oInsCmd.CommandType = CommandType.StoredProcedure;
  oInsCmd.Parameters.Add(new SqlParameter("@sCustomerID", SqlDbType.NChar, 5, "CustomerID"));
  oInsCmd.Parameters.Add(new SqlParameter("@dtOrderDate",
  SqlDbType.DateTime, 8,"OrderDate"));
  oInsCmd.Parameters.Add(new SqlParameter("@sShipCity",
  SqlDbType.NVarChar, 30, "ShipCity"));
  oInsCmd.Parameters.Add(new SqlParameter("@sShipCountry", SqlDbType.NVarChar, 30, "ShipCountry"));
  oDa.InsertCommand = oInsCmd;  //==========================================================
  //-- Set up the UPDATE Command
  //==========================================================
  sProcName = "prUpdate_Order";
  oUpdCmd = new SqlCommand(sProcName, oCn, oTrn);
  oUpdCmd.CommandType = CommandType.StoredProcedure;
  oUpdCmd.Parameters.Add(new SqlParameter("@nOrderID", SqlDbType.Int, 4, "OrderID"));
  oUpdCmd.Parameters.Add(new SqlParameter("@dtOrderDate", SqlDbType.DateTime, 8,"OrderDate"));
  oUpdCmd.Parameters.Add(new SqlParameter("@sShipCity", SqlDbType.NVarChar, 30, "ShipCity"));
  oUpdCmd.Parameters.Add(new SqlParameter("@sShipCountry", SqlDbType.NVarChar, 30, "ShipCountry"));
  oDa.UpdateCommand = oUpdCmd;  //==========================================================
  //-- Set up the DELETE Command
  //==========================================================
  sProcName = "prDelete_Order";
  oDelCmd = new SqlCommand(sProcName, oCn, oTrn);
  oDelCmd.CommandType = CommandType.StoredProcedure;
  oDelCmd.Parameters.Add(new SqlParameter("@nOrderID", SqlDbType.Int, 4, "OrderID"));
  oDa.DeleteCommand = oDelCmd;  //==========================================================
  //-- Save all changes to the database
  //==========================================================
  oDa.Update(oDs.Tables["Orders"]);  oTrn.Commit();
  oCn.Close();
 }
 catch (DBConcurrencyException exDBConcurrency)
 {
  //=======================================================
  //-- Roll back the transaction
  //=======================================================
  oTrn.Rollback();  //--------------------------------------------------------
  //-- May want to rethrow the Exception at this point.
  //-- This depends on how you want to handle the concurrency
  //-- issue.
  //--------------------------------------------------------
  //-- throw(exDBConcurrency);
 }
 catch (Exception ex)
 {
  //==========================================================
  //-- Roll back the transaction
  //==========================================================
  oTrn.Rollback();  //--------------------------------------------------------
  //-- Rethrow the Exception
  //--------------------------------------------------------
  throw;
 }
 finally
 {
  oInsCmd.Dispose();
  oUpdCmd.Dispose();
  oDelCmd.Dispose();
  oDa.Dispose();
  oTrn.Dispose();
  oCn.Dispose();
 }
 oCn.Close();
 return oDs;
}

             图2 捕获ADO.NET中的并发性问题  在图2 的代码中,可以注意到一个示例方法,它接收一个DataSet参数,并将DataSet中的更改通过DataAdapterUpdate方法提交给数据库。如果检测到并发性问题,将由一个特定的catch代码块引发和捕获DBConcurrencyException异常。此时,事务可以回滚,然后还可重新引发异常,直到最终异常被客户端应用程序捕获,从而得以通知用户并询问用户希望如何处理。这里同样要仔细地考虑catch代码块的顺序。例如,如果首先出现一般性的catch代码块,那么它已经捕获了并发性问题。异常处理的关键在于,要确保针对更特定异常的catch代码块在其他catch代码块之前出现,从而可由特定catch代码块首先捕获特定异常。
批量更新  ADO Recordset对象经常会用到的一种情形是进行批量更新。例如,在一个N层应用程序中,ADO Recordset可检索一个行集,将其从数据源中断开连接,并将Recordset发送到客户端层。在客户端应用程序中,对几行的更改将在断开连接的ADO Recordset中进行。然后Recordset可被发回给中间层,通过ADO Connection对象与数据库重新连接,其更改可通过UpdateBatch方法应用于数据库。UpdateBatch方法将接受Recordset中的所有被更改的行,并将更改应用于Recordset源查询中指示的源表。ADO.NET也有内置的处理批量更新的功能。DataSet是一个自成一体的、总是断开连接的行集集合,它可以存储行集中行与列的原始值和当前值。DataSet可以被发送给客户端应用程序,由后者进行更改操作。DataSet然后被发送到中间层,其更改将通过DataAdapter对象应用于数据库。图2说明了ADO.NET DataAdapter如何使用其不同的Command对象执行SELECTINSERTUPDATEDELETE SQL命令:

//-- Setting the ADO.NET DataAdapter's command objects
oDa.InsertCommand = oInsCmd;
oDa.UpdateCommand = oUpdCmd;
oDa.DeleteCommand = oDelCmd;
oDa.Update(oDs.Tables["Orders"]);


  ADO.NET技术在对数据库应用更新的方式上比传统的ADO批量更新技术更灵活。传统ADO是通过查看ADO RecordsetSource属性来确定保存更改的位置。例如,假定Recordset的源是:

SELECT OrderID, CustomerID, OrderDate, ShipCity, ShipCountry FROM Orders

  这种情况下,在对Recordset已经进行批量更改而且调用了RecordsetUpdateBatch方法之后,Recordset必须指向发送更改的位置。它查看源的SQL语句,并确定Orders表的主键(OrderID)在语句中而且只用到了一个表。因此,它可以使用SQL语句创建UPDATEINSERTDELETE语句对Orders表进行处理。为了能够派生操作查询,Recordset必须知道唯一行标识符。同样,存储过程不能用于这种技术更新数据库,因为语句是隐式创建的。以下ADO 2.x代码说明了包含OrdersRecordset如何在客户端应用程序中进行更新:

'-- Using traditional ADO,
'-- Updating two rows in the client tier
oRs.Find "CustomerID = 'VINET'"
oRs.Fields("ShipCity") = "Somewhere"
oRs.Update
oRs.Find "CustomerID = 'CHOPS'"
oRs.Fields("ShipCity") = "Elsewhere"
oRs.Update


  之后,一旦Recordset传回给中间层,将调用以下代码从而将已完成的更改应用于数据库:

'-- Using traditional ADO,
'-- Sending both updated rows to the database, in the business tier
oRs.UpdateBatch


  ADO.NET CommandBuilder的工作方式与此类似,因为它可以自动地为DataAdapter生成UpdateCommandInsertCommandDeleteCommand对象。自动生成命令的过程也会带来系统开销。显式地指定要使用的INSERTUPDATEDELETE语句效率更高。与显式指定命令相比,使用ADO.NET CommandBuilder意味着性能更低下,在对数据源应用更改方式上的控制也更差。准确的SELECTINSERTUPDATEDELETE语句通常在设计时是已知的,可以用来生成ADO.NET DataAdapter的四个不同Command属性,它们甚至可以用存储过程的名称表示。ADO.NET的这一功能是传统的ADO所缺乏的一个关键部分,这可以大大提高传统ADO的批量更新技术的灵活性。当然,这需要在设计时进行更多的编码工作,但为了获得它所提供的更大灵活性(可使您非常具体地指定SQL语句或更常见地用于为每个操作指定存储过程),付出这些努力也是值得的。  断开连接的行集  通过适当设置一些属性,ADO Recordset对象可以从其数据源中断开连接。通过断开连接,它能够在内存中存储整个行集,并在使用传统ADO的应用程序之间传递。以下代码示例将断开Recordset的连接:

'-- Disconnecting an ADO Recordset
oRs.CursorLocation = adUseClient
oRs.CursorType = adOpenStatic
oRs.LockType = adLockBatchOptimistic
oRs.Open
Set oRs.ActiveConnection = Nothing


  断开Recordset的连接涉及的关键属性是CursorTypeCursorLocationCursorLocation必须设置为adUseClient,表示行集应该存储在Recordset的内存中。CursorType应该设置为adOpenStatic,这将允许行集的游标能够在任何方向上移动,但是不允许行集自动对基础数据库的更改处于敏感状态。通过结合使用这两个设置,Recordset可以断开连接,但是,全部步骤还没有完。一旦打开Recordset,因为它真的断开连接了,那么它的ActiveConnection属性应该被设置为关键字Nothing,就像上一个代码示例中看到的那样。构建ADO Recordset是为了在断开连接和已连接这两种模式下工作。但是在ADO.NET中,当DataSet完全断开连接时,DataReader要维持连接。DataSet是断开连接的ADO Recordset的继承者,因为它实现了一个客户端游标,可以向任何方向滚动,而且ADO.NET DataSet还支持许多其他功能。传统的ADO Recordset对象提供的断开连接功能非常有限,而ADO.NET DataSet对象是专门为断开连接而设计的。与Recordset不同,DataSet可以在DataTable对象内存储和表示多个行集,甚至还可以使用DataRelation对象将它们互相联系起来。下面是使用SqlDataAdapterFill方法创建和填充DataSet的一种常见方式:

//-- Creating and filling an ADO.NET DataSet
DataSet oDs = new DataSet("MyDataSet");
oDa.Fill(oDs);


  DataSet可以施加关系、主键约束和外键约束,甚至实现列表达式。ADO Recordset还能够使用它的数据构形功能返回层次化的结果。虽然有这些层次化功能,但是它们需要使用我个人认为非常笨拙的数据构形语法。就此方面而言,ADO.NET DataSet更加高效也更加直观,因为它就是为使用其DataRelationDataTable对象处理关系数据结构而构建的。  在之前,我讨论了几个传统的ADO 2.x开发中常用的技术,以及如何将它们转换到ADO.NETADO.NETADO 2.x发展的下一阶段,许多传统ADO中的功能都将在ADO.NET中进行全面改造。例如,ADO 2.x中添加了OpenSave方法的功能以支持XML,而XML支持从一开始就已经被纳入到ADO.NET的设计蓝图中了。ADO的一些功能发生了很大变化,变化之一就是Recordset的许多游标类型被分为多个不同的各有侧重的对象,例如DataReaderDataSetDataAdapter。其他功能(例如ConnectionCommand类)的改变则不是那么明显。但是,说到底,从ADOADO.NET的迁移还是相对比较易懂的,这是因为ADO.NET的设计构筑于传统ADO的使用体验之上。