本文,我将致力于在 Microsoft Visual Studio 2005 开发环境下使用这些类以及派生的类进行开发工作。对于类型化 DataSet 所作的更改和由 Visual Studio 2005 生成的新类型化 TableAdapter,是本文将要讨论的具体内容。除此之外,为开发以数据为中心的应用程序而使用的设计器和工具也是本文要讨论的内容,这些设计器和工具提供了很高的灵活性和工作效率。为了解释不同的概念和特性,我将逐步介绍开发人员在实现应用程序数据处理部分时通常要经历的过程。代码示例使用 Northwind 数据库,该数据库是 Microsoft SQL Server(以及 MSDE)7.0 版本和 Microsoft SQL Server 2000 自带的示例数据库。
Visual Studio 2005 引入了项目数据源的概念。数据源代表在应用程序中可以使用的数据。数据库不是这些数据的唯一来源,定义数据源所使用的 Data Source Configuration Wizard 答应您从三种不同的源获取数据:
1.
数据库 — 既可以是基于服务器的数据库(例如 SQL Server 或 Oracle),也可以是基于文件的数据库(例如 access 或 SQL Server EXPRess)。Visual Studio 自动生成类型化 DataSet 及其他类,并将它们添加到项目。
在 Visual Studio 主菜单中,选择 Data 菜单项中的 Show Data Sources,即可显示数据源窗口(在该窗口未显示出来的情况下)。
2.
在数据源窗口上,单击 Add New Data Source 工具栏按钮。该操作将启动 Data Source Configuration Wizard,该向导将 DataAdapter Configuration Wizard 的大部分功能和 Visual Studio 2002/2003 中的 DataSet 生成工具结合在一起。
3.
假如您的 Visual Studio 版本仍然包含向导的欢迎页面,请选择 Next。然后将显示 Choose a Data Source Type 页面。
向导中另一个增加的页面是 Choose Methods to Generate 页。在该页中可以选择您所定义的每个查询/命令方法名(一个或多个)。向导为每个命令都提供一个 Fill 和 Get 方法,如图 3 所示。Fill 要求提供需要填写数据的 DataTable,而 Get 方法将返回填写后新创建的 DataTable。
图 3. Choose Methods to Generate 页面中的 Fill 和 Get 方法
按照典型的用法,您可以为一个 TableAdapter 定义多个 Fill 命令,它将返回同一种架构(多个列),只是 WHERE 子句不同而已。这也是默认情况下向导会为方法名提供一个 FillBy 和 GetDataBy 前缀的原因。不过,您当然可以选择任何名字作为方法名。
虽然 TableAdapter 可以包含多个 Fill 命令,但在调用其中的 Update 方法时却只能执行一组更新命令。这组命令根据 TableAdapter 的主查询自动生成。第一次创建 TableAdapter 时定义的查询被认为是它的主查询。假如随后定义的任何查询返回的架构与主查询的架构不同,则设计器将显示一个消息框进行提醒。另一方面,假如您修改了主查询的架构,Visual Studio 将修改其他查询的架构以与之匹配。
通过 TableAdapter Configuration Wizard 添加新命令
现在,我们将另一个命令添加到 Orders 表的 TableAdapter。
1.
选择数据源窗口工具栏中的 Edit DataSet with Designer,打开 DataSet 设计器。
再选择右侧 Listbox 中的 FillByCustomer 方法。此时将显示该方法的代码,如下所示: Public Overloads Overridable Function FillByCustomer(ByVal dataTable As NorthwindDataSet.OrdersDataTable, ByVal CustID As String) As Integer Me.Adapter.SelectCommand = Me.CommandCollection(1) If (CustID Is Nothing) Then Throw New System.ArgumentNullException("CustID") Else Me.Adapter.SelectCommand.Parameters(0).Value = CType(CustID,String) End If If (Me.m_clearBeforeFill = true) Then dataTable.Clear End If Dim returnValue As Integer = Me.Adapter.Fill(dataTable) Return returnValue End Function 从该代码片断中我们可以了解几件事情。
在 Visual Studio 设计器中打开一个窗体时,Visual Studio 工具箱会出现一个以项目名为标签的选项卡。在项目中添加一个数据源并至少将其编译一次后,该选项卡中将出现创建的 DataSets 和 TableAdapters。您可以将这些组件拖放到窗体设计器上,在使用设计器实现数据访问组件的情况下,这是很好的做法,但通常情况下往往不这么做。而是使用以下三种不同方法中的一种来构建以数据为中心的窗体。首先,逐步介绍第一种方法,这种方法最简单,甚至可能最普遍。它就是所谓的“一次拖动”数据绑定。
1.
双击解决方案资源治理器中的 Form1.vb,打开 Visual Studio 窗体设计器中的 Form1。
这些文件都用于实现组成 DataSet 的类。使用两个文件是为了利用简单且功能非常强大的新特性 — partial classes。它是一个编译器功能,它答应在几个声明之间拆分类(或结构)的定义。不同的声明可能保存在不同的源代码文件中,只要声明都属于同一个程序集和命名空间就可以。Visual Studio 广泛采用了该功能,从而将由设计器生成的某个类的代码与开发人员编写的该类的代码区分开来。在 Visual Studio 2002/2003 中,所有窗体代码都是该窗体类声明的一部分,例如: Public Class Form1 Inherits System.Windows.Forms.Form 该窗体(包括窗体上放置的任何控件)的初始化代码由 Visual Studio 生成。这些代码位于 InitComponent() 方法中,默认情况下,该方法出现在名为“Windows 窗体设计器生成代码”的代码区域中用户编写的代码之前。该区域通常关闭,这是为了尽量避免与您编写的代码搞混而分散您的注重力。在 Visual Studio 2005 中,代码保存在 Form1.Designer.vb 这一完全不同的文件中,这将更有助于您集中注重力。此外,假如想查看该文件的内容,在该窗体中就可以 — 单击解决方案资源治理器工具栏上的 Show All Files,展开 Form1.vb 节点,然后双击 Form1.Designer.vb,代码编辑器中将显示代码。文件 Form1.vb 只包含您(作为开发人员)为 Form1 类编写的代码。
至于 DataSet 及其相关类的代码,使用部分类和不同文件区分设计器代码和开发人员编写的代码则具有更重大的意义。这种区分不止可使代码更整洁,而且解决了在 Visual Studio 2002/2003 中使用类型化 DataSets 时存在的一个主要问题。
通常,您还希望在为 DataSet 及其相关类自动生成的代码基础上进行扩展或添加,例如附加属性或自定义验证代码。那么,您可以放心地在生成代码中添加。只要不更改架构就可以,否则您需要重新生成 DataSet 代码。在 Visual Studio 2002/2003 中,由于代码和生成代码都添加在某一个文件中,因此重新生成代码时将清除添加的代码。正是由于使用了部分类,Visual Studio 2005 才得以避免出现这种情况。新生成的代码将覆盖现有的设计器代码并保存在以 .Designer.vb 为扩展名的文件中,而 .vb 文件中开发人员编写的代码仍然完整无缺。
要扩展使用部分类的 DataSet 功能,一种方法是添加自定义的验证代码。这时可以在生成的类型化 DataSet 中添加一些应用程序逻辑。我们将自定义的验证代码和初始化添加到 NorthwindDataSet 中 Orders 表内增加的新行中。添加一个新行时,我们希望检查传递的邮政编码值是否与实际传递的城市相匹配。假如不是,则将 ShipPostalCode 字段的值更改为 Invalid。假定实现了以下这样一个函数,该函数在给定的邮政编码与给定城市匹配的情况下返回 True: Function IsPostalCodeInCity (ByVal PostalCode as string, ByVal City as string) As Boolean 通过以下方式可将该检验条件添加到 NorthwindDataSet:
1.
选择数据源窗口工具栏中的 Edit DataSet with Designer,打开 DataSet 设计器。
2.
双击设计器背景的空白区域。在代码编辑器中打开文件 NorthwindDataSet.vb。
3.
输入以下代码来替换默认代码: Partial Public Class NorthwindDataSet Partial Class OrdersDataTable Protected Sub ValidateNewRow(ByVal sender As Object, _ ByVal e As System.Data.DataTableNewRowEventArgs) _ Handles Me.TableNewRow ' Create a strongly typed instance of the row ' This helps us avoid code in quotes, ' eg, e.Row("ShipPostalCode") Dim ordersRow As NorthwindDataSet.OrdersRow ordersRow = e.Row If Not ordersRow.IsShipPostalCodeNull And _ Not ordersRow.IsShipCityNull Then If Not IsPostalCodeInCity(ordersRow.ShipPostalCode, _ ordersRow.ShipCity) Then ' Set the value of the Ship Postal Code ordersRow.ShipPostalCode = "Invalid" ' Typically, changing a users data is a bad user experience ' So indicate an error with the ErrorProvider ' We are illustrating both approaches here ordersRow.SetColumnError( _ ShipPostalCodeColumn.ColumnName, "Invalid Postal Code") Else ' we always need to reset the error when the value is valid ordersRow.SetColumnError( _ ShipPostalCodeColumn.ColumnName, String.Empty) End If End If End Sub End Class Private Shared Function IsPostalCodeInCity(ByVal postalCode As String, _ ByVal city As String) As Boolean ' This is a stub, just to check functionality If city = "Rio de Janeiro" Then Return False Else Return True End If End Function End Class 注重,DataSet 的所有相关类(例如,OrdersDataTable)在其内部均作为嵌套类实现。前面代码中的部分类声明就反映了这一实现过程。