多活动结果集(Multiple Active Result Sets,简称MARS)是ADO.NET 2.0的一个新特征-它允许在单个连接上执行多重的数据库查询或存储过程。这样的结果是,你能够在单个连接上得到和管理多个、仅向前引用的、只读的结果集。在MARS以前,针对每个结果集需要一个独立的连接。当然,你还必须管理这些连接并且要付出相应的内存和潜在的应用程序中的高度拥挤的瓶颈代价-特别是在数据集中的Web应用程序中。
第一个支持MARS的商业数据库是SQL Server 2005,在本文中你将会详细了解怎样使用这一强有力的然而却是易于添加到ADO.NET开发中去的工具。
当在以前版本的SQL Server中存取数据结果集时,你需要创建额外的SqlConnection对象以配合使用SqlCommand。你需要追踪你的所有结果集及其导致低效率程序的SqlConnections连接以及"There is already an open DataReader associated with this Connection"错误。现在,使用了MARS技术后,所有这些都成了过去。
一、 准备工作
最开始,你需要设置一新的连接字符串以启动多个活动连接。自然地,我们把这个设置命名为"MultipleActiveResultSets",并且如下使用之:
String connectionString = "Data Source=DBSERVER;" +
"Initial Catalog=AdventureWorlds;IntegratedSecurity=SSPI;" +
"MultipleActiveResultSets=True";
它的默认设定是"False",并且你能明确地禁用它-如果你想传递"False"给这个MultipleActiveResultSets设定的话。
请遵循下面步骤来建立和启动MARS和ADO.NET 2.0:
1. 创建一个如上面所示的连接字符串。
2. 创建一个SqlConnection对象并且用该连接字符串初始化它。
3. 使用它的Open()方法打开这个SqlConnection对象。
4. 对于你想要执行的每个查询,创建一个新的SqlCommand对象。把它们与你在上面创建并打开的SqlConnection对象相关联。
5. 使用适当的命令(如,如果你想读该取查询结果的话,可以使用ExecuteReader();或使用ExecuteNonQuery()来进行更新,等等)来执行查询。
6. 完成后,关闭SqlConnection对象。
二、实战
在实际开发中普遍存在的一种典型的从数据库中读写数据的情形是,你可以使用多重连接而现在只用一个连接就足够了。例如,如果你有一些来自于几个表中的数据-它们不能被联结到一个查询中,那么你就会有多重的连接-每个连接都有一个与之相关连的命令用于读取数据。同样,如果你正在向一个表写数据,那么你需要另外一个连接或连接集合-如果有多个表要被更新的话。
可以考虑这样的情形-你需要读取来自两个查询A和B中的数据-从可能要写向表C的数据获取值,并且对你要写向表D的行为保持一个审计记录。在如此情形中,你的代码可能如下:
·为A打开一个连接
·执行查询A,并且填充一个数据集
·为A关闭一个连接
·为B打开一个连接
·执行查询B,并且填充一个数据集
·为B关闭一个连接
·为C打开一个连接
·为D打开一个连接
·用从A和B中取得的详细数据更新C
·用做过的审计标记更新D
·关闭D
·关闭C
这是相当复杂的!
当使用MARS时,你只要完成如下:
·用"MultipleActiveResultSets=true"打开连接
·执行A并且填充一个数据集
·执行B并且填充一个数据集
·用从A和B中取得的详细数据更新C
·用做过的审计标记更新D
·关闭连接
更简单!
三、基于MARS和C#的示例分析
本示例使用了随同SQL Server 2005一起发行的AdventureWorks示例数据库,并且使用了该数据库的开发版。注意,它还要改变该库的一些字段的内容,因此如果你想把这个示例数据库用于别的目的,请注意这一点。
本示例将展示怎样从数据库中读取一个SalesOrder,然后减少已卖出的项目的库存数额。典型地,这将要求建立到数据库的两个顺序连接-一个用于读取售出的项目数额,另一个用于使用减少的数额来更新库存。
下面的代码片断显示了怎样在不使用MARS功能的情况下达到这一目的。
ArrayList ids = new ArrayList();
ArrayList qtys = new ArrayList();
string connectionString = "Data Source=MEDIACENTER;" +
"Initial Catalog=AdventureWorks;Integrated Security=SSPI;" +
"MultipleActiveResultSets=False";
string strSQLGetOrder = "Select * from Sales.SalesOrderDetail" +
"WHERE SalesOrderID = 43659";
SqlConnection readConnection = new SqlConnection(connectionString);
readConnection.Open();
SqlCommand readCommand =new SqlCommand(strSQLGetOrder, readConnection);
using (SqlDataReader rdr = readCommand.ExecuteReader()){
while (rdr.Read()){
ids.Add(rdr["ProductID"]);
qtys.Add(rdr["OrderQty"]);
}
}
readConnection.Close();
string strSQLUpdateInv = "UPDATE Production.ProductInventory " +
"SET Quantity=Quantity-@amt WHERE (ProductID=@pid)";
SqlConnection writeConnection = new SqlConnection(connectionString);
writeConnection.Open();
SqlCommand writeCommand = new SqlCommand(strSQLUpdateInv,writeConnection);
writeCommand.Parameters.Add("@amt",SqlDbType.Int);
writeCommand.Parameters.Add("@pid",SqlDbType.Int);
for(int lp=0;lp<ids.Count;lp++){
writeCommand.Parameters["@amt"].Value=qtys[lp];
writeCommand.Parameters["@pid"].Value=ids[lp];
writeCommand.ExecuteNonQuery();
}
writeConnection.Close();
这个示例从数据库中读取单个销售订单(这里,订单号43659被硬编码)-该库中有一项目列表。这些项目应该从库存中扣除,而这是通过第二个连接完成的。然而,为了建立在第二个连接上的正确查询-从相应的产品中扣除正确的数量-要求第一个查询的结果在内存中被缓冲。并且在这个示例中这是通过使用两个数组列表来完成的。这里的明显耗费是:如果假定这是一个高度拥挤的网站,那么我们需要大量的缓冲内存来处理这些最终要被扔掉的数值。
为此,你还有另外一个方法-通过同时打开两个连接并且使用从一连接中读取的数据结果-该连接被直接传递到在第二个连接上的更新命令;但是仍存在在打开多个连接时内存和数据库方面的代价。典型地,数据库连接对于一个应用程序来说比内存具有更高的代价,所以这里使用了顺序连接方式。
MARS提供了解决这个问题的在以上两个方面均能达到最优的方法。你可以保持单个连接打开着,从而减少了到数据库的所有连接。这样以来,你就不需要用一个内存变量来存储读取的结果。
而且,该MARS代码也为更短并且因此更易于读取和维护。下面的代码片断展示了在相同的操作上使用MARS的情况:
string connectionString = "Data Source=MEDIACENTER;" +
"Initial Catalog=AdventureWorks;Integrated Security=SSPI;" +
"MultipleActiveResultSets=True";
string strSQLGetOrder = "Select * from Sales.SalesOrderDetail" +
"WHERE SalesOrderID = 43659";
string strSQLUpdateInv = "UPDATE Production.ProductInventory " +
"SET Quantity=Quantity-@amt WHERE (ProductID=@pid)";
SqlConnection marsConnection = new SqlConnection(connectionString);
marsConnection.Open();
SqlCommand readCommand = new SqlCommand(strSQLGetOrder, marsConnection);
SqlCommand writeCommand = new SqlCommand(strSQLUpdateInv, marsConnection);
writeCommand.Parameters.Add("@amt", SqlDbType.Int);
writeCommand.Parameters.Add("@pid", SqlDbType.Int);
using (SqlDataReader rdr = readCommand.ExecuteReader()){
while (rdr.Read()){
writeCommand.Parameters["@amt"].Value = rdr["OrderQty"];
writeCommand.Parameters["@pid"].Value = rdr["ProductID"];
writeCommand.ExecuteNonQuery();
}
}
marsConnection.Close();
正如你所见,这里的方式在内存和数据库连接方面比上一个示例更易于读取和管理且更为有效。并且,在这种情况中,只读取一次,随后跟着的是写操作;在某种典型情形下你的不使用MARS功能的代码有可能更为复杂,并因此使得MARS为你带来的节省更为明显。
尽管MARS便利了在同一个连接上的多重活动结果集的操作,但是在这些结果集上的操作仍然是串行运行的;如果你要求并行处理数据,那么多连接还是必需的。而且,请注意,一个使用了MARS功能的连接要比不使用的连接将利用更多些的资源。当然,从长远来看,你却节约了资源-由于你可以在同一个连接上执行多个命令;但是如果你在不需要的地方使用了MARS(也就是,如果你只需要单个结果集),你将会严重地影响系统性能。因此,如果你是在基于多数据库连接构建一个应用程序,那么你必须认真考虑哪些连接需要MARS,而哪些连接不需要-为了最大限度地利用资源。
总之,MARS是在ADO.NET中新增的一个非常优秀的功能-它允许你更高效地编写应用程序。欢迎使用MARS!
<script type=text/javascript> function ImgZoom(Id)//重新设置图片大小 防止撑破表格 { var w = $(Id).width; var m = 550; if(w < m) { return; } else { var h = $(Id).height; $(Id).height = parseInt(h*m/w); $(Id).width = m; } } window.onload = function() { var Imgs = $("content").getElementsByTagName("img"); var i=0; for(;i