读书笔记:.NET数据存取架构(4)

14:通过数据集执行数据库更新

在引入 ADO.NET 以后,执行数据库更新的体系结构已经显著改变。ADO.NET 的目标是使得开发能够适应大型数据库和大量客户端的多层应用程序变得更加容易。这已经产生了一些重要的结果,尤其是:

ADO.NET 应用程序通常将客户端上的应用程序逻辑与中间层和数据库层上的业务和数据完整性计算分开。实际上,这意味着典型的应用程序将具有较多的批处理或事务处理性质,而在客户端应用程序和数据库之间具有较少的(但较大的)交互。

ADO.NET 应用程序对于究竟如何 处理更新具有更多的控制(与 ADO 及其前身比较)。

ADO.NET 允许应用程序通过存储在后端数据库中的存储过程来传播更改,而不是直接操纵数据库表中的行。这是推荐的实施策略。

更新使用模式

使用 ADO.NET 从 DataSet 中更新数据的过程可以按如下方式加以概述:

1.

创建一个 DataAdapter 对象,并且使用数据库查询的结果填充 DataSet 对象。数据将在本地缓存。

2.

对本地 DataSet 对象进行更改。这些更改可以包括对本地缓存的 DataSet 中的一个或多个表执行更新、删除和插入操作。

3.

初始化 DataAdapter 的与更新相关的属性。此步骤配置处理更新、删除或插入的确切方式。因为有多种处理该问题的方法,下面的“初始化 DataAdapter 以进行更新”部分对推荐方法进行了讨论。

4.

调用 DataAdapter Update 方法以提交挂起的更改。本地缓存的 DataSet 中的每个已更改的记录都将被处理。(未更改的记录将被 Update 方法自动忽略。)

5.

处理由 DataAdapter Update 方法引发的异常。当无法在数据库中进行请求的更改时,将引发异常。

(还有另外一种执行更新的方法。可以使用 ExecuteNonQuery 方法直接执行 SQL 更新查询。当您希望以编程方式更新特定的行并且不使用 DataSet 对象时,适合使用该技术。)

初始化 DataAdapter 以进行更新

在 ADO.NET 中,必须添加您自己的代码来向 DataAdapter 对象提交数据库更新。有三种完成该任务的方法:

可以提供您自己的更新逻辑。

可以使用“数据适配器配置向导”生成更新逻辑。

可以使用 CommandBuilder 对象生成更新逻辑。

建议您提供自己的更新逻辑。要节省时间,您可以使用“数据适配器配置向导”,但如果您确实使用了该向导,请尽量不要在运行时生成更新逻辑。除非迫不得已,否则请不要依赖于 CommandBuilder 对象,因为它会导致性能下降,并且您无法控制该对象生成的更新逻辑。此外,CommandBuilder 不会帮助您使用存储过程来提交更新。

对于动态生成数据访问逻辑的应用程序,如报告工具或数据提取工具,您可以使用 CommandBuilder。使用 CommandBuilder 可免除这些工具编写其自身的代码生成模块的需要。

使用存储过程

通过使用存储过程来进行更新,可以使数据库管理员实现比使用动态 SQL 时粒度更低的安全性,以及更完善的数据完整性检查。例如,存储过程除了执行请求的更新以外,还可以向审核日志中插入一项。因为数据库内部对存储过程执行脱机查询优化,所以存储过程还能够提供最佳性能。最后,因为存储过程在数据库结构和应用程序之间提供了隔离,所以更加易于维护。

因为使用存储过程的 ADO.NET 应用程序提供了许多好处,并且不比那些直接对数据库进行更改的应用程序更难实现,所以建议在几乎所有情况下都使用该方法。例外情况是当您必须使用多个后端或不支持存储过程的数据库(如 Microsoft Access)时。在这些情况下,请使用基于查询的更新。

管理并发

DataSet 对象的目的是鼓励对长期运行的活动(例如,当您远程处理数据时以及当用户与数据进行交互时)使用开放式并发。在从 DataSet 向数据库服务器提交更新时,有四种管理开放式并发的主要方法:

仅包括主键列

包括 WHERE 子句中的所有列

包括唯一键列和时间戳列

包括唯一键列和已修改的列

注意,后三种方法维护了数据完整性;第一种方法则没有。

仅包括主键列

