1、译文: Visual Studio .NET 如何为并发控制生成 SQL 语句 作者:史蒂夫斯坦的 Visual Studio 团队时间: 2002 年 2 月 摘要:这篇文章研究 Visual Studio? .NET 为不同的并发控制方式所产生的 SQL语句,如何对它们进行修改可以提高执行效率,以及如何生成不带并发控制的SQL 语句。 引言 任何可能同时被多个用户访问或修改数据的应用程序,都需要进行并发控制。否则,一个用户更改记录时可能不经意的覆盖了其他用户的更改。 Visual Studio .NET 的设计工具可以生成“保持所有值”方式的开放式并发 SQL 语句或生成“最后的更新生效”
2、方式的 SQL 语句来更新数据。这篇文章将解释: * 不同的 SQL 语句是如何生成的 * 如何修改自动生成的 SQL 语句可以提高执行效率 阅读此文章时应具备的一些知识 你需要具备以下知识: * 基本的 ADO.NET 概念,包括数据集 (DataSet)以及数据适配器 (DataAdapters)。更多信息请参见 ADO.NET 数据访问介绍( Introduction to Data Access with ADO.NET)。 * 数据并发机制以及会操作 Visual Studio .NET。更多内容请参见介绍 ADO.NET 中的数据并发 (Introduction to Data C
3、oncurrency in ADO.NET)。 自动生成的 SQL 语句在哪里 自动生成的 SQL 语句在 command 对象的 CommandText 属性里。在设计阶段配置 DataAdapter 对象时或使用 CommandBuilder 对象时 SQL 命令被自动生成。更多信息,请参见并发与 CommandBuilder 对象( Concurrency and Command Builder Objects)。 配置 DataAdapter 对象 * 从工具箱的数据选项卡中拖一个 DataAdapter 对象 * 从服务器资源管理器拖一个数据表 * 选中已有的 DataAdapter
4、 对象,然后单击在属性窗口底部的“配置数据适配器”链接 CommandBuilder 对象 * CommandBuilder 对象在运行时刻被创建,更多信息请参阅 SqlCommandBuilder或 OleDbCommandBuilder。 并发控制与数据适配器 (DataAdapter) 使用“数据适配器配置向导”配置数据适配器时,你可以选择是否使用开放式并发来生 成 Update 和 Delete 语句。 一些思考和注意事项 * 你的数据源必须有一个主键才能以开放式并发方式生成 SQL 语句 * 当使用从“服务器资源管理器”拖放一个数据表的方式来创建 DataAdapter 对象时, D
5、ataAdapter 对象自动生成基于开放式并发的 Update 和 Delete 语句。如果你不想使用开放式并发,右击 DataAdapter 对象,从快捷菜单中选择“配置数据适配器”,然后在“高级 SQL 生成选项”对话框中清除“使用开放式并发”选项的选定。向导则会重新创建不带并发检测的 SQL 语句。 * 当重新配置现有的 DataAdapter 时,应注意“高级 SQL 生成选项”对话框里的选项已经全部恢复默认。例如最初配置 DataAdapter 时没有选定“使用开放式并发”选项,但是当重新配置 DataAdapter 时,“使用开放式并发”选项却会被选定,即便你根本没有打开过“高级
6、 SQL 生成选项”对话框。 * 如果你在“数据适配器配置向导”的“选择查询类型”页面选择“使用现有的存储过程”,则“使用开放式并发”选项将不可用。存储过程仍按其原来的方式执行。如果想使用并发检测的话,必须将其包括到存储过程中、或在你的应用程序中编写相应的代码。 * 当使用开放式并发来创建 SQL 命令时,不会对二进制数据列验证进行并发处理。这将导致用这种方法对大的二进制记录集执行按位比较算法时的效率低下。 用向导生成 SQL 语句 为了理解 Visual Studio .NET 如何使用开放式并发来生成 SQL 语句,让我们来看看用“数 据适配器配 置向导”生成的 Update 语句。我们将
7、查看同一条语句在选择“使用开放式并发”选项和不选择“使用开放式并发”选项时的不同状态。 你会注意到,选择开放式并发与不选择开放式并发所生成 SQL 语句的区别只存在于 Where 子句上。注:以下的例子使用用“数据适配器配置向导”生成的 Update 语句,并从 NorthWind 示例数据库的 Customers 表中选择了若干列。 使用开放式并发的 Update 语句 这个例子使用了“数据适配器配置向导”的默认配置,即选中了“使用开放式并发”选项。 注:当使用开放式并发时,生成的 command 的参数集里还存在一个参数副本。第二个参数集(带 Original_前缀的那个)保存了最初从数据
8、源里读取的值。 检查 Where 子句发现,每一个生成的语句都要检测数据库当前的值是否等于最初读取的值(例, WHERE City = Original_City)。通过数据库中的每个字段与最初读取的值相比较,我们很容易确定是否同时有其他用户修改了某个字段。如果 Where 子句不成立,就没有记录会被修改,与此同时还引发了一个“数据库并发”异常。如果数据源的某个字段为空值( NULL),生成的 SQL 语句同样验证最初读 取的记录是否也为空值。 UPDATE Customers SET CustomerID = CustomerID, CompanyName = CompanyName, Co
9、ntactName = ContactName, ContactTitle = ContactTitle, City = City WHERE (CustomerID = Original_CustomerID) AND (City = Original_City OR Original_City IS NULL AND City IS NULL) AND (CompanyName = Original_CompanyName) AND (ContactName = Original_ContactName OR Original_ContactName IS NULL AND Contact
10、Name IS NULL) AND (ContactTitle = Original_ContactTitle OR Original_ContactTitle IS NULL AND ContactTitle IS NULL); SELECT CustomerID, CompanyName, ContactName, ContactTitle, City FROM Customers WHERE (CustomerID = CustomerID) 不使用开放式并发的 Update 语句 这个例子更改了“数据适配器配置向导”的高级选项,没有选中“使用开放式并发”选项。 以 下 的 语 句 表
11、明 : 只 要 数 据 库 中 一 条 记 录 满 足 CustomerID = Original_CustomerID,则所有的字段都会被更新。不管这条记录现在是什么样的值,它都将被设置为通过 SQL 语句传递到数据源的值。在这里没有任何关于并发的检测,也无法得知是否同时有其它用户在更改这条记录。这种方式称为“最后的更新生效”方式。无论以前对这条记录进行过什么样的修改,更新操作都会执行。 UPDATE Customers SET CustomerID = CustomerID, CompanyName = CompanyName, ContactName = ContactName, Con
12、tactTitle = ContactTitle, City = City WHERE (CustomerID = Original_CustomerID); SELECT CustomerID, CompanyName, ContactName, ContactTitle, City FROM Customers WHERE (CustomerID = CustomerID) 优化生成的 SQL 语句 Visual Studio .NET 生成“保持所有值”方式的 SQL 语句来实现开放式并发。虽然这可能没 有生成最高效的 SQL 语 句,但是它的却生成了可以对数据源所有列(包括主键)进行并
13、发检测的 SQL 语句。使用“保持所有值”方式实现开放式并发,当执行效率非常低下时,你可以手工修改生成的 SQL 语句以使它们不检查数据源的所有列。最常见的方式是使用时间戳或版本号字段。如果你的数据源包含一个每次修改记录时都会更新的时间戳字段,你只需要验证数据源中的时间戳和你程序中的时间戳二者是否匹配,就可以知道是否同时有其他用户修改了记录。 下面这条 SQL 语句使用检查时间戳模式。 注:这个例子假设数据库已经设置了时间戳字段 UPDATE Customers SET CustomerID = CustomerID, CompanyName = CompanyName, ContactNam
14、e = ContactName, ContactTitle = ContactTitle, City = City WHERE (CustomerID = Original_CustomerID) AND (TimeStamp = Original_TimeStamp); SELECT CustomerID, CompanyName, ContactName, ContactTitle, City, TimeStamp FROM Customers WHERE (CustomerID = CustomerID) 并发与 CommandBuilder 对象 当应用程序使用 SqlCommandB
15、uilder 或者 OleDbCommandBuilder 时,生成的Update 和 Delete 语句的 CommandText 属性被自动的以开放式并发方式创建。如果你不想用开放式并发,则可以通过修改 DataAdapter 对象的 Update 和 Delete 命令的CommandText 属性 来实现。更多内容,请参见 OleDbCommand.CommandText 属性或 SqlCommand.CommandText 属性。 结论 当使用开放式并发的“保存所有值”方法时, SQL 语句在设计时由 Visual Studio .NET 的设计工具自动生成或在运行时由 CommandBuilde 自动生成。它把数据库所有字段(包括主键)当前值与初始值进行比较,这可能不是一种最高效