最近在闲来之余,想回顾一些基础知识,在SAHIL MALIK等人所著的Pro Ado.net2.0中提到了Ado.net2.0中的新东西包括以基础类为本(base-class-based)的数据源提供程序(provider)模型、异步访问架构、批处理更新与大量数据复制(bulk copy)、SQL Server 2005 的回调通知、单一连接同时多执行结果集(MARS)、执行统计、强化的 DataSet 类等。这些东西着实让人眼前一亮。不仅感叹MS的强大。但唯独有些遗憾的是有些功能,只有在结合SQL Server2005才能发挥出来,这又让人感叹MS的封闭。同时应该注意到要想有效发挥 SQL Server 2005 的功能,你只有结合ADO.NET 2.0 来开发,但对于oracle用户来说,确只能应Ado.net的一些基础应用了。让人很是不爽。今天简单介绍下同一连接下多数据结果集,不算是剖析,只能说是让你知道怎么用。到底怎样才能将其性能发挥出来,还有待大家来指教。
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
SqlCommand cmd1 = new SqlCommand("select * from Products ;select * from Orders", conn);
SqlDataReader rd = cmd1.ExecuteReader();
while (rd.Read())
{
GridView1.DataSource = rd;
GridView1.DataBind();
}
rd.NextResult();
while (rd.Read())
{
GridView2.DataSource = rd;
GridView2.DataBind();
}
conn.Close();
以往,在一个连接中只能执行一个reader否则会抛出异常,这种一个连接上产生多个结果集的方式,我总觉得是不是还有隐式的连接,很是不解,后查询msdn得到详细定义:多活动结果集 (MARS) 是一项允许对单个连接执行多个批处理的功能。在以前的版本中,在单个连接上一次只能执行一个批处理。使用 MARS 执行多个批处理并不意味着同时执行操作。
SQL Server 2005提供了在同一条连接上可以同时传递多个没有游标结构(cursorless)的结果集(也称为默认结果集),此功能称为 Multiple Active Resultsets(MARS)。如此可以节省需要同时打开的连接数,但要注意的是连接字符串设置要加上 MultipleAct-iveResultSets=true 属性,否则默认不启动多数据结果集的功能。默认情况下禁用 MARS 功能。可以通过在连接字符串中添加“MultipleActiveResultSets=True”关键字对来启用此功能。“True”是启用 MARS 的唯一有效值。以下示例演示如何连接到 SQL Server 实例以及如何指定应启用 MARS。
string connectionString = "Data Source=MSSQL1;" +
"Initial Catalog=AdventureWorks;Integrated Security=SSPI" +
"MultipleActiveResultSets=True";
可以通过在连接字符串中添加“MultipleActiveResultSets=False”关键字对来禁用此功能。“False”是禁用 MARS 的唯一有效值。以下连接字符串演示如何禁用 MARS。
通常情况下,现有的应用程序不需要修改,即可使用启用 MARS 的连接。但是,如果要在应用程序中使用 MARS 功能,应了解下列特殊注意事项。
MARS 操作在服务器上同步执行。允许 SELECT 和 BULK INSERT 语句的语句交替。但是,数据操作语言 (DML) 和数据定义语言 (DDL) 语句会自动执行。将阻止任何在执行原子批处理时尝试执行的语句。服务器上的并行执行不是 MARS 功能。
如果在 MARS 连接下提交两个批处理,其中一个批处理包含 SELECT 语句,另一个包含 DML 语句,DML 可以在 SELECT 语句执行过程中开始执行。但是,DML 语句必须运行完成,SELECT 语句才可以继续执行。如果两个语句在相同事务下运行,读取操作将看不到 DML 语句在 SELECT 语句开始执行后所作的任何更改。
SELECT 语句中的 WAITFOR 语句在等待时不生成事务,即直到生成第一行时才生成事务。这意味着在 WAITFOR 语句等待时,无法在相同连接内执行任何其他批处理。
如果打开启用了 MARS 的连接,将创建一个逻辑会话,这样会增加系统开销。为了使系统开销最小并提高性能,SqlClient 将 MARS 会话缓存在连接内。缓存最多可以包含 10 个 MARS 会话。用户不可调整此值。如果达到会话限制,将创建一个新会话 — 不会生成错误。缓存及其包含的会话针对特定连接;不在连接之间共享。会话释放后,除非已达到池的上限,否则,将返回池中。如果缓存池已满,会话将关闭。MARS 会话不会过期。只在连接对象断开后才进行清理。MARS 会话缓存不会预加载。如果应用程序需要更多的会话,将加载该会话。
MARS 操作不是线程安全的。
启用 MARS 的连接像任何其他连接一样建立池连接。如果应用程序打开两个连接,一个启用了 MARS,一个禁用了 MARS,这两个连接将位于独立的池中。有关更多信息,请参见SQL Server 连接池 (ADO.NET)。
打开连接时,将定义默认的环境。然后,将此环境复制到逻辑 MARS 会话中。
批处理执行环境包括下列组件:
设置选项(例如 ANSI_NULLS、DATE_FORMAT、LANGUAGE、TEXTSIZE)
安全上下文(用户/应用程序角色)
数据库上下文(当前数据库)
执行状态变量(例如 @@ERROR、@@ROWCOUNT、@@FETCH_STATUS @@IDENTITY)
顶级临时表
在 SQL Server 2000 以及更低版本中,在相同连接下执行的所有批处理将共享相同的批处理环境。所有后续的批处理可以看到通过批处理对批处理环境所作的更改。
使用 MARS,默认的执行环境将与连接关联。在给定连接下开始执行的每个新的批处理会接收默认环境的副本。只要代码在给定的批处理下执行,对环境所作的所有更改将作用于特定的批处理。执行完成后,执行设置将复制到默认环境中。如果单个批处理发出的多个命令要在相同事务下顺序执行,语义与通过与早期客户端或服务器有关的连接公开的语义相同。
使用 MARS 后,并非不再需要在应用程序中使用多个连接。如果应用程序需要对服务器真正地并行执行命令,应使用多个连接。
例如,考虑以下方案。创建了两个命令对象,一个用于处理结果集,另一个用于更新数据;这两个命令对象通过 MARS 共享公共连接。在此方案中,Transaction.Commit 在更新时失败,直到在第一个命令对象上读取了所有结果,并生成以下异常:
消息:其他会话正在使用事务的上下文。
可以通过三种方式处理此方案:
在创建读取器之后开始事务,使读取器不是事务的一部分。每次更新将变为读取器自己的事务。
在读取器关闭之后提交所有工作。对于大量的更新批处理,可能会这样做。
不使用 MARS;而是对每个命令对象使用独立的连接,就像在 MARS 之前一样。
应用程序可以通过读取 SqlConnection.ServerVersion 值来检查 MARS 支持。SQL Server 2005 的主版本号为 9。
该示例打开单个与 AdventureWorks 数据库的连接。 使用 SqlCommand 对象创建一个 SqlDataReader 对象。 在使用该读取器时,打开第二个 SqlDataReader,使用来自第一个 SqlDataReader 的数据作为第二个读取器的 WHERE 子句的输入。
以下示例使用随 SQL Server 2005 提供的示例 AdventureWorks 数据库。示例代码中提供的连接字符串假定数据库在本地计算机上已安装并且可用。 根据环境的需要修改连接字符串。
using System;
using System.Data;
using System.Data.SqlClient;
class Class1
{
static void Main()
{
// By default, MARS is disabled when connecting
// to a MARS-enabled host such as SQL Server 2005.
// It must be enabled in the connection string.
string connectionString = GetConnectionString();
int vendorID;
SqlDataReader productReader = null;
string vendorSQL =
"SELECT VendorId, Name FROM Purchasing.Vendor";
string productSQL =
"SELECT Production.Product.Name FROM Production.Product " +
"INNER JOIN Purchasing.ProductVendor " +
"ON Production.Product.ProductID = " +
"Purchasing.ProductVendor.ProductID " +
"WHERE Purchasing.ProductVendor.VendorID = @VendorId";
using (SqlConnection awConnection =
new SqlConnection(connectionString))
{
SqlCommand vendorCmd = new SqlCommand(vendorSQL, awConnection);
SqlCommand productCmd =
new SqlCommand(productSQL, awConnection);
productCmd.Parameters.Add("@VendorId", SqlDbType.Int);
awConnection.Open();
using (SqlDataReader vendorReader = vendorCmd.ExecuteReader())
{
while (vendorReader.Read())
{
Console.WriteLine(vendorReader["Name"]);
vendorID = (int)vendorReader["VendorId"];
productCmd.Parameters["@VendorId"].Value = vendorID;
// The following line of code requires
// a MARS-enabled connection.
productReader = productCmd.ExecuteReader();
using (productReader)
{
while (productReader.Read())
{
Console.WriteLine(" " +
productReader["Name"].ToString());
}
}
}
}
Console.WriteLine("Press any key to continue");
Console.ReadLine();
}
}
private static string GetConnectionString()
{
// To avoid storing the connection string in your code,
// you can retrive it from a configuration file.
return "Data Source=(local);Integrated Security=SSPI;" +
"Initial Catalog=AdventureWorks;MultipleActiveResultSets=True";
}
}
以下控制台应用程序演示如何对三个 SqlCommand 对象和单个启用了 MARS 的 SqlConnection 对象使用两个 SqlDataReader 对象。 第一个命令对象检索信用评级为 5 的供应商列表。第二个命令对象使用 SqlDataReader 提供的供应商 ID 为第二个 SqlDataReader 加载特定供应商的所有产品。 每个产品记录通过第二个 SqlDataReader 访问。 通过执行计算来确定新的 OnOrderQty。 然后,通过第三个命令对象来使用新值更新 ProductVendor 表。 整个过程在单个事务中进行,在结束时回滚。
以下示例使用随 SQL Server 2005 提供的示例 AdventureWorks 数据库。示例代码中提供的连接字符串假定数据库在本地计算机上已安装并且可用。 根据环境的需要修改连接字符串。
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
class Program
{
static void Main()
{
// By default, MARS is disabled when connecting
// to a MARS-enabled host such as SQL Server 2005.
// It must be enabled in the connection string.
string connectionString = GetConnectionString();
SqlTransaction updateTx = null;
SqlCommand vendorCmd = null;
SqlCommand prodVendCmd = null;
SqlCommand updateCmd = null;
SqlDataReader prodVendReader = null;
int vendorID = 0;
int productID = 0;
int minOrderQty = 0;
int maxOrderQty = 0;
int onOrderQty = 0;
int recordsUpdated = 0;
int totalRecordsUpdated = 0;
string vendorSQL =
"SELECT VendorID, Name FROM Purchasing.Vendor " +
"WHERE CreditRating = 5";
string prodVendSQL =
"SELECT ProductID, MaxOrderQty, MinOrderQty, OnOrderQty " +
"FROM Purchasing.ProductVendor " +
"WHERE VendorID = @VendorID";
string updateSQL =
"UPDATE Purchasing.ProductVendor " +
"SET OnOrderQty = @OrderQty " +
"WHERE ProductID = @ProductID AND VendorID = @VendorID";
using (SqlConnection awConnection =
new SqlConnection(connectionString))
{
awConnection.Open();
updateTx = awConnection.BeginTransaction();
vendorCmd = new SqlCommand(vendorSQL, awConnection);
vendorCmd.Transaction = updateTx;
prodVendCmd = new SqlCommand(prodVendSQL, awConnection);
prodVendCmd.Transaction = updateTx;
prodVendCmd.Parameters.Add("@VendorId", SqlDbType.Int);
updateCmd = new SqlCommand(updateSQL, awConnection);
updateCmd.Transaction = updateTx;
updateCmd.Parameters.Add("@OrderQty", SqlDbType.Int);
updateCmd.Parameters.Add("@ProductID", SqlDbType.Int);
updateCmd.Parameters.Add("@VendorID", SqlDbType.Int);
using (SqlDataReader vendorReader = vendorCmd.ExecuteReader())
{
while (vendorReader.Read())
{
Console.WriteLine(vendorReader["Name"]);
vendorID = (int) vendorReader["VendorID"];
prodVendCmd.Parameters["@VendorID"].Value = vendorID;
prodVendReader = prodVendCmd.ExecuteReader();
using (prodVendReader)
{
while (prodVendReader.Read())
{
productID = (int) prodVendReader["ProductID"];
if (prodVendReader["OnOrderQty"] == DBNull.Value)
{
minOrderQty = (int) prodVendReader["MinOrderQty"];
onOrderQty = minOrderQty;
}
else
{
maxOrderQty = (int) prodVendReader["MaxOrderQty"];
onOrderQty = (int)(maxOrderQty / 2);
}
updateCmd.Parameters["@OrderQty"].Value = onOrderQty;
updateCmd.Parameters["@ProductID"].Value = productID;
updateCmd.Parameters["@VendorID"].Value = vendorID;
recordsUpdated = updateCmd.ExecuteNonQuery();
totalRecordsUpdated += recordsUpdated;
}
}
}
}
Console.WriteLine("Total Records Updated: " +
totalRecordsUpdated.ToString());
updateTx.Rollback();
Console.WriteLine("Transaction Rolled Back");
}
Console.WriteLine("Press any key to continue");
Console.ReadLine();
}
private static string GetConnectionString()
{
// To avoid storing the connection string in your code,
// you can retrive it from a configuration file.
return "Data Source=(local);Integrated Security=SSPI;" +
"Initial Catalog=AdventureWorks;" +
"MultipleActiveResultSets=True";
}
}