该选项造成了这样一种情况,即最后的更新覆盖了所有以前的更改。CommandBuilder 不支持此选项,而“数据适配器配置向导”却支持。要使用此选项,请转至 AdvancedOptions 选项卡并清除 UseConcurrency 复选框。

该方法不是推荐的实施策略,因为它允许用户无意中改写其他用户的更改。损害其他用户更新的完整性永远是不可取的。(该技术仅适用于单用户数据库。)

包括 WHERE 子句中的所有列

使用该选项,可防止您改写由其他用户在您的代码取出行和您的代码提交该行中挂起的更改之间这段时间内进行的更改。该选项是“数据适配器配置向导”与 SqlCommandBuilder 生成的 SQL 代码二者的默认行为。

由于以下原因,该方法不是推荐的实施策略:

如果向表中添加了额外列,查询将需要修改。

通常,数据库不让您比较两个 BLOB 值,因为它们太大,以至于这样的比较效率很低。(像 CommandBuilder 与“数据适配器配置向导”这样的工具不应该在 WHERE 子句中包含 BLOB 列。)

将表中的所有列与已更新行中的所有列进行比较时,会产生额外的开销。

包括唯一键列和时间戳列

使用该选项,数据库会在每次更新行后,将时间戳列更新为唯一值。(您必须在表中提供时间戳列。)目前,CommandBuilder 与“数据适配器配置向导”都不支持该选项。

包括唯一键列和已修改的列

通常,不推荐使用该选项,因为如果您的应用程序逻辑依赖于过期的数据字段甚至它不更新的字段,则可能会产生错误。例如,如果用户甲更改了订单数量,而用户乙更改了单价,则可能计算出错误的订单总值(数量乘以价格)。

正确地更新空字段

当数据库中的字段不包含数据值时,通常可以方便地将这些空字段视为包含特殊的空值。然而,这一心理却可能是编程错误的根源,因为数据库标准要求对空值进行特殊处理。

空字段的核心问题在于:当两个操作数都为空值或其中一个为空值时,普通的 SQL = 运算符总是返回 false。在 SQL 查询中,运算符 ISNULL 是检查是否存在空字段的唯一正确方法。

如果应用程序通过指定 WHERE 子句,使用上面介绍的技术来管理并发,则您必须在字段可能为空的任何地方包括显式的 IS NULL 表达式。例如,如果 OldLastName 为空,下面的查询将总是失败:

SET LastName = @NewLastName WHERE StudentID = @StudentID AND 
                                  LastName = @OldLastName

应该按如下方式重写该查询:

SET LastName = @NewLastName WHERE (StudentID = @StudentID) AND
                                  ((LastName = @OldLastName) OR
                                   (OldLastName IS NULL AND LastName IS NULL))

要了解如何编写上述种类的更新逻辑,一种好方法是阅读 CommandBuilder 工具生成的输出。

更多信息

有关数据库更新的完整论述,请参阅 David Sceppa 的《Microsoft ADO.NET》(Microsoft Press, 2002) 第 11 和 12 章。

使用强类型数据集对象

强类型 DataSet 对象将数据库表和列呈现为对象和属性。访问是按名称执行的,而不是通过对集合进行索引来执行的。这意味着您可以使用访问字段的方法来识别强类型和非类型化 DataSet 对象之间的区别:

string n1 = myDataSet.Tables["Students"].Rows[0]["StudentName"];  // untyped
string n2 = myDataSet.Students[0].StudentName;           // strongly typed

使用强类型 DataSet 对象有以下几点好处:

访问字段所需的代码可读性更高、更加简洁。

Visual Studio .NET 代码编辑器中的智能感知功能可以在您键入时自动完成代码行。

编译器可以捕获强类型 DataSet 类型不匹配错误。在编译时检测类型错误要比在运行时检测更好。

何时使用强类型数据集

强类型 DataSet 很有用,因为它们使应用程序开发变得更加容易并且更少出错误。对于多层应用程序的客户端而言尤其如此,在此类客户端上,重点在于需要进行多字段访问操作的图形用户界面和数据验证。

不过,如果数据库结构改变(例如,当字段名和表名被修改时),则强类型 DataSet 可能会很麻烦。在此情况下,必须重新生成类型化 DataSet 类,并且必须修改所有相关类。

可以在同一应用程序中使用强类型方法和非类型化方法。例如,一些开发人员在客户端使用强类型 DataSet,在服务器上使用非类型化记录。强类型 DataSet.Merge 方法可用来从非类型化 DataSet 中导入数据。

