演练:N 层数据应用程序中的筛选和更新






4.47/5 (7投票s)
在 Microsoft N 层数据应用程序演练的基础上,增加了筛选和更新功能。
![]() ![]() |
![]() |
引言
如果您刚接触 Visual Studio 2008,并且刚刚开始学习数据库编程,那么您无疑已经学习了 Visual Studio 文档和 MSDN 中提供的一些出色的演练。其中一个非常适合中级初学者的教程是 MSDN Library Online 上的文章演练:创建 N 层数据应用程序。
Microsoft 演练将引导您完成使用 Visual Studio 2008 强大新数据库功能访问 Northwind 数据库中的 Customers 和 Orders 表的过程。本文在此基础上进行了扩展,向用户界面添加了常见的数据库操作,例如筛选和更新。这不是一项简单的任务,因为我们正在处理的更改在某些情况下必须跨此 N 层数据应用程序的所有或大多数不同层进行。
本文的结构如下。在下一节“本文展示了什么”中,我们将概述所概述的程序中演示的概念。下一节“将常见数据库功能添加到 NTierWalkthrough UI”将介绍基于 Microsoft 提供的教程的主要演练。接下来,我们将在“兴趣点”部分给出总结性评论并回顾要点。最后,“历史记录”部分记录了本文的更改、更新和改进。
我使用 Windows Vista Business Service Pack 1、Microsoft .NET Framework 3.5 SP1、Microsoft Visual Studio 2008 Professional Edition SP1 和 Microsoft SQL Server 2008 Express Edition with Tools 编写了本文。我使用的 SQL Server 实例名为 MSSQLSERVER2008。您的配置中可能有所不同。
如有关于本文的问题,请随时给我发送电子邮件。我还提供数据库、GUI 和进程间通信(使用 WCF)咨询服务。我很乐意通过远程或在当地大都市区亲自提供我的服务。如有问题、评论或建议,请发送电子邮件给我。如果我们可以一起做一个项目,请告诉我您的想法!
本文展示了什么
请注意上面的两张图片。两张图片都说明了用户启动教程中构建的表示层窗体时看到的屏幕。
上面左侧的图片显示了 Microsoft 演练生成的窗体。右侧的图片(“之后”图片)显示了我们添加扩展以允许用户将对表所做的更改保存回数据源,并显示如何允许用户筛选 Orders 表,以便仅显示特定客户下的订单后的窗体。用户可以选择通过单击“显示所有”按钮来显示 Orders 表中的所有记录。此外,如果用户更改了某个字段但想再次从数据源获取数据,则用户可以选择刷新显示。
将常见数据库功能添加到 NTierWalkthrough UI
要开始扩展演练,请使用上面的链接下载 NTierWalkthrough_before 项目并跟着操作,或者访问 MSDN 上的演练:创建 N 层数据应用程序页面并遵循其中列出的所有步骤。完成所有步骤后,您就可以开始遵循我们扩展演练的步骤了。
要扩展“创建 N 层数据应用程序”演练,将添加以下功能
- 在窗体上托管的 ToolStrip 上添加保存数据和刷新按钮,以允许更新数据库和刷新视图;
- 一个筛选器,用户可以通过从 Customers 表中选择特定的 CustomerID,筛选 Orders 表以查看该客户的所有订单。
- 一个显示所有按钮,用于显示所有客户的所有订单。
在本节中,我们首先执行一些步骤为开始我们的扩展演练做准备。所有内容都以分步方式解释。接下来,我们添加保存数据和刷新视图的功能。然后,我们稍作休息,将控件的侧面锚定到可调整大小的窗体,以便于使用。最后,我们深入研究此扩展演练的核心内容,其中包括实现数据筛选和修改表示层的用户界面,以帮助用户访问筛选后的数据。
扩展演练:准备
为了准备本文中介绍的演练:创建 N 层数据应用程序的扩展,我们需要重命名演练的项目,以区分不同版本
- 从文章演练:创建 N 层数据应用程序的最终结果开始,打开 Visual Studio 并打开 Visual Studio 中的 NTierWalkthrough 解决方案。
- 将 NTierWalkthrough 解决方案重命名为 NTierWalkthrough_extended。
- 在解决方案资源管理器中,右键单击文本“解决方案 'NTierWalkthrough'”,然后单击出现的上下文菜单中的重命名。键入解决方案的新名称,然后按键盘上的 Enter 键。
添加保存数据和刷新视图的功能
在演练中的 PresentationTier 项目中,用户能够查看 Northwind 数据库中 Customers 和 Orders 表的内容。用户还可以就地编辑信息,甚至向每个表添加新记录。
Microsoft 演练没有添加将数据和更改保存到 Customers 和 Orders 选项卡表回数据源的功能,也没有刷新视图的功能;即,不保存地从数据源获取数据到窗体。这对于您更改了表格单元格,又改变主意,想取消更新的情况很有用。
在本节中,我们首先介绍从窗体将数据保存到数据源的步骤。然后,我们实现刷新按钮,该按钮从数据源加载数据并更新窗体。
将数据从窗体保存到数据源
目前,没有将用户所做更改更新到数据库的功能。在默认的演练项目中,工具栏包含一个“保存数据”按钮。此按钮被禁用,并且在演练中没有为其添加任何功能。微软竟然遗漏了这样一个关键功能,这很奇怪。
要提供此功能,必须遵循三个主要步骤。首先,我们必须启用窗体 ToolStrip
控件上的保存数据按钮。其次,我们向窗体添加组件,这些组件通过我们对 DataAccessTier 项目的引用提供,这些组件驱动窗体中显示的数据的保存和管理。最后,我们将保存数据按钮连接到实现事件处理程序,并使用 DataAccessTier 组件将数据从窗体传输到数据源。
要启用 DataGridView 工具栏上的保存数据按钮,请执行以下操作
- 在解决方案资源管理器中,导航到 PresentationTier 项目。
- 双击 Form1.cs 以在设计器中打开用户界面。
- 在保存数据按钮的工具栏图标上,右键单击该按钮,然后单击启用。这如图 2 所示。
- 任何
ToolStrip
控件的按钮都可以通过这种方式启用或禁用。
- 任何
现在“保存数据”按钮已启用,我们通常会添加代码以使用用户的更改来更新数据库。首先,我们需要在设计器中向窗体添加某些组件,然后将这些元素连接到代码。
要提供“保存数据”按钮的功能,请执行以下操作
- 在解决方案资源管理器中,导航到 PresentationTier 项目。
- 双击 Form1.cs 以在设计器中打开用户界面。
- 从“工具箱”中,打开“DataAccessTier Components”选项卡,如图 3 所示。图 3. DataAccessTier Components 选项卡在工具箱中打开,要添加的控件以红色圆圈突出显示。
- 将 CustomersTableAdapter、OrdersTableAdapter 和 TableAdapterManager 组件拖到设计图面的任何空白区域。
customersTableAdapter1
、ordersTableAdapter1
和tableAdapterManager1
对象的图标出现在窗体下方的组件托盘中,如图 4 所示。
图 4. CustomersTableAdapter1、ordersTableAdapter1 和 tableAdapterManager1 对象的图标,以红色圆圈突出显示,位于窗体设计器的组件托盘中。 - 在“属性”窗口中,重命名
customersTableAdapter1
、ordersTableAdapter1
和tableAdapterManager1
对象的(名称)属性,删除名称末尾的数字“1”,使它们的名称现在为customersTableAdapter
、ordersTableAdapter
和tableAdapterManager
对象。这只是我个人的一个小癖好。 - 右键单击组件托盘中的
tableAdapterManager
对象,然后单击出现的菜单中的属性。确保 CustomersTableAdapter 属性设置为customersTableAdapter
,并确保 OrdersTableAdapter 属性设置为ordersTableAdapter
。 - 接下来,双击窗体顶部
ToolStrip
中的保存数据按钮。
- 将生成一个骨架实现处理程序,Visual Studio 会将您移动到代码中的位置,以开始填写处理程序的实现。
- 将清单 1 中显示的代码添加到处理程序中。
private void customersBindingNavigatorSaveItem_Click(object sender, EventArgs e) { try { this.UseWaitCursor = true; this.Validate(); this.customersBindingSource.EndEdit(); // Take the form out of Edit mode this.ordersBindingSource.EndEdit(); // Take the form out of Edit mode this.tableAdapterManager.UpdateAll(this.northwindDataSet); this.UseWaitCursor = false; } catch (Exception ex) { this.UseWaitCursor = false; ShowStopError(ex.Message); } }
清单 1. 用于将窗体上所做更改更新到数据源的代码。
清单 1 中的代码使用了结构化异常处理 (SEH)。在这种情况下,我们处理异常最基本的方法是关闭等待光标,然后使用
ShowStopError
辅助函数显示一个包含异常错误文本的消息框。 - 添加
ShowStopError
辅助函数的代码,如清单 2 所示。这只是个人偏好;我发现自己经常在捕获到异常时显示消息框,所以我更喜欢设置一个辅助函数来处理消息。当然,我也更喜欢进行充分的调试和测试,这样用户就永远不必处理捕获到的异常;但是,我们永远无法预测 100% 的失败情况。您可以随意选择您认为最适合您的实现的方式。private void ShowStopError(string Message) { MessageBox.Show(Message, "NTierWalkthrough", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1); }
清单 2. 用于在捕获到异常时显示消息框的代码。
- 按 F5 键生成并运行程序。对任一表中的一个或多个单元格进行更改或添加一行或多行。然后单击保存数据按钮。关闭窗体,然后再次按 F5 键。您会发现所做的更改仍然可见,因为更改已保存到数据源。
- 这种操作模式与 Microsoft Access 单击其保存数据按钮时的行为完全相同。
我们现在已经了解了如何在 N 层数据应用程序演练中更新数据源上的数据。接下来,我们将讨论从数据源刷新数据到视图,此功能用于(例如)将窗体恢复以擦除用户不希望提交到数据源的编辑中所做的更改。
从数据源刷新数据
假设我有一家公司,其数据库是 Northwind 数据库。假设我是一名销售代表,我对 Customers 和 Orders 表进行了几项更改。然而,公司正在迁移到新的数据库系统,数据已经复制,并且已向所有销售代表发送了关于更改和使用新数据库并停止使用 Northwind 数据库的备忘录。
现在我刚想起来,我需要撤销我的更改,否则正在进行的迁移过程可能会由于数据库的参照完整性受到损害而失败。我可以通过以下方式做到这一点:a) 不点击保存数据按钮;b) 重新从数据源获取数据,就像窗体最初加载时一样。
要实现刷新,我们将遵循两个主要步骤。首先,我们将一个按钮添加到窗体的 ToolStrip
控件,然后我们将一个处理程序添加到窗体代码中。该处理程序将与 Microsoft 演练中添加的 Form1_Load
事件处理程序非常相似。要实现此功能,您将需要一个工具栏按钮的图像。我在图 5 中提供了一个,这是我从另一个程序用户界面的屏幕截图中截取的。它似乎很合适。
接下来,执行以下操作
- 在浏览器中,右键单击图 5 中的图像,并将其保存到计算机上的文件,最好保存到 PresentationTier 项目文件夹中。
- 打开解决方案资源管理器并双击 Form1.cs 图标以在设计器中打开窗体。
- 点击窗体
ToolStrip
控件中保存数据按钮右侧的空白区域。一个新的工具栏按钮出现。 - 右键单击新按钮,然后单击设置图像。出现选择资源对话框。单击导入按钮,浏览到您下载的按钮图像存储在计算机上的位置,然后单击确定直到您返回到窗体。
- 右键单击新按钮,然后单击属性。按如下设置其属性
- 将(名称)属性设置为
refreshButton
。 - 将文本和工具提示文本属性都设置为
Refresh
。 - 将 ImageTransparentColor 属性设置为
White
。
- 将(名称)属性设置为
- 双击新按钮,跳转到代码编辑器中新添加的按钮 Click 事件处理程序的位置。
- 添加清单 3 中显示的代码以实现该按钮。
private void refreshButton_Click(object sender, EventArgs e) { try { // If the user happens to have a row in Edit mode at the time they click // Refresh, end the edit mode so there are no database conflicts. this.customersBindingSource.EndEdit(); this.ordersBindingSource.EndEdit(); // Now we connect to the WCF Service and, through the service // access the data source and merge the data from the data source // into the DataGridView ServiceReference1.Service1Client DataSvc = new ServiceReference1.Service1Client(); northwindDataSet.Merge(DataSvc.GetCustomers()); northwindDataSet.Merge(DataSvc.GetOrders()); } catch (Exception ex) { ShowStopError(ex.Message); } }
清单 3. 实现刷新按钮的代码。
- 按 F5 键生成并运行程序。对任一表中的一个或多个单元格进行更改或添加一行或多行。然后单击刷新按钮。您所做的任何更改都将丢失,并且表格将完全显示数据源中的数据。
旁注:使用窗体调整控件大小
您可能已经注意到,我们最近两次通过按 F5 键构建和运行代码时,您可以通过拖动边框来调整窗体的大小。但是,DataGridView
控件不会随窗口更改大小。暂时停下来添加此功能可能是可取的,因为两个网格控件都会滚动,并且可能有人会调整窗体大小以在同一视图中显示更多信息。
要使用窗体调整网格控件的大小,请执行以下操作
- 在 Visual Studio 中,打开解决方案资源管理器。
- 在解决方案资源管理器中,找到并双击 Form1.cs 图标,以在设计器中打开项目的窗体。
- 右键单击显示 Northwind 数据库 Customers 表数据的
DataGridView
,然后单击属性。 - 将 Anchor 属性设置为
Top, Left, Right
。 - 右键单击显示 Northwind 数据库 Orders 表数据的
DataGridView
控件(我将它放在窗体底部),然后单击属性。 - 将 Anchor 属性设置为
Top, Bottom, Left, Right
。 - 按 F5 键生成项目,执行程序并显示窗体。
到目前为止,在测试项目时,请注意,当您通过拖动其边框来调整窗体大小时,“客户”和“订单”网格视图也会随之调整大小。您还可以通过单击新的“保存数据”按钮将更改保存到数据源,并且可以使用“刷新”按钮从数据源刷新数据。我们快完成了。现在我们添加筛选。
筛选数据
注意“客户”表中的 CompanyName
列。每个公司都是 Northwind 的客户,并且每个公司都向 Northwind 下了各种订单。我想选择一个特定的公司,并仅从“订单”表中调出包含该客户订单的记录。但是,我们也应该保留查看“订单”表所有记录的功能,以防万一。
要做到这一点,我们将不得不修改应用程序的不同层并更新所有代码。首先,我们更新数据集的设计,以在 DataAccessTier 项目的 OrdersTableAdapter
中添加一个新查询,然后我们向 WCF 服务添加方法以按客户 ID 筛选订单,添加一个方法以获取 Orders 表中的所有记录。最后,我们更新 PresentationTier 项目窗体的用户界面,以允许用户选择要筛选的客户,最后,将所有内容在代码中连接起来。
我们开始工作吧。请执行以下操作
- 单击 Visual Studio 的“窗口”菜单,然后单击“关闭所有文档”。
- 在解决方案资源管理器中,双击 NorthwindDataSet.xsd 文件以在设计器中打开数据集。图 6 显示了打开的数据集。图 6. 在设计器中打开的 NorthwindDataSet.xsd 文件。
- 右键单击 OrdersTableAdapter,然后单击添加查询。
- 出现TableAdapter 查询配置向导。
- 单击两次下一步以接受默认设置。
- 在“指定 SQL SELECT 语句”页面上,将显示的 SQL 查询更改为清单 4 中的样子。
SELECT OrderID, CustomerID, EmployeeID, OrderDate, RequiredDate, ShippedDate, ShipVia, Freight, ShipName, ShipAddress, ShipCity, ShipRegion, ShipPostalCode, ShipCountry FROM dbo.Orders WHERE CustomerID = @CustomerID
清单 4. 用于选择筛选数据的 SQL 查询字符串。请注意
WHERE
子句中的@CustomerID
参数。在此向导中,您只需键入带有@
符号的参数即可向查询添加参数。此参数为用户在后续步骤中指定的信息提供了一个占位符。 - 单击下一步接受查询字符串。在“选择要生成的方法”页面中,将默认方法名称分别替换为
FillByCustomerID
和GetOrdersByCustomerID
。 - 单击完成退出向导。单击“文件”菜单,然后单击“全部保存”。
- 通过单击生成菜单,然后单击生成解决方案来更新解决方案中的文件。
- 在解决方案资源管理器中的 DataService 项目下,双击 IService1.cs 和 Service1.cs 文件,在代码编辑器中打开它们。
- 在 IService1.cs 文件中,添加清单 5 中以粗体显示的几行代码(您完成 Microsoft 演练项目时它应该已经如此)。
namespace DataService { [ServiceContract] public interface IService1 { [OperationContract] DataEntityTier.NorthwindDataSet.CustomersDataTable GetCustomers(); [OperationContract] DataEntityTier.NorthwindDataSet.OrdersDataTable GetOrders(); [OperationContract] DataEntityTier.NorthwindDataSet.OrdersDataTable GetOrdersByCustomerID( string CustomerID); } }
清单 5. 要添加到
IService1
数据服务接口的代码,用于获取具有特定CustomerID
的所有订单。 - 打开 Service1.cs 文件,并在类实现的末尾添加以下代码
public DataEntityTier.NorthwindDataSet.OrdersDataTable GetOrdersByCustomerID( string CustomerID) { DataAccessTier.NorthwindDataSetTableAdapters.OrdersTableAdapter OrdersTableAdapter1 = new DataAccessTier.NorthwindDataSetTableAdapters.OrdersTableAdapter(); return OrdersTableAdapter1.GetOrdersByCustomerID(CustomerID); }
清单 6.
GetOrdersByCustomerID
方法的实现。 - 点击构建菜单,然后点击构建解决方案以构建解决方案并更新解决方案不同层之间的各种引用。现在,我们准备实现用户界面。
- 在解决方案资源管理器中,导航到 PresentationTier 项目,然后双击 Form1.cs 文件以在设计器中打开项目的用户界面。
- 调整窗体大小,以便在窗体上的两个
DataGridView
控件之间留出更多空间。 - 打开“工具箱”,并将
ComboBox
控件拖到DataGridView
之间的空白区域。 - 在属性窗口中,将
ComboBox
的(名称)属性设置为filterBox
。 - 将
ComboBox
控件的DropDownWidth
属性设置为225
。我们将在此控件中显示需要额外宽度的项目。 - 单击
ComboBox
右上角的小右箭头(如下所示)以显示ComboBox 任务窗格。图 7. 打开 ComboBox 任务窗格。 - 如上所示,单击ComboBox 任务窗格中的使用数据绑定项复选框。
- 将数据源属性设置为
customersBindingSource
。
- 我们希望组合框是用户友好的,并为每个客户显示公司名称,但每个选择都应该映射到每个公司名称对应的
CustomerID
值,因为这是我们筛选的依据。
- 我们希望组合框是用户友好的,并为每个客户显示公司名称,但每个选择都应该映射到每个公司名称对应的
- 将 Display Member 属性设置为
CompanyName
,将 Value Member 属性设置为CustomerID
。
ComboBox
允许用户编辑当前选定的项目。不希望出现这种行为,因为如果用户不小心修改了框中显示的项目,可能会破坏底层数据源的参照完整性。因此,我们修改ComboBox
的属性,以防止用户编辑当前选定项目的文本。
- 将
ComboBox
的 DropDownStyle 属性更改为DropDownList
。
接下来,我们通过添加两个按钮使过滤器生效:一个名为Go的按钮(当时觉得很合适),另一个名为Show All Orders的按钮。用户在选择要筛选订单的客户后,应单击Go按钮。Show All Orders按钮将显示重置为显示Orders表的全部内容,不带任何过滤器。
- 从“工具箱”中,将两个按钮控件拖到窗体上,放置在
ComboBox
的右侧。为它们指定您认为合适的(名称)属性,但将这两个按钮的标题(通过设置每个按钮的文本属性)分别设置为Go和显示所有订单,使它们如下图所示图 8. 用于筛选的控件。 - 确保上面显示的控件位于窗体上已有的两个
DataGridView
控件之间。 - 双击Go按钮并添加以下代码
private void btnGo_Click(object sender, EventArgs e) { try { this.UseWaitCursor = true; northwindDataSet.Orders.Clear(); ServiceReference1.Service1Client DataSvc = new ServiceReference1.Service1Client(); northwindDataSet.Merge(DataSvc.GetCustomers()); northwindDataSet.Orders.Merge(DataSvc.GetOrdersByCustomerID( filterBox.SelectedValue.ToString())); this.UseWaitCursor = false; } catch (Exception ex) { ShowStopError(ex.Message); } }
清单 7. 实现Go按钮以执行数据库筛选的代码。
- 在解决方案资源管理器中,打开 PresentationTier 项目并右键单击服务引用文件夹。
- 右键单击
ServiceReference1
图标,然后单击更新服务引用。
- 由于我们更改了提供数据源访问的 WCF 服务中的代码,因此必须刷新信息和客户端代码,以便我们调用服务的方法。
- 最后,我们来到演练的最后一步。在代码编辑器中任意位置右键单击,然后单击查看设计器以设计模式打开窗体。
- 双击“显示所有订单”按钮,在窗体代码中添加一个骨架事件处理程序。
- Visual Studio 会在代码编辑器中打开窗体代码,并将光标放置在新创建的事件处理程序主体内。
- 在新创建的事件处理程序中,添加以下清单 9 中的代码
private void btnShowAll_Click(object sender, EventArgs e) { try { this.UseWaitCursor = true; northwindDataSet.Orders.Clear(); ServiceReference1.Service1Client DataSvc = new ServiceReference1.Service1Client(); northwindDataSet.Merge(DataSvc.GetCustomers()); northwindDataSet.Orders.Merge(DataSvc.GetOrders()); this.UseWaitCursor = false; } catch (Exception ex) { ShowStopError(ex.Message); } }
清单 9. 实现显示所有订单按钮的代码。
- 在解决方案资源管理器中,在 PresentationTier 项目下,双击 app.config 文件。
- 查找文本
maxReceivedMessageSize
并确保其值设置为6553600
。 - 就是这样!按 F5 键测试解决方案。在列表中选择一个公司名称,单击Go以在下方的
DataGridView
中查看该公司的订单,然后单击显示所有订单以在下方的DataGridView
中显示 Orders 表的全部内容。
恭喜。您已完成 Microsoft 演练的此扩展部分,现在拥有一个功能齐全的迷你数据库应用程序,它具有 N 层架构。将此示例用作您未来数据库开发的模板!
关注点
在离开之前,有一些兴趣点。实现数据保存相对简单。请注意,我们无需修改 WCF 服务公开的底层方法来促进实现;所有真正必要的只是向 PresentationTier 窗体添加更多组件。通过刷新工具栏按钮实现数据刷新主要涉及复制一些用于实现 Form1_Load
事件处理程序的代码。
本文的重点是实现筛选。请注意,添加筛选器需要更改此应用程序的所有不同层。首先,修改了 DataAccessTier 项目中底层的 TableAdapter
对象。接下来,我们更新了 WCF 服务公开的接口,以添加调用 OrdersTableAdapter
对象中查询的方法。最后,我们向 PresentationTier 项目的窗体添加了控件和代码,以便为用户提供应用筛选器的方法。
和往常一样,程序员的工作永无止境。这个项目还有更多任务和扩展可以进行。其中包括
- 添加验证。演示此功能的演练是“演练:向 N 层数据应用程序添加验证”文章,可在 MSDN 上找到。
- 找出各种部署和扩展场景如何工作,以便此应用程序的各个层可以放置在不同的计算机和服务器上,所有这些都通过网络连接。
- ……而且我相信还有其他几种可能的扩展可以完成。想出一些并告诉我们!
希望本文有所帮助。我确实提供咨询服务;如果您正在寻找贵公司的数据库编程和用户界面专家,或者技术文档 SME,请给我发送电子邮件,让我们一起合作一个项目。
历史
2009 年 1 月 11 日:文章完成。