ADO.NET的开发场景及传统ADO的处理
【IT168 技术文档】当转为使用ADO.NET时,您将需要了解如何应对以前知道用ADO处理而现在必须用ADO.net解决的场景。就像使用Visual Basic、C++和ASP开发的N层解决方案经常要依赖ADO来满足数据访问需要一样,Windows?窗体、Web窗体和Web服务也要依赖ADO.NET。我曾经从使用传统ADO开发的角度讨论了如何使用ADO.NET来处理一些数据访问的场景。其中的一些主题包括将行集保留为XML、处理只进流水游标和执行Command对象的多种方式。在文中,我将继续讨论使用ADO.NET的开发场景,以及使用传统的ADO技术是如何处理的。我将从经常使用的传统ADO的只进、静态、键集和动态游标的情况开始讨论。我还要讨论如何处理并发性问题,以及断开连接的行集如何从ADO演变到ADO.NET。然后,我会说明如何将使用传统的ADO处理批量更新的代码转换为使用ADO.NET(用DataAdapter及其四个命令对象)的代码。
传统的ADO公开了四个不同类型的游标,可以改变ADO Recordset对象的运作方式。ADO Recordset对象的行为方式因其CursorType属性的设置情况互不相同,可能有非常大的差异。例如,通过将CursorType设置为adOpenForwardOnly,Recordset可保持与其数据源的连接,而且必须按只进方向进行遍历。然而,当您将CursorType属性设置为adOpenDynamic时,Recordset可向前或者向后遍历,甚至可以使游标跳到某个特定行。通过其CursorType和CursorLocation属性,ADO Recordset对象采用了一种将许多解决方案包装到单个对象中的方式。而ADO.NET采用的方法则不相同,它设计由不同的对象和方法来处理各种特定情况。在传统的ADO中,只进、只读游标是通过将CursorType设置为adOpenForwardOnly、将CursorLocation设置为adUseServer(这也是默认设置)实现的,这将使Recordset对象采用只进的服务器端游标形式。MoveNext方法将Recordset重定位到下一行,而MovePrevious方法则根本不允许使用,但是可以调用Recordset的MoveFirst方法。这有些容易引起误解,因为该方法并不将Recordset重定位到当前行集的开始;相反,它调用原来的SQL语句,从头开始重新填充Recordset,因此会再次移动到第一个记录。每次执行传统ADO Recordset的MoveFirst方法时(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
//-- ASP.NET and ADO.NET in C# SqlDataReader oDr = oCmd.ExecuteReader();
'-- 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中的更改将通过DataAdapter的Update方法传递给数据库。在数据真正保存之前,如果数据库中的名字不再是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;
}
'-- Using traditional ADO, '-- Sending both updated rows to the database, in the business tier oRs.UpdateBatch
'-- Disconnecting an ADO Recordset oRs.CursorLocation = adUseClient oRs.CursorType = adOpenStatic oRs.LockType = adLockBatchOptimistic oRs.Open Set oRs.ActiveConnection = Nothing
//-- Creating and filling an ADO.NET DataSet DataSet oDs = new DataSet("MyDataSet"); oDa.Fill(oDs);