生成 DataSet 类

.NET 框架 SDK 和 Visual Studio.NET 都提供了实用工具,帮助您生成必要的 DataSet 子类。.NET 框架 SDK 涉及到使用命令行工具和编写代码。很显然,Visual Studio .NET 方法依赖于 Visual Studio .NET 开发环境,并且不要求您打开命令窗口。

无论如何生成 DataSet 类,都必须将新类部署到所有引用该类型化 DataSet 的层上。(这种情形不太常见,但如果通过使用远程处理技术在多个层中传递类型化 DataSet,则需要考虑这种情况。)

使用 .NET 框架实用工具

.NET 框架 SDK 包含一个称为 XML 架构定义工具的命令行实用工具,可帮助您基于 XML 架构 (.xsd) 文件生成类文件。请将该实用工具与 DataSet 对象的 WriteXmlSchema 方法结合使用,以将非类型化 DataSet 转化为强类型 DataSet

以下命令阐明了如何从 XML 架构文件生成类文件。打开一个命令窗口,并键入以下内容:

C:\>xsd MyNewClass.xsd /d

该命令中的第一个参数是 XML 架构文件的路径。第二个参数表示要创建的类派生于 DataSet 类。默认情况下,该工具会生成 Visual C# .NET 类文件,但它还可以通过添加适当的选项来生成 Visual Basic .NET 类文件。要列出该工具的可用选项,请键入以下内容:

xsd /?

在创建了新的类文件以后,请将其添加到项目中。现在,可以创建强类型 DataSet 类的实例,如下面的 Visual C# .NET 代码片段所示:

MyNewClass ds = new MyNewClass();

使用 Visual Studio .NET

要在 Visual Studio .NET 中生成强类型 DataSet,请右键单击窗体设计器窗口,然后单击 Generate Dataset。这将创建一个 .xsd(XML 架构定义)文件以及一个类文件,然后将其添加到项目中。在执行此操作之前,请确保已经将一个或多个 DataAdapter 添加到 Windows 窗体中。注意,类文件是隐藏的。要查看该文件,请单击位于解决方案资源管理器窗口工具栏中的 Show All Files 按钮。该类文件与 .xsd 文件相关联。

要向强类型 DataSet 添加关系,请通过双击解决方案资源管理器窗口中的架构文件打开 XML 架构设计器,然后右键单击您要向其添加约束的表。在快捷菜单上,单击 Add New Relation

在 Visual Studio .NET 中生成强类型 DataSet 的另一种方法是,右键单击项目资源管理器中的项目,选择 Add Files,然后选择 dataset。将创建一个新的 .xsd 文件。此时您可以使用服务器资源管理器连接到数据库,并将表拖到 xsd 文件上。

处理空数据字段

以下是几点帮助您在 .NET 数据体系结构中正确使用空字段值的提示:

始终使用 System.DBNull 类设置空字段的值。不要使用由 C# 或 Visual Basic .NET 提供的空值。例如:

rowStudents["Nickname"] = DBNull.Value   // correct!

强类型 DataSet 包含两个针对 DataRow 执行操作的附加方法:一个用于检查列是否含有空值,另一个用于将列值设置为空。以下代码片段显示了这两个方法:

If (tds.rowStudent[0].IsPhoneNoNull()) {a€_.}
tds.rowStudent[0].SetPhoneNoNull()

请始终使用 DataRow 类(或在上一个项目符号中给出的强类型等效类)的 IsNull 方法来测试数据库中的空值。该方法是测试数据库空值的唯一受支持的方式。

如果数据字段可能包含空值,请确保在需要非空值的上下文中使用该值之前,对其进行测试(通过 IsNull 方法)。这方面的一个典型示例是可能为空的 Integer 值数据字段。注意,.NET 运行时 Integer 数据类型不包含空值。以下为一个示例:

int i = rowStudent["ZipCode"];         // throws exception if null!

使用强类型 DataSet .xsd 文件的 nullValue 批注来配置如何映射数据库中的空值。默认情况下会引发异常;然而,为了获得更高粒度的控制,可以将该类配置为使用指定的值(如 String.Empty)来替换空值。

posted on 2004-06-28 13:44  木人(我现在不是老大)  阅读(881)  评论(0)    收藏  举报

导航