摘要:了解 Microsoft Visual Studio 2005 生成的类型化的 DataSet 类和新的 TableAdapter 类中的新功能以及设计这些类的工具。还学习新的 BindingSource 和 BindingNavigator 组件,并了解如何使用这些组件快速创建灵活、数据绑定的 WinForm 应用程序。(本文包含一些指向英文站点的链接。)
除非另有说明,否则本文的内容基于 Visual Studio 2005 的 2004 年 12 月的 CTP (社区技术预览)版。
本文中讨论的示例代码可从此处下载。
本页内容
引言
数据源
创建以数据为中心的表单
自定义生成的代码
小结
引言
在上一篇文章 ADO.NET 2.0 中的新增 DataSet 功能中,我介绍了即将对 ADO.NET 的 DataSet 类及相关类(例如 DataSet、DataTable 和 DataView)进行的更改及改进。所有这些类都是 Microsoft .NET Framework 基类库的一部分。
在本文中,我将重点介绍从 Microsoft Visual Studio 2005 开发环境中使用这些类以及衍生类进行开发的问题。本文特别讨论了对 Visual Studio 2005 生成的类型化的 DataSet 类和新的类型化的 TableAdapter 类所做的更改。本文还介绍了一些设计器和工具,它们为开发以数据为中心的应用程序功能提供了极大的灵活性和效率。为了解释不同的概念和功能,我将逐步介绍开发人员在实现应用程序的数据部分时通常会经历的过程。本文中的代码示例使用 Northwind 数据库,该数据库是 Microsoft SQL Server(及 MSDE)7.0 和 2000 附带的示例数据库。
数据源
Visual Studio 2005 引入了项目的数据源概念。数据源表示可供应用程序使用的数据。这些数据不一定来自数据库,用来定义数据源的“数据源配置向导”允许您从三个不同的来源获得数据:
-
数据库 - 可以是基于服务器的数据库,例如 SQL Server 或 Oracle;也可以是基于文件的数据库,例如 Access 或 SQL Server Express。Visual Studio 可以自动生成类型化的 DataSet 和其他类,并将它们添加到您的项目中。
-
对象 - 具有公共属性的任何对象都可以作为数据源,不需要实现任何特殊的接口。
-
Web 服务 - 通过 Web 服务创建数据源将创建一些与 Web 服务返回的数据类型相对应的对象。
数据源的用途包括两方面。首先,它作为一种方法,使指定、设计和生成强类型的类(用于表示应用程序的数据)变得更容易。其次,它提供了一种灵活但统一的机制,可以快速创建丰富且功能强大的 WinForm 和 WebForm 用户界面。阅读本文后,就会了解此过程是多么快速、简单和灵活。
在本文中,我们将重点介绍如何创建数据库 (DataSet) 数据源,以及如何在 WinForm 应用程序中使用它们。但是,记住以下两点很重要:
-
创建数据源后,不管它的数据来自何处,使用它的方式都是相同的。也就是说,就像您可以轻松(且图形化)地将基于数据库的数据源绑定到一个网格或一组控件一样,实际上来自 Web 服务或自定义业务对象的数据也是如此。
-
数据源以相同的方式进行定义,不管它们将要用在 WinForm 应用程序还是 WebForm 应用程序中。不同的数据提供程序还可以抽象化,所以如果只使用 DataSet 和 TableAdapter 提供数据访问,要更改实际的数据库,只需更改连接字符串并重新生成类即可。
类型化的 DataSet 和 TableAdapter
一个数据库数据源是一个强类型的 DataSet 与一对或多对强类型的 DataTable 和 TableAdapter 的组合。类型化的 DataSet 并不是一个新概念,我们曾在 Visual Studio 2002/2003 中讨论过它。类型化的 DataSet 是一个生成的类,是从 .NET Framework 的一般 DataSet 类衍生来的,但具有已定义的架构以及特定于该架构的属性和方法。同时,对于 DataSet 中的每个表,还生成了特定于该 DataSet 的三个附加衍生类:DataTable、DataRow 和 DataRowChangeEvent。每个类都为相关的表提供了特定的架构、属性和方法。例如,如果我定义一个基于 Northwind Employees 表的数据源,最后会生成以下类:
-
NorthwindDataSet
-
EmployeesDataTable
-
EmployeesDataRow
-
EmployeesRowChangeEvent
这四个类组成了类型化的 DataSet。在 Visual Studio 2005 中,还会生成第五个类,即名为 EmployeesTableAdapter 的类型化的 TableAdapter 类,我们稍后会进行讨论。当然,如果您动态定义查询,则不能生成类型化的 DataSet,而需要使用标准的 DataSet。
为什么非要使用类型化的 DataSet 呢?除了强迫您事先仔细考虑数据架构的问题,而不是只“随便想想”之外,类型化的 DataSet 还提供了以下几个具体的优点:
-
DataSet、DataTable、DataRow 和 RowChangeEvent 特定于要处理的架构。
-
表、列和关系是作为命名属性提供的,而不是作为一般的集合元素。
-
作为第 (2) 条的结果,您可以从 Visual Studio 代码编辑器中获得全部的 IntelliSense 和语句完成支持。
-
同样作为第 (2) 条的结果,使编译时类型检查成为可能(例如,可以在编译时而不是运行时捕获字段名称拼写错误)。
-
代码更简洁、可读性更强,不会出现这样的代码:
country = dsNorthwind.Tables ("Employees").Rows (row) ("Country")
相反,代码应是:
country = dsNorthwind.Employees (row).Country
最基本的是,类型化的 DataSet 提供的设计时和编译时帮助不仅可以大大减少最初的开发时间,还可以大大减少调试和稳定应用程序所需的时间。
而 TableAdapter 对 Visual Studio 2005 来说则是一个全新的概念。它隐含的意思是,强类型的 TableAdapter 是标准 DataAdapter 的强类型的等价物。您使用 TableAdapter 连接数据库并对该数据库执行查询(或存储过程),然后用数据填充相关的 DataTable。每个 DataTable-TableAdapter 对简称为一个 TableAdapter。
注意:在 2004 年 12 月的 CTP 中,DataTable-TableAdapter 对被称为数据组件。但是,Visual Studio 已不再使用此术语,以后的版本中只会将该对称为 TableAdapter。本文只使用术语 TableAdapter。
TableAdapter 实质上是标准 DataAdapter 的包装程序,它具有以下几个优点:
-
同一个 TableAdapter 类可用于多个表单或组件中,使查询/命令的任何更改都能自动反映在所有实例中。这与现状是不同的,现在,访问数据库的每个组件都必须具有自己单独配置的 DataAdapter。这使 DataTable 和 DataAdapter 之间的同步更容易实现。
-
TableAdapter 使您可以轻松地为一个给定的 DataTable 定义多个命令,而不必使用多个 DataAdapter(或手动编写的切换代码)为一个 DataTable 定义多个查询/命令。
-
填充命令具有可读(友好)的名称,而且 TableAdapter 包含的代码能够自动将所有参数的类型和值信息填充到这些命令方法中。您不用再担心如何传递提供程序特定的数据类型(例如 SqlInt)。
一个简单的代码片断将有助于说明这些功能。在 Visual Studio 2002/2003 中,即使使用类型化的 DataSet,使用两个参数执行一个简单查询的代码对查询来说也很重要:
SELECT FirstName, LastName from Employees WHERE Country = @country AND City = @city
我们必须编写如下所示的代码:
Me.SqlAdapter1.SelectCommand.Parameters ("@country").value = Me.CountryListbox.SelectedValue.Trim() Me.SqlAdapter1.SelectCommand.Parameters ("@city").value = Me.CityTextbox.Text.Trim() Me.SqlAdapter1.Fill (Me.NorthwindDataSet.Employees)
当然,随着参数数量的增加,代码的行数也会增加。更重要的是,正确记住并键入每个参数名称的可能性大大降低。即使参数名称正确,我还需要记住该参数的数据类型。最惨的是,如果我输入了错误的字段名称或者尝试指定了错误类型的值,直到运行时才能发现这个错误!
使用 Visual Studio 2005 中的 TableAdapter,定义命令 FillByCountryAndCity 之后,我只需编写一行代码(用于传递参数值),即可在任意位置使用该命令:
Me.EmployeesTableAdapter.FillByCountryAndCity ( _ Me.NorthwindDataSet.Employees, Me.CountryListbox.SelectedValue.Trim(), _ Me.CityTextbox.Text.Trim() )
请务必注意,我们不仅是从一个 TableAdapter 中获取多个命名命令,而且这些命令是强类型的。这意味着在 Visual Studio 中编写代码时,我们可以获得全部的 IntelliSense,能够将这些命令视为 TableAdapter 的方法。我们还可以对这些命令的参数进行编译时类型检查,还可以获得带有方法和参数类型定义的工具提示以帮助我们处理各种情况。TableAdapter 可以有多个方法,用来执行不同的命令并接受不同的参数。我们将在稍后创建示例表单时进一步介绍 TableAdapter。
入门 - 创建数据源
在本文中,我们将创建一个表单,用来显示 Northwind 数据库中每个订单的订单信息。打开一个新的 Visual Basic WinForm 项目后,首先要做的是在该项目中添加一个新的数据源(我们将使用 Visual Basic 逐步介绍此过程,但所有内容同样适用于 C#)。
添加数据源的步骤:
-
从 Visual Studio 主菜单的 Data(数据)菜单项中选择 Show Data Sources(显示数据源),以显示 Data Sources(数据源)窗口(如果尚未显示)。
-
在 Data Sources(数据源)窗口中,单击 Add New Data Source(添加新数据源)工具栏按钮。将启动 Data Source Configuration Wizard(数据源配置向导),该向导合并了 DataAdapter Configuration Wizard(DataAdapter 配置向导)的大多数功能与 Visual Studio 2002/2003 中的 DataSet 生成工具。
-
如果您的 Visual Studio 版本仍然包含向导的 Welcome(欢迎)页面,请选择“下一步”。将显示 Choose a Data Source Type(选择数据源类型)页面。
-
选择 Database(数据库)。
-
选择 Next(下一步)。将显示 Choose Your Data Connection(选择您的数据连接)页面。
-
选择 New Connection(新建连接)。将显示 Add Connection(添加连接)对话框。
-
输入连接 SQL Server 或 MSDE 实例及 Northwind 数据库所需的信息。
-
选择 OK(确定)关闭该对话框。
-
请注意,连接字符串现在被保存为设置属性,可以通过以下字符串进行访问:
My.Settings.NorthwindConnectionString
在 C# 中,该字符串应为:
VSDataSets.Properties.Settings.Default.NorthwindConnectionString;
-
选择 Next(下一步)。将显示 Choose Your Database Objects(选择您的数据库对象)页面。
-
请注意,您可以从 Tables(表)、Views(视图)、Stored Procedures(存储过程)或 Functions(函数)中进行选择。
展开 Tables(表)节点,然后选择 Orders 和 Order Details 表。我们将使用表中的所有列,但您可以只选择您的应用程序需要的那些列。
-
选择 Finish(完成)退出向导。
图 1 显示了生成的 Data Sources(数据源)窗口,其中的 Order Details 表已展开以显示表中的所有列。
如果要重新打开 Data Source Configuration Wizard(数据源配置向导)以进行某些更改,可以从 Data Sources(数据源)窗口的工具栏或者从右键单击该窗口中的任意元素后显示的上下文菜单中选择 Configure DataSet with Wizard(使用向导配置 DataSet)。但是,还有一个更强大的工具可用来编辑生成的 DataSet 和 TableAdapter,那就是 DataSet 设计器。
DataSet 设计器
Visual Studio 2005 附带了 DataSet 设计器,此工具实际上是为指定和编辑 DataSet 及其相关的 TableAdapter 而设计的。这是针对 Visual Studio 2002/2003 中的问题做出的重大改进,在 Visual Studio 2002/2003 中,我们必须使用 XML 架构编辑器来定义强类型的 DataSet。在 Visual Studio 2005 中,DataSet 和 TableAdapter 定义仍然保存在 .XSD 文件中,而且仍然提供了 XML 编辑器,以便在您确实需要编辑 XML 架构时使用。但是,新旧版本之间的相似性仅限于此。目的不在于使 DataSet 设计器支持任意 XSD 文件,而是为了说明它只是一种方便的文件格式。
要打开 DataSet 设计器,请从 Data Sources(数据源)窗口的工具栏或者从右键单击该窗口中的任意元素后显示的上下文菜单中选择 Edit DataSet with Designer(使用设计器编辑 DataSet)。设计 DataSet 及其 DataTable 时,您可能觉得设计器与您以前设计数据库时使用的其他工具很相似。您可以通过以下方法从要连接的数据库中添加一个表:将数据库对象(例如表、视图或存储过程)从 Server Explorer(服务器资源管理器)中拖到设计器上,或者从设计器上下文菜单的主 Data(数据)菜单项中选择 Add TableAdapter(添加 TableAdapter)以启动 TableAdapter Configuration Wizard(TableAdapter 配置向导)。由于 DataSet 也可以包含直接加载且未与数据库连接的表,因此您也可以通过从菜单中选择 Add DataTable(添加 DataTable)来添加独立的表。当然,您也可以在编辑器中添加和/或重命名列。另一个灵活的功能是,编辑器可以自动识别数据库中各表之间的关系,并为您定义 DataSet 中各表之间的对应关系。
图 2 显示了 DataSet 设计器中的数据源,它由 Orders 表和 Order Details 表组成。请注意,与每个 DataTable 密切相关的是对应的 TableAdapter,它的作用是用数据填充表并用这些数据的更改更新数据库(后者可选)。
TableAdapter Configuration Wizard ( TableAdapter 配置向导)
要在 DataSet 设计器中启动 TableAdapter Configuration Wizard(TableAdapter 配置向导)(在 Visual Studio 2005 的 2004 年 12 月的 CTP 版中仍然称为 Data Component Configuration Wizard [数据组件配置向导]),请从主 Data(数据)菜单或从右键单击设计器中的任意 TableAdapter 时出现的上下文菜单中选择 Add Query(添加查询)或 Configure(配置)(现有查询)。此向导与 Visual Studio 2002/2003 中的 DataAdapter Configuration Wizard(DataAdapter 配置向导)基本相同,只是多提供了两个页面。第一个是图 3 所示的 Choose a Query Type(选择查询类型)页面。由于 TableAdapter 是给定表中所有命令的中心点,因此它不仅允许您定义多个 Select/Fill 命令,还允许您运行任意类型的查询(更新、插入、删除)或只返回一个值的查询。请注意,这些更新查询是 TableAdapter 的命名方法,必须直接调用。它们是您执行 TableAdapter 的 Update 方法(就像 DataAdapter.Update 方法一样)时自动调用的更新查询的补充。
向导中的第二个附加页面是 Choose Methods to Generate(选择要生成的方法)页面。在此页面上,您可以为您定义的每个查询/命令方法选择名称。向导为每个命令都提供了 Fill 和 Get 方法,如图 3 所示。Fill 方法要求您指定要填充的 DataTable,而 Get 方法将返回新创建和填充的 DataTable。
通常情况下,您会为一个 TableAdapter 定义多个 Fill 命令,这些命令返回相同的架构(列),但却具有不同的 WHERE 子句。这就是默认情况下向导为方法名称提供 FillBy 和 GetDataBy 前缀的原因。当然,您也可以为方法指定您选择的任意名称。
尽管一个 TableAdapter 可以有多个 Fill 命令,但调用 TableAdapter 的 Update 方法时,只执行一组更新命令。这些命令是基于 TableAdapter 的主查询自动生成的。首次创建 TableAdapter 时定义的查询被认为是 TableAdapter 的主查询。如果以后定义的任何查询返回的架构与主查询返回的架构不同,设计器将使用消息框提醒您。此外,如果您修改主查询的架构,Visual Studio 将转到并修改其他查询以匹配该架构。
使用 TableAdapter Configuration Wizard ( TableAdapter 配置向导)添加新命令
下面,我们将另一个命令添加到 Orders 表的 TableAdapter 中。
-
从 Data Sources(数据源)窗口的工具栏中选择 Edit DataSet with Designer(使用设计器编辑 DataSet),以打开 DataSet 设计器。
-
选择 Orders 表的 TableAdapter,然后从其上下文菜单中选择 Add Query(添加查询)。
-
如果您的 Visual Studio 版本中仍然存在 Welcome(欢迎)页面,请单击该页面上的 Next(下一步)。将显示 Choose a Command Type(选择命令类型)页面。
-
选择 Next(下一步),接受默认的 SQL 语句。将显示 Choose a Query Type(选择查询类型)页面。
-
选择 Next(下一步),接受默认的 SELECT 语句。将显示 Specify a SQL SELECT Statement(指定 SQL SELECT 语句)页面。
-
输入以下 SQL 语句:
SELECT OrderID, CustomerID, EmployeeID, OrderDate, RequiredDate, ShippedDate, ShipVia, Freight, ShipName, ShipAddress, ShipCity, ShipRegion, ShipPostalCode, ShipCountry FROM Orders WHERE CustomerID = @CustID
将为查询返回由 @CustID 参数指定的客户的所有订单。
-
选择 Next(下一步)。将显示 Choose Methods to Generate(选择要生成的方法)页面。
-
将两个复选框保留为选中状态。将两个方法名称分别修改为 FillByCustomer 和 GetDataByCustomer。
-
选择 Next(下一步),然后选择 Finish(完成)以完成该过程。
如果您查看 DataSet 设计器中的 OrdersTableAdapter,就会看到现在显示了第二对命令 FillByCustomer 和 GetDataByCustomer,它们使用 CustomerID 值作为参数。此方法参数的 (.NET) 类型是什么?让我们看一看。
后台查看
在 DataSet 设计器或任何相关的向导中进行更改时,Visual Studio 将转到并为一组类型化的类生成代码。具体来说,它会生成以下类:
-
DataSet 类
-
DataTable 类
-
TableAdapter 类
-
DataRow 类
-
DataRowChangeEvent 类
除了 DataSet 类(每个数据源只有一个)外,DataSet 中定义的每个表都有其他四个类。您可以通过执行以下操作来查看这些类的代码:
-
在 Solution Explorer(解决方案资源管理器)窗口中,单击工具栏上的 Show All Files(显示所有文件)按钮,以显示与项目相关的所有文件。
-
展开 NorthwindDataSet.xsd 文件的节点。
-
双击 NorthwindDataSet.Designer.vb 的文件节点。这就是为实现构成 DataSet 的类而生成的代码。
-
打开代码窗口左上方的列表框。您可以从中看到此文件中的类列表:
NorthwindDataSet
Order_DetailsDataTable
OrdersDataTable
Order_DetailsRow
OrdersRow
Order_DetailsRowChangeEvent
OrdersRowChangeEvent
Order_DetailsTableAdapter
OrdersTableAdapter
-
从左侧列表框中选择 OrdersTableAdapter 类。
-
从右侧列表框中选择 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
我们可以从这段代码中了解几个问题。
-
FillByCustomer 方法实际上使用两个参数,一个是要填充的 OrdersDataTable,另一个是作为字符串的 CustID 参数。
-
不必查看文件中的所有代码就可以看出 TableAdapter 类维护一个命令集,它自动从该集中将正确的命令指定给 .NET DataAdapter,DataAdapter 实际上用来与数据库通信。
-
方法用于检查状态参数的实例是否已传递给方法(如果参数的 AllowDBNull 属性设置为 False)。
-
CustID 参数值被指定给 DataAdapter 的 SelectCommand 属性的预配置参数。
-
所有内容都设置好之后,将调用标准 DataAdapter.Fill () 方法以填充 OrdersDataTable。
请记住,您不需要自己编写这段代码,所有内容都已经生成并配置好了。花些时间浏览一下为其他类生成的代码,您将会更广泛且更深入地理解这些类是如何实现的,可能还会学到一两个好的编码方法!
注意:尽管类型化的 DataSet 及其相关的类(包括 TableAdapter)都在一个源文件中生成,但 TableAdapter 在一个单独的名称空间中生成。这反映了这样一个事实:实体对象 (DataSet) 与实际的数据访问对象 (TableAdapter) 应分开。
创建以数据为中心的表单
现在已经生成了一个类型化的 DataSet,下面我们要创建一个显示这些数据的表单。尽管我不会介绍 .NET Framework 2.0 和 Visual Studio 2005 中的 WinForm 和数据绑定的所有细节和新功能(要介绍的内容有很多),但这是一个好机会,可以了解为了更轻松灵活地创建以数据为中心的功能表单已经完成的某些操作。
工具箱中的数据组件
如果您习惯于从工具箱的 Data(数据)选项卡上的标准数据组件开始创建以数据为中心的代码,那么打开 Visual Studio 2005 后看不到这些标准组件会让您有点失望和迷茫。当然,这是“故意的”,Microsoft 要促使我们利用新的类型化的 DataSet 和 TableAdapter。如果您非常希望使用旧的、非类型化的组件,则可以手动将它们添加回工具箱中。强烈建议您不要这样做,通过利用新的 TableAdapter 以及局部类技术,新的类型化的 DataSet 和 TableAdapter 将更容易使用和展开。
您可以从 Visual Basic 团队网络日志中了解有关这些问题和其他设计决策(以及做出这些决策的想法)的详细信息。
尤其应阅读 Steve Lasker 的两个帖子:Why are the Data Components no longer on the Toolbox? 和 Why can't I drag from Server Explorer to my form?
在 Visual Studio 设计器中打开一个表单时,Visual Studio 工具箱中将显示一个标有您的项目名称的选项卡。将数据源添加到项目中并且至少编译一次后,此选项卡上将包含您创建的类型化的 DataSet 和 TableAdapter。尽管您可以将这些组件拖到表单设计器中,但您通常可能不会这样做(但是,如果您要使用设计器实现数据访问组件,这却是一个不错的方法)。您通常会使用三种不同方法中的一种来创建以数据为中心的表单。我们先来详细介绍第一种方法,这是最简单的,可能也是最常用的方法。这就是所谓的“一次拖动”数据绑定。
-
双击 Solution Explorer(解决方案资源管理器)中的“Form1.vb”,在 Visual Studio 表单设计器中打开 Form1。
-
在 Data Sources(数据源)窗口中,展开 Orders 表的节点。
请注意,DataSet 中的每个表和每个列都有一个相关的图标。这些图标表示将表或列拖放到表单中时,用来绑定到数据的 WinForm 控件的类型(或“拖放类型”)。通过选择一个项目并从相关的下拉列表中选择一个控件,可以更改控件的类型。请注意,下拉列表中包括选项 None(无,表示未显示任何内容)和 Customize(自定义,表示可以指定任意控件)。
只有当前活动的窗口为表单(或组件)设计器时,这些图标和拖放类型列表才可见。否则,您将无法从 Data Sources(数据源)窗口中拖放控件,这些图标也会发生变化,向您指明这一点。
-
将 Orders 表的控件类型从 DataGridView 更改为 Details。这意味着当您将整个 Orders 表拖动到一个表单上时,系统将创建一个逐行显示详细信息的表单,而不是创建一个在一个网格中显示所有数据(一次显示所有行)的表单。对于 Details(详细信息)视图,将为每一列添加一个标签和一个控件,控件的类型在 Data Sources(数据源)窗口中指定。
-
将 Orders 表从 Data Sources(数据源)窗口拖动到设计器中的 Form1 上。
-
选择最后七 (7) 个字段(以及它们的标签),然后将这些字段拖到前七 (7) 个字段的旁边,使表单如图 4 所示。
图 4 :设计器中的 Form1 -
启动应用程序,然后使用表单顶部的工具栏上的导航按钮逐个查看记录,验证该应用程序能否正常工作。
我们来看一看将 Data Source 表拖到表单上后,Visual Studio 执行了哪些操作。查看表单下面的组件栏,可以看到它向表单中添加了四个组件。我们已经了解(或许已经喜欢上)其中的两个组件:NorthwindDataSet 和 OrdersTableAdapter。OrdersTableAdapter 用于使用数据库中的数据填充 NorthwindDataSet 的 OrdersDataTable。甚至执行此 Fill 操作的一行代码也已经编写并自动添加到 Form1 的 Load 事件处理程序中。
Me.OrdersTableAdapter.Fill(Me.NorthwindDataSet.Orders)
用于数据绑定的关键类是 BindingSource 类,它在本例中被明确命名为 OrdersBindingSource。BindingSource(在 Beta 1 中称为 DataConnector)提供将控件绑定到表单所需要的服务。它在数据源和绑定到该数据源的控件之间提供了一个非直接层。通过设置 BindingSource 的 DataSource 和 DataMember 属性,将 BindingSource 附着到一个数据源上;然后通过添加到控件的 DataBindings 集合中,将这些控件绑定到 BindingSource。与数据进行的所有交互(例如记录的导航、排序、筛选和编辑)都是通过 BindingSource 完成的。BindingSource 还通过 List、Item 和 Current 属性允许访问基础数据。
添加的另一个组件是 OrdersBindingNavigator。BindingNavigator 类是一个工具栏,它为导航和操作表单上的数据提供了一个标准的用户界面。BindingNavigator(在 Beta 1 中称为 DataNavigator)是一个 ToolStrip 控件,它具有一组预配置的按钮。它附着到作为其数据源的 BindingSource 上,并提供控制导航可用数据的工具栏按钮。但是,如果您不想控制导航,而是希望响应某些导航事件,则应连接 BindingSource 对象的这些事件。
创建一个 “ 主表单 - 详细信息 ” 表单
现在,我们已经创建了一个可以显示一个表中的数据的表单,那么以“主表单-详细信息”的格式显示第二个相关表中的数据容易(或困难)吗?只需继续在 Form1 中执行以下步骤:
-
现在,我们将使用“连接点”数据绑定来创建表单,并将一个控件从工具箱中拖放到该表单上,然后将 Data Source(数据源)窗口中的一个元素拖放到该控件上,将控件和元素连接起来。
-
从工具箱的 All Windows Forms(所有 Windows 表单)选项卡上选择 DataGridView 控件。将该控件拖放到 Form1 上,使其占据表单下半部分的大部分空间。
-
返回到 Data Source(数据源)窗口,如图 5 所示。请注意,Order Details 表实际上在该窗口中出现两次。第一次,它作为 NorthwindDataSet 的直接子控件和 Orders 表的同级控件。第二次,它显示为 Orders 表的子控件,表明它是一个相关表。如果要在表单上单独显示 Order Details 表,则可以选择直接位于 NorthwindDataSet 下的 Order Details 表的实例。但在本例中,我们要显示与(主)Orders 表相关的 Order Details 表,因此,我们应选择显示直接位于 Orders 表下的 Order Details 表的实例。
图 5 : Data Source (数据源)窗口中的 Order Details 表 -
选择 Orders 表下的 Order Details 表,然后将其拖动到 Form1 中的 DataGridView 上。
-
请注意,Order_DetailsBindingSource 和 Order_detailsTableAdapter 已被添加到 Form1 的组件栏中。
-
运行应用程序,并使用 BindingNavigator 浏览 Orders 表中的记录,如图 6 所示。请注意,DataViewGrid 中显示的 Order Details 记录将自动发生变化,以便只显示与当前 Order 记录相关的那些记录。
图 6 : Orders 表中的 BindingNavigator
由于某些原因,“连接点”数据绑定在 Visual Studio 2005 的 2004 年 12 月 CTP 版中是断开的。但是,它在 Visual Studio 2005 以前和以后的版本中都能正确工作。如果您使用的是 12 月的 CTP 版,只需将 Order Details 表(位于 Orders 表下)拖放到 Form1 中。
自定义生成的代码
在前面我们查看 DataSet 的类的代码时,您可能已经注意到了实际上有两个 Visual Basic 代码文件:NorthwindDataSet.Designer.vb 和 NorthwindDataSet.vb。如果没有 NorthwindDataSet.vb 文件,请返回到 DataSet 设计器,双击该设计器的背景以创建该文件。
这两个文件都用来实现构成 DataSet 的类。存在两个文件的原因在于,我们要利用一个新的、简单但很强大的功能,称为局部类。局部类是一个编译器功能,它允许将类(或结构)的定义拆分到几个声明中。不同的声明可以位于不同的源代码文件中,只要这些声明都位于相同的程序集和名称空间中就可以。Visual Studio 广泛使用此功能,将设计器和开发人员为同一个类生成/编写的代码分开。例如,在 Visual Studio 2002/2003 中,表单的代码可能是该表单的类声明的所有内容,例如:
Public Class Form1 Inherits System.Windows.Forms.Form
该表单的初始化代码(包括该表单上放置的所有控件)都由 Visual Studio 生成。这段代码位于 InitComponent () 方法中,默认情况下,出现在名为“Windows 表单设计器生成的代码”代码区域中用户编写的代码之前。此区域通常是闭合的,以便最大程度地减少这段代码对您实际要编写的代码造成的混乱和干扰。在 Visual Studio 2005 中,这段代码造成的干扰更少,因为它包含在一个名为 Form1.Designer.vb 的完全不同的文件中!同样,如果您愿意,仍然可以查看该文件的内容:单击 Solution Explorer(解决方案资源管理器)工具栏上的 Show All Files(显示所有文件),展开 Form1.vb 节点,然后双击 Form1.Designer.vb,使其显示在代码编辑器中。文件 Form1.vb 中只包含您作为开发人员为 Form1 类编写的代码。
对于 DataSet 及相关类的代码,使用局部类和不同的文件将设计器代码和开发人员代码相分离就变得更加重要。除了更加有序外,这种分离还解决了在 Visual Studio 2002/2003 中使用类型化的 DataSet 时存在的一个大问题。
您经常希望对系统自动为 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 中:
-
在 Data Sources(数据源)窗口的工具栏中选择 Edit DataSet with Designer(使用设计器编辑 DataSet),以打开 DataSet 设计器。
-
双击设计器背景中的空白区域。将在代码编辑器中打开 NorthwindDataSet.vb 文件。
-
输入以下代码而不是默认代码:
Partial Public Class NorthwindDataSet Partial Class OrdersDataTable Protected Sub ValidateNewRow(ByVal sender As Object, _ ByVal e As System.Data.DataTableNewRowEventArgs) _ Handles Me.TableNewRow ' 创建行的类型化实例 ' 这可以帮助我们避免将代码放在引号中, ' 例如 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 ' 设置 ShipPostalCode 的值 ordersRow.ShipPostalCode = "Invalid" ' 通常,更改用户数据是不好的用户体验 ' 因此使用 ErrorProvider 指明错误 ' 这里我们介绍了两种方法 ordersRow.SetColumnError( _ ShipPostalCodeColumn.ColumnName, "Invalid Postal Code") Else ' 当值为 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 ' 这是一个存根,只是为了验证功能 If city = "Rio de Janeiro" Then Return False Else Return True End If End Function End Class
请注意,DataSet 的所有相关类(例如 OrdersDataTable)都是作为 DataSet 中的嵌套类实现的。前面代码中显示的局部类声明反映了这种实现。
为了获得错误提供程序指示的错误,除了将邮政编码值更改为 Invalid 外(也可以不更改),还需执行以下操作:
-
将 Error Provider 控件从工具箱拖到 Form1 上,将其放在 Ship Postal Code 文本框的右侧。
-
在 Error Provider 的属性窗口中,将 DataSource 属性设置为 OrdersBindingSource。
运行应用程序,然后导航到城市 Rio de Janeiro 的记录,查看当前运行的验证代码,如图 7 所示。
这个示例只是为了说明如何轻松地扩展 Visual Studio 自动生成的类型化的 DataSet 及相关类的功能。您可能还需要向生成的类中添加其他方法和属性。在设计应用程序和使用 DataSet 时,您会想到更多的可能性。需要记住的关键点是,由于 Visual Studio 2005 中使用了局部类,您编写的代码位于一个单独的文件中,重新生成 DataSet 类时不会受到影响。
小结
使用 Visual Studio 2005 生成的类型化的 DataSet 比以往更容易、更灵活。DataSet 设计器为定义 DataSet 提供了一个更容易、更自然的工具。新的 TableAdapter 类(可以在 DataSet 设计器中进行配置)提供了一个集中的机制,使您可以对特定的数据表轻松地维护和执行多个不同的查询和命令。使用局部类编译器功能使设计器生成的代码与开发人员编写的代码完全分离,重新生成 DataSet 类时将不会影响为了扩展这些类而编写的任何自定义代码。最后,新的 .NET 数据绑定类和机制与 Visual Studio 2005 提供的工具相结合,使开发以数据为中心的应用程序变得更容易、更快捷。
感谢 Steve Lasker 以及 Microsoft 的 Alan Griver 和 Pablo Castro 为本文的诞生提供的帮助。