九、构建 Windows 窗体应用
本章介绍 Windows 窗体以及如何使用 C# 2012 开发 Windows 窗体应用。
在本章中,我们将介绍以下内容:
- Understanding Windows forms
- User interface design principles
- User interface design best practices
- Use Windows forms
- Understanding design and code views
- Sort properties in the properties window.
- Set the properties of solutions, projects and Windows forms
- Using controls
- Set docking and anchor properties
- Add a new form to the project
- Implement MDD
了解 Windows 窗体
Windows 窗体,也称为 WinForms,是图形用户界面(GUI)应用编程接口(API)的名称,是微软的一部分。NET Framework,通过在托管代码中包装现有的 Windows API 来提供对本机 Microsoft Windows 界面元素的访问。
WinForms 是用户界面的基本构件。它们作为容器来承载允许您呈现应用的控件。WinForms 是应用开发中最常用的界面,尽管其他类型的应用也是可用的,如控制台应用和服务。但是 WinForms 提供了与用户交互的最佳方式,并接受按键或鼠标点击形式的用户输入。
用户界面设计原则
与任何应用交互的最佳机制通常是用户界面。因此,拥有易于使用的高效设计变得非常重要。在设计用户界面时,你首先要考虑的应该是使用这个应用的人。他们是你的目标受众,了解你的目标受众可以让你更容易地设计用户界面,帮助用户学习和使用你的应用。另一方面,一个设计糟糕的用户界面,如果导致目标受众回避甚至放弃你的应用,会导致沮丧和低效。
窗体是 Microsoft Windows 应用的主要元素。因此,它们为每个级别的用户交互提供了基础。可以将各种控件、菜单等添加到窗体中,以提供特定的功能。除了功能性之外,你的用户界面应该对用户有吸引力。
用户界面设计的最佳实践
用户界面为用户提供了与应用交互的机制。因此,易于使用的高效设计至关重要。下面是一些设计用户友好、优雅和简单的用户界面的指导原则。
简单
简单是用户界面的一个重要方面。视觉上“繁忙”或过于复杂的用户界面使得学习应用更加困难和耗时。用户界面应该允许用户快速完成程序所需的所有交互,但是它应该只暴露应用每个阶段所需的功能。在设计用户界面时,你应该记住程序的流程和执行,这样你的应用的用户会发现它很容易使用。显示相关数据的控件应该在表单上组合在一起。ListBox、ComboBox 和 CheckBox 控件可用于显示数据,并允许用户在预设选项之间进行选择。
使用 tab 键顺序(用户通过按 Tab 键在表单上的控件间循环的顺序)允许用户快速导航字段。
在设计用户界面时,试图重现真实世界的物体是一个常见的错误。例如,如果您想创建一个代替纸质表单的表单,那么很自然地会尝试在应用中复制纸质表单。这种方法可能适用于某些应用,但对于其他应用,它可能会限制应用,并且不会给用户带来真正的好处,因为复制纸质表单会限制应用的功能。设计应用时,考虑您的独特情况,并尝试使用计算机的功能来增强目标受众的用户体验。
默认值是简化用户界面的另一种方式。例如,如果您希望应用的 90%的用户在州字段中选择华盛顿,请将华盛顿作为该字段的默认选项。
设计用户界面时,来自目标受众的信息是最重要的。设计用户界面时最好的信息是来自目标受众的输入。定制您的界面,使频繁的任务易于执行。
控制位置
用户界面上控件的位置应该反映它们的相对重要性和使用频率。例如,如果你有一个既用于输入必需信息又用于输入可选信息的表单,那么必需信息的控件就更重要,应该得到更大的重视。在西方文化中,用户界面通常被设计成从左到右和从上到下阅读。最重要或最常用的控件最容易在窗体顶部访问。用户完成表单上的操作后将使用的控件,如提交按钮,应该遵循信息的逻辑流程,并放在表单的底部。
还需要考虑信息的相关性。相关信息应该显示在组合在一起的控件中。例如,如果您有一个显示有关客户、采购订单或雇员信息的窗体,您可以将每组控件分组到一个选项卡控件上,使用户可以轻松地在显示之间来回移动。
美观也是放置控件的一个重要考虑因素。您应该尽量避免显示比一目了然更多信息的表单。只要有可能,控件之间应该留有足够的空间,以创造视觉吸引力和易用性。
一致性
您的用户界面应该在应用中的每个表单上展示一致的设计。不一致的设计会让你的应用看起来杂乱无章,阻碍你的目标用户的采用。当用户在表单间导航时,不要要求他们适应新的视觉元素。
一致性是通过在整个应用中使用颜色、字体、大小和控件类型来实现的。在进行任何实际的应用开发之前,您应该决定一个在整个应用中保持一致的可视化方案。
美学
只要有可能,用户界面应该是吸引人的和令人愉快的。虽然清晰和简单不应该为了吸引人而牺牲,但是你应该努力创建一个不会阻止用户使用它的应用。
颜色
明智地使用颜色有助于让你的用户界面吸引目标受众,并吸引他们使用。然而,过度使用颜色是很容易的。鲜艳的颜色可能会吸引一些用户,但其他人可能会有负面反应。为应用设计背景配色方案时,最安全的做法是使用具有广泛吸引力的柔和颜色。
总是研究任何与颜色相关的特殊含义,这些含义可能会影响用户对你的应用的反应。如果你正在为一家公司设计应用,你可以考虑在你的应用中使用该公司的公司配色方案。为国际观众设计时,要意识到某些颜色可能具有文化意义。保持一致性,不要过度使用颜色。
总是考虑颜色如何影响可用性。例如,白色背景上的灰色文本可能难以阅读,从而削弱可用性。此外,请注意与色盲相关的可用性问题。例如,有些人不能区分红色和绿色。因此,这类用户看不到绿色背景上的红色文本。不要仅仅依靠颜色来传达信息。对比也能吸引人们对应用中重要元素的注意。
来源
可用性应该决定你为应用选择的字体。为了方便使用,避免使用难以阅读或修饰过的字体。坚持使用简单易读的字体,如 Palatino 或 Times New Roman。此外,与其他设计元素一样,字体应该在整个应用中保持一致。使用草书或装饰性字体只是为了视觉效果,比如在合适的时候用在扉页上,不要传达重要信息。
图像和图标
图片和图标增加了应用的视觉趣味,但是精心的设计对它们的使用是必不可少的。看起来“忙碌”或分散用户注意力的图像会阻碍你的应用的使用。图标可以传达信息,但同样,在决定使用它们之前,需要仔细考虑最终用户的反应。例如,您可以考虑使用类似于美国停止标志的红色八角形来表示用户可能不想在应用中超过该点。只要有可能,图标应该保持简单的形状,容易在 16x 16 像素的正方形中呈现。
使用 Windows 窗体
要使用 Windows 窗体,您需要使用 Visual Studio 2012 创建一个 Windows 窗体应用项目。为此,请单击开始所有程序 Visual Studio 2012,并从显示的列表中选择 Microsoft Visual Studio 2012。这将打开 Visual Studio 起始页。点击文件新建
项目。现在你会看到新建项目对话框,你可以从中选择 Windows 窗体应用模板,如图图 9-1 所示。
***图 9-1。*选择 Windows 窗体应用项目模板
默认情况下,该项目被命名为 WindowsFormsApplication1(下一个是 WindowsFormsApplication2,依此类推)。选择项目模板时,您可以在“名称”文本框中为项目输入另一个名称,也可以在以后重命名项目。
一旦选择了 Windows 窗体应用模板以及所需的名称和位置,请单击“确定”。这将打开 Visual Studio 集成开发环境(IDE ),之所以这样称呼是因为它将所有与开发相关的工具、窗口、对话框、选项等嵌入(或集成)在一个公共窗口中,这使得开发过程更加容易。
在 IDE 中,当您打开项目时,您会看到一个名为Form1.cs
的 Windows 窗体已被添加,并且在右侧您还可以看到 Solution Explorer 窗口。您还需要了解另一个名为属性窗口的窗口。如果“解决方案资源管理器”窗口下的“属性”窗口不可用,可以通过单击“查看属性窗口”或按 F4 打开它。现在开发环境将看起来像图 9-2 。
***图 9-2。*带有解决方案资源管理器和属性窗口的 IDE
因为这是一个 Windows 窗体应用项目,所以您将使用允许您以 GUI 形式实现功能的控件或工具。您可以从工具箱中选取控件,工具箱显示在开发环境中 Windows 窗体的左侧。如果将鼠标指针悬停在工具箱选项卡上,将会打开工具箱窗口。展开所有 Windows 窗体工具集,如图图 9-3 所示。您可以从那里选取控件并将其放在 Windows 窗体的表面上。
***图 9-3。*带工具箱的 IDE
理解设计和代码视图
在 Visual Studio IDE 中,您主要处理两个视图:设计视图和代码视图。当你打开 Visual Studio IDE 时,默认情况下它会显示设计视图,如图图 9-3 所示。“设计”视图允许您将控件拖放到窗体上。您可以使用“属性”窗口设置对象和窗体或解决方案资源管理器中显示的其他文件的属性。解决方案资源管理器还允许您重命名项目、窗体甚至项目中包含的其他文件。通过选择这些对象,单击鼠标右键,然后从上下文菜单中选择“重命名”,可以重命名这些对象。
基本上,“设计”视图为您提供了一种处理控件、对象、项目文件等的可视化方式。当您使用代码来实现位于 Windows 窗体表面的可视化控件背后的功能时,您会希望使用 Visual Studio IDE 中的另一个可用视图“代码”视图。
若要从“设计”视图切换到“代码”视图,请单击“查看代码”,或者在“设计”视图中右击 Windows 窗体并选择“查看代码”。这两种方法都会为你打开代码视图,如图图 9-4 所示。
***图 9-4。*代码视图
代码视图显示所有代码功能。在图 9-4 中,注意Form1.cs
页签(你看到的是代码视图)在 Form1.cs【设计】页签旁边,它实际上是 Windows 窗体 Form1 的设计视图;这些选项卡允许您在“设计”视图的所有 GUI 元素和“代码”视图中帮助您实现功能的相关代码之间切换。有趣的是,如果您尝试在代码视图中访问工具箱,您会看到工具箱中没有任何控件。但是当您切换回设计视图时,您会发现工具箱已经完全加载了控件。
若要切换回“设计”视图,请在“代码”视图中右击窗体,然后选择“视图设计器”。您将会看到,现在您回到了设计视图,并且可以继续使用可视元素或控件。
您还可以使用解决方案资源管理器在“设计”和“代码”视图之间切换,方法是选择所需的 Windows 窗体(如果您打开了多个 Windows 窗体),右击并选择“查看代码”或“视图设计器”。这将打开所选 Windows 窗体的代码视图或设计视图。
在属性窗口中排序属性
每个对象(如表单控件)都有许多属性,您可能需要在使用任何应用时设置这些属性。为了帮助您浏览“属性”窗口中列出的许多属性,您可以按类别或字母顺序对它们进行排序。让我们来看看这些排序选项中的每一个。
分类视图
分类视图以属性集的形式组织属性,每个属性集都有一个名称来描述属性集合;例如,有名称为“外观”、“行为”、“数据”、“设计”、“焦点”等类别。通过单击显示在“属性”窗口顶部的工具栏最左边的图标,可以切换到分类视图。
在显示分类视图的图-9-5 中,在外观类别下,您将看到所有定义对象(在本例中是一个表单)外观和感觉的属性。注意其他类别也显示在图 9-5 中。
注在图 9-5 中,我们有意将其他类别保持在折叠模式,只是为了向您展示所有类别。当您切换到 Categorized 视图时,您会看到默认情况下所有的类别都是展开的。
***图 9-5。*房产分类视图
按字母顺序查看
字母视图按名称从 a 到z升序组织属性。您可以通过单击属性窗口顶部工具栏上左起第二个图标切换到字母视图。
在显示该视图的图 9-6 中,所有列出的属性按字母顺序排列。使用按字母顺序排列的视图,而不是按类别排列的视图,会使工作变得更加容易。例如,假设您正在寻找字体属性。在分类视图中,您必须知道该属性位于哪个类别下才能找到它。但是,如果您有按字母顺序组织的属性,您可以很容易地找到这个属性,因为它是以字母 F 开始的,所以您知道是否需要后退或前进来找到您的控件的这个属性。
***图 9-6。*按字母顺序排列的房产视图
设置解决方案、项目和 Windows 窗体的属性
在你开始在 Windows 窗体上放置控件之前,你需要学习如何修改你之前创建的解决方案、项目和窗体的一些属性值(如前面的图 9-2 所示)。
选择 WindowsFormsApplication1 解决方案,转到属性窗口,将其 Name 属性值设置为 Chapter9 。
注意在某些情况下,您可能无法在 Visual Studio 中看到解决方案(
.sln
)文件。要列出一个解决方案文件,比如解决方案第九章(1 项目),如图图 9-7 所示,你必须点击工具选项,转到项目和解决方案选项卡,选择常规,勾选“总是显示解决方案”选项,然后点击确定。
在解决方案资源管理器中选择 WindowsFormsApplication1 项目,转到“属性”窗口,并修改定义项目文件名的项目文件属性值,使其显示为WinApp.csproj
。
现在更改 Windows 窗体的名称:在解决方案资源管理器中选择Form1.cs
,在属性窗口中将文件名属性从Form1.cs
修改为WinApp.cs
,并在出现的对话框中单击“是”。
现在单击位于解决方案资源管理器窗口中的 Form1。一旦选择了 Form1,您将在“属性”窗口中看到属性列表已经更改。选择 Text 属性,并将其值从 Form1 修改为 Windows 应用。Text 属性定义显示在窗体标题栏上的名称。
在设置了解决方案、项目和 Windows 窗体的属性后,IDE 将看起来像图 9-7 。
***图 9-7。*在设置了解决方案、项目和 Windows 窗体的属性后,使用 IDE
使用控件
现在已经有了 Windows 窗体应用,可以开始使用控件了。
任何 Windows 应用的基本元素都是控件,它通过提供嵌入在应用中的代码功能的视觉含义来发挥重要作用。
最常用的控件是标签、按钮、文本框、单选按钮、复选框、列表框、组合框、MenuStrip 和 ContextMenuStrip。没有这些控件,应用就无法存在,所以您将看到如何将其中一些控件合并到您的应用中。
试试看:使用标签、文本框和按钮控件
在本练习中,您将创建一个带有三个标签、两个文本框和一个按钮的 Windows 窗体应用。应用将接受你的名字作为输入,然后以对话框的形式闪现一条“欢迎”消息。
-
转到您之前创建的名为 Chapter9 的解决方案下名为 WinApp 的项目(参见图 9-7 )。确保您处于设计视图中。
-
Drag a Label control onto the form, and position it at the top middle of the form. Select this label, navigate to the Properties window, and set the following properties:
- 将 Name 属性设置为 lblWelcome。
- 将 Text 属性设置为 Welcome。
- 选择“字体”属性,单击省略号按钮,并在“大小”下拉列表中将 Label 控件的大小指定为 16 磅。
- 将 TextAlign 属性设置为 TopCenter。
提示你也可以双击工具箱中的任意控件将其添加到表单中。拖动控件和双击控件的区别在于,在拖动时,可以根据需要在窗体上定位控件。但如果你只是双击一个控件,它会被添加到左上角;所以,如果你更喜欢它在不同的位置,你还是要把它拖到那里。
-
将另外两个 Label 控件拖动到表单上,并将其放在“Welcome”文本的下方,稍微靠近表单的左侧。选择第一个标签,导航到“属性”窗口,将 Name 属性设置为 lblFirstName,将 Text 属性设置为 FirstName。
-
现在选择第二个标签,导航到“属性”窗口,将其 Name 属性设置为 lblLastName,将其 Text 属性设置为 LastName。
-
将两个 textBox 控件拖到窗体上,并将名为 textBox1 的 TextBox 放在名字标签的前面,将名为 textBox2 的 TextBox 放在姓氏标签的前面。
-
选择 textBox1,转到“属性”窗口,将其 Name 属性设置为 txtFname。选择 textBox2,并在“属性”窗口中将其 Name 属性设置为 txtLname。
-
Drag a Button control onto the form, and place it below the Label and TextBox controls. Select the Button control, go to the Properties window, change the Name property to btnSubmit, and then set its Text property to Submit.
现在,您已经准备好了应用的 GUI 设计;它应该类似于图 9-8 中所示的形式。
***图 9-8。*Windows 应用表单的 GUI 设计
是时候添加功能并切换到代码视图了。您将读入用户提供的名字和姓氏值,并在单击 Submit 按钮时显示一条消息,这意味着您需要将所有功能放在 Submit 按钮的 click 事件之后,该事件最终将从 TextBox 控件中读取值。为此,请继续执行以下步骤:
-
Double-click the Submit button. This will take you to the Code view, and you will see that the
btnSubmitClick
event template has been added to the Code view window. Now you will add the code to show a dialog box, with a greeting and welcome message for the entered first name and last name. To do so, you will useMessageBox
class; this class provides aShow()
function to display a dialog box with the provided information. Now let’s add the following code inside thisbtnSubmitClick
event to achieve the desired functionality of a dialog, with a message, a caption in dialog box’s title bar, an OK button, a Cancel button, and an information icon displayed:MessageBox.Show("Hello" + ' ' + txtFname.Text + ' ' + txtLname.Text + ' ' + "Welcome to the Windows Application","Welcome", MessageBoxButtons.OKCancel, MessageBoxIcon.Information);
现在你的代码视图将显示按钮的点击事件代码,如图图 9-9 所示。
***图 9-9。*用
MessageBox.Show
代码查看您的Button
点击事件 -
现在,单击 Build Build Solution,并确保在输出窗口中看到以下消息:
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========
-
现在是运行和测试应用的时候了。为此,请按 Ctrl+F5。Visual Studio 2012 将加载该应用。
-
在名字和姓氏文本框中输入值,然后单击提交按钮。您将看到类似于图 9-10 中所示的信息。
***图 9-10。*运行 Windows 应用表单
它是如何工作的
Visual Studio 附带了许多功能来帮助开发人员编写代码。其中一个特性是,您只需双击要添加代码的 GUI 元素,就会在代码视图中找到与该 GUI 元素相关的代码。例如,当您双击设计视图中的提交按钮时,您将被带到代码视图,并且自动生成btnSubmitClick
事件模板。
若要实现此控件的功能,请添加以下代码:
MessageBox.Show("Hello" + ' ' + txtFname.Text + ' ' + txtLname.Text + ' ' + "Welcome to the Windows Application","Welcome", MessageBoxButtons.OKCancel, MessageBoxIcon.Information);
MessageBox.Show()
是一个基于提供的参数弹出消息框的. NET Windows 窗体方法。要在消息框中显示带有用户指定的名字和姓氏的“欢迎”消息,您可以在编写代码时应用字符串串联方法。
在代码段中,您对消息“Hello Welcome to the Windows Application”进行了硬编码,但是用户的名字和姓氏出现在单词 Hello 之后,并与消息的其余部分“Welcome to the Windows Application”连接在一起
为了提高可读性,您还在从txtFnam
和txtLname
的Text
属性中读取的单词和值之间添加了由+
操作符的实例连接的单个空格字符(''
)。如果在字符串连接过程中不包括单个空格字符(''
),单词将会相互交错,消息框中显示的消息将难以阅读。
您得到的第二个参数是Caption
,它是对话框的标题。我们将其硬编码为“欢迎”,然后通过MessageBoxButtons.OKCancel
选择按钮集。我们传递的最后一个参数是MessageBoxIcon
,并使用了一个信息类型图标。
注意
MessageBox.Show()
是非常强大和方便的功能;您可能希望更多地使用智能感知为MessageBoxButtons
和MessageBoxIcon
类型参数显示的各种选择。
设置停靠和锚属性
在 Visual Studio 2005 之前,调整 Windows 窗体的大小需要您重新定位和/或调整这些窗体上的控件。例如,如果在窗体的左侧有一些控件,并且您试图通过向右侧拉伸或向左侧拉回来调整窗体的大小,这些控件不会根据调整后的窗体的宽度自动调整。开发人员必须编写相应的代码来改变控件,以适应用户调整表单的大小。这种技术代码量很大,不容易实现。
从 Visual Studio 2005 开始,出现了两个新的属性,Anchor 和 Dock,它们在设计时很容易设置。Visual Studio 2012 提供了相同的 Dock 和 Anchor 属性,它们解决了用户在调整窗体大小时面临的控件行为问题。
码头属性
Dock 属性允许您将控件附加到其父控件的一个边缘。术语 parent 适用于 Windows 窗体,因为 Windows 窗体包含您拖放到其上的控件。默认情况下,任何控件的 Dock 属性都设置为 None。
例如,停靠在窗体上边缘的控件将始终连接到窗体的上边缘,并且当调整其父控件的大小时,它将自动左右调整大小。
可以使用属性窗口中提供的图形界面来设置控件的 Dock 属性,如图图 9-11 所示。
***图 9-11。*设置 Dock 属性
锚属性
当用户调整窗体大小时,控件在 Anchor 属性的帮助下保持与其父窗体边缘的恒定距离。任何控件的 Anchor 属性的默认值都设置为 Top,Left,这意味着该控件将与窗体的上边缘和左边缘保持恒定的距离。可以使用属性窗口中提供的图形界面来设置 Anchor 属性,如图图 9-12 所示。
由于 Anchor 属性的默认设置为 Top,Left,如果您试图通过向右侧拉伸窗体来调整其大小,您将看到其控件仍然位于左侧,而不是在调整大小后移动到窗体的中心来调整窗体的大小。
如果在 Anchor 属性中同时设置了相对的边缘(例如左边缘和右边缘),则当调整窗体大小时,控件将拉伸。但是,如果在 Anchor 属性中没有设置相对的边缘,控件将在调整父级大小时浮动。
***图 9-12。*设置锚点属性
试试看:使用停靠和锚定属性
在本练习中,您将使用在本章前面创建的名为 WinApp 的现有 Windows 窗体应用。您将看到如何以这样的方式修改这个应用,即当您调整表单大小时,它的控件会相应地运行,并保持应用对用户的可呈现性。
转到解决方案资源管理器并打开 WinApp 项目。在设计视图中打开 WinApp 窗体。
-
通过单击窗体的标题栏来选择窗体;您将看到窗体边框周围的手柄,这些手柄允许您调整窗体的高度和宽度。
-
将光标放在右边框的手柄上,当鼠标指针变成双头时,单击并向右侧拉伸表单。您会看到窗体的宽度增加了,但是控件仍然连接在窗体的左上角。
-
Similarly, grab the handle located on the bottom of the form and try to increase the height of the form. You will notice that the controls are still attached to the top side of the form.
看一下图 9-13 ,它显示了调整后的(高度和宽度)表单和控件的位置。这些控件出现在左上角,因为它们的 Dock 属性值为 None,而它们的 Anchor 属性值为 top,left。
***图 9-13。*调整控件的形状和位置
现在,您将尝试设置控件的 Dock 和 Anchor 属性,然后重新测试应用。
-
选择名为 lbl Welcome 的标签控件,并将文本值设置为 Welcome。转到属性窗口。选择 AutoSize 属性,并将其值设置为 False(默认值为 True)。
-
将 Label 控件的宽度调整为窗体的宽度,并将 Label 控件调整为窗体的上边框。将该控件的 TextAlign 属性设置为顶部居中。
-
将 Label 控件的 Dock 属性从 None 设置为 Top,这意味着您希望标签始终贴在窗体的上边框上。
-
现在选择所有剩余的控件(两个标签、两个文本框和一个按钮),方法是在按住鼠标左键的同时滚动所有控件,或者在按住 Shift 或 Ctrl 键的同时单击选择每个控件。
-
选择所有控件后,转到“属性”窗口。您将看到列出了您在窗体上选择的控件共有的所有属性。
-
Select the Anchor property; modify its value from the default Top, Left to Top, Left, and Right. This will allow you to adjust the controls accordingly as soon as you resize the form. The controls will also grow in size accordingly to adjust to the width of the form, as you can see in Figure 9-14.
***图 9-14。*Anchor 属性设置 Top,Left,Right 对调整大小后的表单的影响
注意主播属性有非常有趣的行为;您可以尝试以各种组合设置该属性,并在调整窗体大小时查看效果。
-
将窗体恢复到原来的大小,这样就可以看到设置另一个 Anchor 属性的效果。
-
Select all the controls again as you did in step 8. Set the Anchor property to Top only and try resizing the form now. You will notice that the controls are floating in the middle of the form when you resize it, as you can see in Figure 9-15.
***图 9-15。**在调整大小后的窗体上设置 Top 的 Anchor 属性的效果*
- 点击文件
全部保存,保存项目中的更改。
它是如何工作的
当您调整窗体大小时,它将根据 Dock 和 Anchor 属性的设置进行操作。
在第一个实例中,将 Label 控件的 Dock 属性设置为 Top,这允许将该 Label 控件附加到窗体的上边框,并跨越窗体的整个宽度。将其余控件的 Anchor 属性设置为 Top、Left 和 Right 可以移动控件,使它们与窗体的左右边框保持恒定的距离。
向项目添加新表单
任何现实世界或企业应用显然都需要多个 Windows 窗体来执行业务功能。默认情况下,每个项目只打开一个 Windows 窗体,但您可以自由添加更多窗体。
试试看:向 Windows 项目添加一个新窗体
在本练习中,您将向项目中添加另一个 Windows 窗体。您还将使用 ListBox、ComboBox、RadioButton 和 CheckBox 控件。在这个新表单中,您将分别向 ListBox 和 ComboBox 添加两个不同文本框中的数据。
- 导航到解决方案资源管理器并选择 WinApp 项目,右键单击,然后单击 Ad
Windows 窗体。这将在项目中添加一个新的 Windows 窗体。
- 在显示的添加新项目对话框中,将表单名称从
Form1.cs
更改为UserInfo.cs
。单击添加。名为 UserInfo 的新表单将被添加到您的项目中。 - 确保新添加的表单 UserInfo 已在“设计”视图中打开。通过单击表单的标题栏选择 UserInfo 表单,导航到属性窗口,并将 Size 属性的宽度设置为 455,高度设置为 251。
- 将 Label 控件拖到窗体上;选择该控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblCountry。
- 将 AutoSize 属性设置为 false。
- 将位置属性的 X 设置为 12,Y 设置为 26。
- 将 Size 属性的宽度设置为 71,高度设置为 13。
- 设置 Text 属性以输入 Country。
- 将 TextBox 控件拖到 lblCountry 标签的前面。选择该控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtCountry。
- 将位置属性的 X 设置为 97,Y 设置为 19。
- 将 Size 属性的宽度设置为 129,高度设置为 20。
- 拖动 lblCountry 下的另一个标签,选择该控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblState。
- 将 AutoSize 属性设置为 false。
- 将位置属性的 X 设置为 12,Y 设置为 65。
- 将 Size 属性的宽度设置为 60,高度设置为 13。
- 将 Text 属性设置为输入状态。
- 将 TextBox 控件拖动到 lblState 标签的前面。选择该控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtState。
- 将位置属性的 X 设置为 97,Y 设置为 58。
- 将 Size 属性的宽度设置为 129,高度设置为 20。
- 将一个 ListBox 控件拖动到已添加的 TextBox 控件右侧的 UserInfoInfo 窗体上。选择该控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lstCountry。
- 将位置属性的 X 设置为 280,Y 设置为 12。
- 将 Size 属性的宽度设置为 129,高度设置为 82。
- 在刚刚添加的 ListBox 下面拖动一个 ComboBox。选择该控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 cboState。
- 将位置属性的 X 设置为 280,Y 设置为 117。
- 将 Size 属性的宽度设置为 129,高度设置为 21。
- 拖动标签控件下面的两个复选框,并将其命名为 chkPostalMail 和 chkEMail 将它们的 Text 属性分别设置为 Postal Mail 和 E-Mail。
- 将两个 RadioButtons 拖动到 TextBox 控件的下方,并命名为 rdbMale 和 rdbFemale。将它们的文本属性分别设置为男性和女性。
- 拖动一个按钮控件到 UserInfo 表单左侧的 CheckBox 控件下方;选择该控件,导航到“属性”窗口,并设置以下属性:
1. 将 Name 属性设置为 btnAdd。
2. 将位置属性的 X 设置为 12,Y 设置为 165。
3. 将 Size 属性的宽度设置为 75,高度设置为 23。
4. 设置要添加的文本属性。 - 拖动“添加”按钮旁边的按钮控件;选择该控件,导航到“属性”窗口,并设置以下属性:
1. 将 Name 属性设置为 btnRemoveCountry。
2. 将位置属性的 X 设置为 105,Y 设置为 165。
3. 将 Size 属性的宽度设置为 95,高度设置为 23。
4. 设置 Text 属性以删除 Country。 - 拖动“删除国家/地区”按钮旁边的按钮控件;选择该控件,导航到“属性”窗口,并设置以下属性:
1. 将 Name 属性设置为 btnRemoveState。
2. 将位置属性的 X 设置为 220,Y 设置为 165。
3. 将 Size 属性的宽度设置为 86,高度设置为 23。
4. 将 Text 属性设置为移除状态。 - Drag a Button control next to the Remove State button; select this control, navigate to the Properties window, and set the following properties:
1. 将 Name 属性设置为 btnShowDetails。
2. 将位置属性的 X 设置为 327,Y 设置为 165。
3. 将 Size 属性的宽度设置为 100,高度设置为 23。
4. 设置 Text 属性以显示详细信息。
现在你已经完成了 UserInfo 表单的设计部分;当拖放控件时,你应该放置控件来创建一个视觉上吸引人的布局,如图图 9-16 所示。

***图 9-16。**UserInfo 表单的 GUI 设计*
您希望用户在文本框中添加一个名称并单击 add 按钮,然后将 country 添加到 ListBox,将 state 添加到 ComboBox。相应地,Remove 按钮将删除国家或州,Show Details 将显示使用复选框和单选按钮做出的选择。为此,您需要逐一编写所有这些按钮的`click`事件背后的代码功能。
- 双击 Add 按钮并在
btnAdd_Click
事件中编写以下代码,它将读取文本框中输入的国家和州名,并将它们添加到 ListBox 和 ComboBox 中。lstCountry.Items.Add(txtCountry.Text); txtCountry.Clear(); cboState.Items.Add(txtState.Text); txtState.Clear();
- 双击 Remove Country 按钮,并在
btnRemoveCountry_Click
事件中编写以下代码,这将从名为 lstCountry 的列表框中删除选定的国家:lstCountry.Items.Remove(lstCountry.SelectedItem);
- 双击 Remove State 按钮,并在
btnRemoveState_Click
事件中编写以下代码,这将从名为 cboState 的 ComboBox 中删除选中的状态。cboState.Items.Remove(cboState.SelectedItem);
- 双击 Show Details 按钮,并在
btnShowDetails_Click
事件中编写以下代码,这将显示通过复选框和单选按钮选择的选项。if (chkEmail.Checked == true || chkPostalMail.Checked == true && rdbMale.Checked == true) {
- MessageBox。显示(“你好先生,你将通过美国邮政或电子邮件联系”,“信息”,MessageBoxButtons。确定取消,MessageBoxIcon。信息);
} else if (chkEmail.Checked == true || chkPostalMail.Checked == true && rdbFemale.Checked == true) { MessageBox.Show("Hello Mam, you will be contacted by either USPS or email", "Information", MessageBoxButtons.OKCancel, MessageBoxIcon.Information); }
- 转到“生成”菜单,选择“生成解决方案”。您应该会看到一条消息,表明构建成功。
- 保持当前项目打开,因为下一个练习会立即用到它。(不要担心,我们将在之后解释这个和下一个练习是如何工作的。)
试试看:设置启动表单
在一个 Visual C# 项目中设置启动窗体有点棘手,所以我想把它分解成自己的练习。要设置启动表单,您需要遵循以下步骤:
1.在您在前一个练习中修改的项目中,导航到解决方案资源管理器,打开
Program.cs
文件,并查找以下代码行:
Application.Run(new WinApp());
该代码行确保 WinApp 窗体将始终是第一个运行的窗体,因为这是第一个名为 Form1 的窗体,它已经被添加,并且在开始该项目时被重命名;为了将 UserInfo 表单设置为启动表单,您需要稍微修改一下这个语句,如下所示:
Application.Run(new UserInfo());
2.生成解决方案,并通过按 Ctrl+F5 运行和测试应用。将加载 UserInfo 应用表单。
3.在相应的文本框中输入国家和州名,然后单击“添加”按钮;你会看到你输入的名字已经被添加到列表框和组合框中,如图图 9-17 所示。
***图 9-17。*使用 ListBox 和 ComboBox withUserInfo Windows 窗体应用
4.选中两个复选框和一个单选按钮,然后单击 ShowDetails 按钮。你会看到一个消息框显示出来,如图图 9-18 所示。
***图 9-18。*在 UserInfo Windows 窗体应用中使用复选框和单选按钮
它是如何工作的
让我们先来看看“在 Windows 项目中添加一个新窗体”任务,一个按钮一个按钮一行行地理解代码。
首先有一个 Add 按钮,它将国家和州添加到 ListBox 和 ComboBox 中。ListBox 和 ComboBox 控件有一个名为Items
的集合,这个集合可以包含一个项目列表,这就是为什么在这里使用它。接下来调用Items
集合的Add
方法,最后将文本框中输入的值传递给列表框或组合框的Items
集合的Add
方法,如下所示:
lstCountry.Items.Add(txtCountry.Text);
此外,一旦添加了项目,为了更好的用户体验,建议清除文本框,以便用户可以轻松地键入新值。
txtCountry.Clear();
对组合框重复同样的操作:
cboState.Items.Add(txtState.Text); txtState.Clear();
对于移除国家和移除州按钮,您遵循与Items
集合类似的方法,但是这次您调用的不是Add()
而是Remove()
方法,如您所知,移除项的先决条件是必须首先在列表框或组合框中选择 and 项。因此,代码寻求将一个SelectedItem
传递给Remove()
方法。
`lstCountry.Items.Remove(lstCountry.SelectedItem);
cboState.Items.Remove(cboState.SelectedItem);`
现在,对于 Show Details 按钮,您已经使用了一些条件逻辑来根据您的选择生成不同的消息,特别是对于男性和女性单选按钮。
CheckBox 和 RadioButton 控件提供了一个名为 Checked 的属性,该属性可以是 true 或 false,即选中或不选中。你围绕这些建立一个条件,然后显示一个消息框。
if (chkEmail.Checked == true || chkPostalMail.Checked == true && rdbMale.Checked == true) { MessageBox.Show("Hello Mr, you will be contacted by either USPS or email", "Information",MessageBoxButtons.OKCancel, MessageBoxIcon.Information); } else if (chkEmail.Checked == true || chkPostalMail.Checked == true && rdbFemale.Checked == true) { MessageBox.Show("Hello Mam, you will be contacted by either USPS or email", "Information", MessageBoxButtons.OKCancel, MessageBoxIcon.Information); }
在“设置启动表单”任务中,您在Program.cs
文件中创建 AddName 表单的一个实例,如以下代码所示:
Application.Run(new UserInfo());
实施计量吸入器表单
术语多文档界面 (MDI)是指拥有一个 GUI 界面,允许在一个父表单或窗口下有多个文档或表单。
想象一下 Microsoft Word 的工作方式:你可以在一个父窗口中打开多个文档,所有的文档都将列在窗口菜单中,你可以从中选择你想阅读的任何一个,而不是在各自的窗口中打开各个文档,这样很难处理所有的文档,并且会在屏幕上覆盖许多打开的窗口。
同一应用的每个实例都有一个单独的窗口被称为单文档界面(SDI);记事本、MS Paint、计算器等应用都是 SDI 应用。SDI 应用只能在它们自己的窗口中打开,可能会变得难以管理,这与在一个 MDI 界面中打开多个文档或表单不同。
因此,MDI 应用遵循父窗体和子窗体的关系模型。MDI 应用允许您通过在 MDI 父窗体的上下文中打开文档来同时打开、组织和处理多个文档。所以,一旦打开,它们就不能像个体形态一样被拖出来。
父窗体(MDI)组织和排列当前打开的所有子窗体或文档。您可能已经在许多 Windows 应用的 Windows 菜单下看到过这样的选项,例如层叠、垂直平铺等等。
试试看:创建一个带有菜单栏的 MDI 父表单
在本练习中,您将在 WinApp 项目中创建一个 MDI 窗体。您还将看到如何为父窗体创建一个菜单栏,这将允许您导航到所有子窗体。为此,请按照下列步骤操作:
- 导航到解决方案资源管理器,选择 WinApp 项目,右键单击,并选择 Ad
Windows 窗体。将名称值从
Form1.es
更改为ParentForm.es
,并点击添加。 - 在设计视图中选择新添加的 ParentForm。通过单击表单的标题栏选择 ParentForm 表单,导航到“属性”窗口,并设置以下属性:
- 将 IsMdiContainer 属性设置为 True(默认值为 False)。请注意,窗体的背景色已经变成了深灰色。
- 将 Size 属性的宽度设置为 546,高度设置为 411。
- 将 MenuStrip 控件拖动到 ParentForm。在左上角,您应该会看到一个显示文本类型的下拉列表。在下拉框中输入文本打开表单。这将是您的主要顶层菜单。
- 现在在打开的表单菜单下,通过输入文本 Win App 添加一个子菜单。
- 在 Win App 子菜单下,进入用户信息。
- 现在点击顶部菜单,打开表单,在它的右边,输入 Help 。在帮助菜单下,输入退出。
- 现在,点击顶部菜单,在帮助的右侧,键入 Windows 。
- 在 Windows 菜单下,添加以下选项作为单独的子菜单:层叠、水平平铺、垂直平铺和排列图标。这些将有助于安排子窗体。
- 现在是时候将代码附加到您在主菜单打开表单下添加的子菜单中了。首先,您将为子菜单 WinApp 添加代码,这基本上将打开 Win App 表单。在设计视图中,双击 Win 应用子菜单,这将带您到代码视图。在
click
事件下,添加以下代码:WinApp objWA = new WinApp(); objWA.Show();
- Now to associate functionality with the User Info submenu: double-click this submenu, and under the
click
event add the following code:UserInfo objUI = new UserInfo(); objUI.Show();
要将功能与位于帮助主菜单下的退出子菜单相关联,请双击退出,并在`click`事件下添加以下代码:
` Application.Exit();`
- 现在您已经有了表单打开代码功能,并且几乎可以运行应用了。但是首先需要将 ParentForm 设置为启动对象。为此,打开
Program.cs
,将Application.Run(new UserInfo());
语句修改为:Application.Run(new ParentForm());
- Now build the solution, and run the application by pressing F5; the MDI application will open and should look like Figure 9-19.
***图 9-19。**运行 MDI 表单应用*
- 现在,如果您单击 Win App,然后单击 User Info,这两个表单将依次打开。这些窗体可以被打开并拖动到 MDI 窗体之外。这不是 MDI 应用的预期行为,如图 9-20 所示。这个问题将在本章后面讨论。
***图 9-20。*运行 MDI 表单应用
它是如何工作的
每个 windows 窗体都是一个类,并通过为其创建的实例公开一个Show()
函数。您使用下面的代码,它创建一个对象,然后调用Show()
方法。这将打开 MDI 父窗体中的另一个窗体。
这将创建一个 WinApp 窗体实例,并为您打开它:
WinApp objWA = new WinApp(); objWA.Show();
以下代码创建了 UserInfo 表单的一个实例,并为您打开它:
UserInfo objUI = new UserInfo(); objUI.Show();
您使用以下代码关闭应用:
Application.Exit();
试试看:在 MDI 应用中打开 MDI 子窗体
如图 9-20 所述,问题是即使 MDI 表单显示了一个父菜单,这些表单仍然能够在外部打开,并且上下文从一个表单移动到另一个表单。您可以尝试单击每个打开的表单的标题栏,您将看到如何在这些打开的表单中来回移动。
在本练习中,您将克服这个问题,并将之前作为 MDI 子窗体创建的所有窗体与您在前一任务中创建的主 MDI 父窗体相关联。
-
In the project you modified in the previous exercise, you’ll first make the WinApp form an MDI child form. To do so, you need to set the
MdiParent
property of the child form’s object to the MDI parent form itself, but in the Code view. You have already added functionality in the previous task (opening the WinApp form); just before the line where you are calling theShow()
method, add the following code (this code can be found under Win App menu click event):objWA.MdiParent=this; After adding this line, the code will appear as follows: WinApp objWA = new WinApp(); objWA.MdiParent = this; objWA.Show();
注意
this
是一个 C# 语言关键字,表示类的当前实例。在这种情况下,它指的是 ParentForm。因为您是在 ParentForm 内部编写这段代码,所以您可以使用this
关键字来完成同样的工作。 -
Now you will make the UserInfo form an MDI child form. To do so, you need to set the
MdiParent
property to the name of the MDI parent form but in the Code view. Add the following code as you did in the previous step (this code can be found under the User Info menu click event):objUI.MdiParent=this;
添加这一行后,代码将如下所示:
UserInfo objUI = new UserInfo(); objUI.MdiParent=this; objUI.Show();
-
现在构建解决方案,并通过按 F5 运行应用;MDI 应用将会打开,并应如图 9-21 所示。
-
Click Open Form Win App; the WinApp form should open. Again, open the main menu and click User Info. Both the forms should now be open inside your main MDI parent form application, and unlike before, you will not be able to drag these out of your MDI parent form (as shown in Figure 9-20). Figure 9-21 shows the expected behavior of an MDI application with opened form(s).
***图 9-21。*在 MDI 表单应用中打开子表单
-
因为这两个窗体都是在一个 MDI 父窗体中打开的,所以使用它们变得更容易,并且它们不能被拖动到 MDI 父窗体之外。通过单击标题栏在这些表单之间来回切换。
-
完成表单后,选择 Help Exit 关闭应用。
它是如何工作的
正如您在前面的练习中注意到的,讨论的唯一问题是子窗体打开并能够被拖到外面。换句话说,它们并不真正属于父窗体。MDI 应用要求一个带有菜单栏的窗体作为 MDI 父窗体,这样所有的子窗体都可以在其中打开。
为此,首先需要创建子表单的一个对象:
WinApp objWA = new WinApp();
但是在我们真正调用objWA
上的Show()
方法之前,您需要告诉对象谁是它的父对象,这样它就可以在 MDI 父表单中操作。为此,您将使用代表当前表单类的this
关键字。
objWA.MdiParent = this;
您已经创建了对象,并将其上下文设置为 MDI 父表单,所以现在是调用Show()
方法的最佳时机,该方法将启动表单,以便您可以使用它。
objWA.Show();
wa.MdiParent=this;
行告诉子窗体哪个窗体是它的父窗体。因为您希望所有子窗体都出现在 ParentForm 中,并且您在 MDI 父窗体中编写代码,所以您可以使用this
关键字来表示当前对象。
也为 UserInfo 设置前面建议的更改。
UserInfo objUI = new UserInfo(); objUI.MdiParent=this; objUI.Show();
试试看:在 MDI 应用中排列 MDI 子窗体
多个窗体将在一个 MDI 窗口中打开,所以一旦你打开了几个窗体,你的 MDI 应用就会变得混乱。很难移动表格来将你的注意力从一个转移到另一个。因此,最重要的是要有一种机制,允许您以一种有组织的方式来安排表单。
例如,在大多数应用中,您可以排列表单,然后层叠它们,这样您就可以看到一堆打开的表单。或者您可以垂直或水平平铺它们,这样您就可以并排看到多个表单。你甚至可以最小化所有打开的表单,并将它们排列成一个图标。
为此,在本练习中,您将添加如图 9-22 所示的窗口菜单。
***图 9-22。*MDI 窗体应用的窗口菜单排列子窗体
。NET 的 Windows 窗体提供了 LayoutMdi 方法,该方法采用 MdiLayout 枚举来重新排列 Mdi 父窗体中的子窗体。您可以将表单排列成四种模式:层叠、水平平铺、垂直平铺和排列图标。
- 在设计视图中打开 ParentForm,点击窗口菜单,如图图 9-22 所示。
- 双击 Windows 下的第一个选项 Cascade,它会带你到它的
click
事件。添加以下代码:LayoutMdi(MdiLayout.Cascade);
- 双击水平平铺,在
click
事件下的代码视图中,添加以下代码:LayoutMdi(MdiLayout.TileHorizontal);
- 双击垂直平铺,在
click
事件下的代码视图中,添加以下代码:LayoutMdi(MdiLayout.TileVertical);
- 双击垂直平铺,在
click
事件下的代码视图中,添加以下代码:LayoutMdi(MdiLayout.ArrangeIcons);
- 现在构建解决方案,并通过按 F5 运行应用;MDI 应用将会打开。打开后,进入打开表单菜单,依次点击 Win App 和用户信息。在 MDI 父窗体中至少打开两个窗体是很重要的。
现在进入窗口菜单,点击层叠,垂直排列,水平排列,最后排列图标。当你尝试这些选项时,垂直平铺将显示如图图 9-23 排列的子表单。
***图 9-23。*在 MDI 表单应用中排列(垂直平铺)子表单
总结
在本章中,您了解了 Windows 窗体以及与图形用户界面设计相关的设计原则。您还了解了通常被忽略的特性的重要性,比如字体样式和颜色,以及它们对应用的影响和对大量用户的影响。您还使用了解决 Windows 窗体大小调整问题的最常用的 Windows 控件和属性。您了解了 MDI 应用的重要性,然后创建了一个带有菜单控件的 MDI 应用,在 MDI 应用中排列子窗体。
在下一章,你将学习 about 应用设计。
十、ADO.NET 简介
在工业界,如果没有与数据库的交互,大多数应用都无法构建。数据库服务于数据存储的目的,因此以后可以通过 SQL 查询或数据库应用检索数据。几乎每个运行的软件应用都与一个或多个数据库交互。因此,前端需要一种机制来连接数据库,而 ADO.NET 服务于这一目的。大多数的。需要数据库功能的. NET 应用依赖于 ADO.NET。在本章中,我们将介绍以下内容:
- 了解 ADO.NET
- ADO.NET
- The motivation behind it shifted from ADO to ADO.NET.
- Understanding ADO.NET architecture
- Understanding SQL Server data providers
- Understanding OLE DB data providers
- Understanding ODBC data providers
- Data provider as API
了解 ADO.NET
之前。NET 中,开发人员使用了 ODBC、OLE DB 和 ActiveX 数据对象(ADO)等数据访问技术。随着……的引入。微软创造了一种处理数据的新方法,叫做 ADO.NET。
ADO.NET 是一组向其公开数据访问服务的类。NET 程序员,为创建分布式数据共享应用提供了丰富的组件集。ADO.NET 是美国不可分割的一部分。NET Framework,并提供对关系数据、XML 数据和应用数据的访问。ADO.NET 类在System.Data.dll
中被发现。
这项技术支持各种开发需求,包括创建前端数据库客户端和应用、工具、语言和 Internet 浏览器使用的中间层业务对象。因此,ADO.NET 有助于将应用的 UI 或表示层与数据源或数据库连接起来。
ADO.NET 背后的动机
随着应用开发的发展,应用已经变得*松散耦合,*一种架构,其中组件更容易维护和重用,而不依赖于其他组件的实现细节。今天越来越多的应用使用 XML 来编码要通过网络连接传递的数据,这就是运行在不同平台上的不同应用如何进行互操作。
ADO.NET 旨在支持断开连接的数据架构、与 XML 的紧密集成、能够组合来自多个不同数据源的数据的通用数据表示,以及用于与数据库交互的优化工具,所有这些都是。NET 框架。
在 ADO.NET 的开发过程中,微软希望包括以下功能:
借力当前 ADO 知识 : ADO。NET 的设计解决了当今应用开发模型的许多需求。同时,编程模型尽可能与 ADO 相似,因此当前的 ADO 开发人员不必从头开始。ADO.NET 是美国固有的一部分。NET 框架,但 ADO 程序员对此并不陌生。
阿多。网也与麻烦共存。虽然是最新的。基于。网的应用将使用阿多。网编写,ADO 仍然可用 100 .净程序员通过. NET COM 互操作性服务.对 n 层编程模型的支持
: The concept of using disconnected recordset has become a focus in the programming model. ADO.NET provides first-class support for the unconnected N-tier programming environment. Addo. NET to build N-tier database application is
DataSet
.Integrated XML support : XML and data access are closely linked. XML is about data coding, and data access is increasingly related to XML. that The. NET Framework not only supports web standards, but also is completely built on these standards.
XML 支持是 ADO.NET 的基础。中的 XML 类。NET 框架和 ADO.NET 是同一个架构的一部分;它们在许多不同的层面上整合。因此,您不再需要在服务的数据访问集和它们的 XML 对应物之间进行选择;从一个跨越到另一个的能力是两者的设计中所固有的。
从阿多搬到 ADO.NET
ADO 是 ActiveX 对象的集合,这些对象被设计成在持续连接的环境中工作。它构建在 OLE DB 之上(我们将在“了解 OLE DB 数据提供程序”一节中讨论)。OLE DB 提供了对非 SQL 数据以及 SQL 数据库的访问,ADO 提供了一个接口,旨在简化 OLE DB 提供程序的使用。
然而,使用 ADO(和 OLE DB)访问数据意味着在到达数据源之前,您必须经过几层连接。正如 OLE DB 可以连接到大量的数据源一样,一种更老的数据访问技术——开放式数据库连接(ODBC)仍然可以连接到更老的数据源,如 dBase 和 Paradox。要使用 ADO 访问 ODBC 数据源,您可以使用 ODBC 的 OLE DB 提供程序(因为 ADO 只直接使用 OLE DB),从而向已经多层的模型中添加更多的层。
利用 ADO 的多层数据访问模型和连接特性,您可能很容易耗尽服务器资源并造成性能瓶颈。ADO 在它的时代服务得很好,但是 ADO.NET 有一些很棒的特性,使它成为一种优越得多的数据访问技术。
ADO.NET 不是 ADO 的新版本
ADO.NET 是一种全新的数据访问技术,具有完全从零开始构建的新设计。让我们先澄清一下:ADO.NET不代表 ActiveX 数据对象. NET。为什么?原因很多,但以下是两个最重要的原因:
- ADO.NET is an inseparable part. NET instead of an external entity.
- ADO.NET is not a collection of ActiveX components.
ADO.NET 这个名字类似于 ADO,因为微软希望开发人员在使用 ADO.NET 时有宾至如归的感觉,并且不希望他们认为他们需要“从头再学一遍”,如前所述,所以微软特意命名并设计了 ADO.NET,以提供以不同方式实现的类似功能。
在的设计过程中。微软意识到 ADO 不适合。ADO 是基于组件对象模型(COM)对象的外部包,需要。NET 应用显式包含对它的引用。相比之下,。NET 应用被设计为共享一个模型,其中所有的库被集成到一个框架中,被组织到逻辑命名空间中,并对任何想要使用它们的应用公开。明智的决定是。NET 数据访问技术应该遵守。NET 架构模型。于是,ADO.NET 诞生了。
ADO.NET 旨在适应连接和断开连接的访问。此外,ADO.NET 比 ADO 更多地采用了非常重要的 XML 标准,因为 XML 的使用是在 ADO 开发出来之后才出现的。使用 ADO.NET,您不仅可以使用 XML 在应用之间传输数据,还可以将应用中的数据导出到 XML 文件中,将其存储在本地系统中,并在以后需要时进行检索。
性能通常是有代价的,但在 ADO.NET 的情况下,价格肯定是合理的。与 ADO 不同,ADO.NET 不透明地包装 OLE DB 提供程序;相反,它使用专门为每种类型的数据源设计的托管数据提供者,从而充分利用它们的真正能力,提高应用的整体速度和性能。
ADO.NET 还可以在连接和断开的环境中工作。您可以连接到数据库,在简单地读取数据时保持连接,然后关闭连接,这是一个类似于 ADO 的过程。ADO.NET 真正开始发光的地方是在这个断开的世界。如果您需要编辑数据库数据,那么在服务器上维护连续连接的成本会很高。ADO.NET 通过提供一个复杂的分离模型来解决这个问题。数据从服务器发送,并在客户端本地缓存。当您准备好更新数据库时,您可以将更改后的数据发送回服务器,在服务器上为您管理更新和冲突。
在 ADO.NET,当你检索数据时,你使用一个叫做数据读取器的对象。当您处理断开连接的数据时,数据被缓存在本地的一个关系数据结构中,或者是一个数据表或者是一个数据集。
ADO.NET 和。NET 基础类库
一个数据集(DataSet
对象)可以在内存缓存中以表格(DataTable
对象)、它们的关系(DataRelation
对象)和约束(Constraint
对象)的形式保存大量数据,然后可以将这些数据导出到外部文件或另一个数据集。由于 XML 支持被集成到了 ADO.NET 中,所以您可以使用 XML 文档生成 XML 模式并传输和共享数据。表 10-1 描述了 ADO.NET 组件分组的名称空间。
因为 XML 支持已经紧密地集成到 ADO.NET 中,所以在System.Data
名称空间中的一些 ADO.NET 组件依赖于在System.Xml
名称空间中的组件。因此,有时需要在解决方案资源管理器中将这两个命名空间作为引用包含在内。
这些名称空间在物理上被实现为程序集,如果您在 VCSE 创建一个新的应用项目,对程序集的引用应该会自动创建,同时还会创建对System
程序集的引用。但是,如果它们不存在,只需执行以下步骤将名称空间添加到项目中:
- 在解决方案资源管理器中右击“引用”项;然后单击添加引用。
- 将显示一个包含可用参照列表的对话框。逐个选择
System.Data
、System.Xml
、System
(如果没有的话)(按住 Ctrl 键可多选);然后单击选择按钮。 - 单击 OK,引用将被添加到项目中。
提示虽然我们在本书中不使用,但是如果使用命令行 C# 编译器,可以使用以下编译器选项来包含所需程序集的引用:
/r:System.dll /r:System.Data.dll /r:System.Xml.dll
。
从名称空间中可以看出,ADO.NET 可以使用 OLE DB 和 ODBC 等旧技术。但是,SQL Server 数据提供程序直接与 SQL Server 通信,无需添加 OLE DB 或开放式数据库连接(ODBC)层,因此这是最有效的连接形式。同样,Oracle 数据提供者直接访问 Oracle。
注各大 DBMS 厂商都支持自己的 ADO.NET 数据提供商。在本书中,我们将坚持使用 SQL Server,但是不管提供者是谁,都要编写相同类型的 C# 代码。
了解 ADO.NET 建筑
ADO.NET 提供了两种类型的架构组件来构建以数据为中心的应用:连接的和断开的。在微软内部。NET Framework 中,ADO.NET 位于名称空间System.Data
(程序集名称为System.Data.dll
)中,因此连接和断开连接的组件的所有类和函数都位于同一个名称空间中。因此,不管您已经选择或者以后将选择的是连接的还是断开的架构风格,在您的应用中添加一个System.Data
的引用是很重要的。
图 10-1 展示了 ADO.NET 最重要的建筑特色。我们将在后面的章节中更详细地讨论它们。
图 10-1。【ADO.NET 建筑
连接的数据对象
阿多。NET 的连接架构依赖于一致的数据库连接来访问数据并对检索到的数据执行任何操作。ADO.NET 提供了以下对象来帮助您使用连接架构构建应用:
Connection
: This is the main or core object of any database-oriented application. As you can imagine, if you don’t know the statistical information of the data source, such as where it is located, what database you want to connect to, what user name and password it needs, etc., it is impossible to establish a connection and perform any data-related activities. Each. NET provider provides its ownConnection
object, which provides features for specific data sources.Command
: This object represents the processing of statements used by applications to perform data-oriented tasks, such as reading data, inserting or modifying data. Therefore, any SQL statement is actually executed through aCommand
object.DataReader
: DataReader involves creating an instance of Command object, and then creating DataReader by calling Command. ExecuteReader retrieves data from the data source, and the returned data can be obtained in a read-only manner through aDataReader
object. The data retrieval behavior ofDataReader
is also called Quick read-only fire hose cursor .Parameter
:Parameter
has always been an important part of any programming model. Similarly, in ADO.NET programming, it is important to pass the value toCommand
.Parameter
can be a value passed to or returned from the stored procedure, or a parameter passed to the SQL query.DataAdapter
:DataAdapter
is an object disclosed by ADO.NET, which is used to build a bridge between the connected and disconnected architectures, so that applications can establish connections and synchronize data with data sources.
断开的数据对象
阿多。NET 的连接架构依赖于一致的数据库连接来访问数据并对检索到的数据执行任何操作。然而,在当今复杂的分布式应用环境中,不可能依靠专用的数据库连接来检索和修改数据。
为了帮助您满足业务需求并在分布式环境中轻松工作,您可以利用 ADO。NET 的非连接体系结构;它提供灵活的应用设计,并帮助组织节省数据库连接。因此,可以检索数据,然后以DataSet
对象的形式存储在本地设备上。检索到的DataSet
可以由用户在他们的本地设备上修改,比如笔记本电脑、手持设备、平板电脑等等,一旦完成,他们就可以将更改同步到中央数据源。非连接架构以一种非常优化的方式利用了像Connection
这样的昂贵资源(也就是晚开早闭)。
ADO.NET 提供了以下对象来帮助您使用非连接架构构建应用:
DataSet
:DataSet
is the core architecture component of ADO.NET, which is used to make disconnected applications. A data set can be regarded as a subset of data. Datasets support disconnected and independent caching of data in a relational way, and update data sources as needed. A dataset contains one or more data tables.DataTable
:DataTable
is a row and column representation, which provides a logical view very similar to the physical table in the database. For example, you can store the data in the database table in ADO.NETDataTable
and manipulate the data as needed.DataRow
: As you know, tables are always made up of rows. In a similar way, ADO. TheDataTable
of. NET consists of rows of typeDataRowCollection
. ThisDataRowCollection
is an enumerable collection ofDataRow
objects. When new data is added toDataTable
, newDataRow
is added.DataColumn
: Like any other column in a database table, ADO. TheDataTable
of. NET is composed ofDataColumn
of typeDataColumnCollection
.DataView
:DataView
The role of ADO.NET is similar to the view in database. Typically, a view in a database provides a set of predefined, organized or filtered records. Similarly, a T2 provides filtered or sorted records from a T3. Just as a database table can have multiple views,DataTable
can also have multiple data views.
理解。NET 数据提供者
ADO.NET 由各种数据提供程序组成,这些数据提供程序允许一个简单的预定义对象模型与各种行业数据库(如 SQL Server、Oracle、Microsoft Access 等)进行通信。
有各种各样的数据库提供程序,所以每个数据提供程序都有自己的名称空间。事实上,每个数据提供者本质上都是在System.Data
名称空间中的接口的实现,专门用于特定类型的数据源。
例如,如果您使用 SQL Server,您应该使用 SQL Server 数据提供程序(System.Data.SqlClient
),因为这是访问 SQL Server 最有效的方式。
OLE DB 数据访问接口支持访问 SQL Server 的旧版本以及其他数据库,如 access、DB2、MySQL 和 Oracle。然而,本地数据提供程序(如System.Data.OracleClient
)在性能上更好,因为 OLE DB 数据提供程序在到达数据源之前通过另外两层工作,即 OLE DB 服务组件和 OLE DB 提供程序。
图 10-2 说明了使用 SQL Server 和 OLE DB 数据提供程序访问 SQL Server 数据库的区别。
图 10-2。 SQL Server 和 OLE DB 数据提供程序的差异
如果您的应用同时连接到较旧版本的 SQL Server (6.5 或更早版本)或多种数据库服务器(例如,Access 和 Oracle 数据库同时连接),则只有在这种情况下,您才应该选择使用 OLE DB 数据访问接口。
不存在严格的规则;如果您愿意,可以同时使用 OLE DB data provider for SQL Server 和 Oracle data provider ( System.Data.OracleClient
),但选择最适合您的用途的提供程序很重要。考虑到特定于服务器的数据提供程序的性能优势,如果您使用 SQL Server,99%的时间应该使用System.Data.SqlClient
类。
在我们了解每种数据提供程序的功能和使用方法之前,您需要清楚它们的核心功能。每个。NET 数据提供程序旨在很好地完成以下两件事:
- Provide access to data through active connection with data source.
- Provide data transmission between disconnected data sets and data tables.
数据库连接是通过使用数据提供者的Connection
类建立的(例如,System.Data.SqlClient.SqlConnection
)。数据读取器、命令和数据适配器等其他组件分别支持检索数据、执行 SQL 语句以及读取或写入数据集或数据表。
正如您所看到的,每个数据提供程序都以它所连接的数据源类型为前缀(例如,SQL Server 数据提供程序以Sql
为前缀),因此它的连接类被命名为SqlConnection
。OLE DB 数据提供程序的连接类被命名为OleDbConnection
。
让我们了解一下可以与 SQL Server 一起使用的三种数据提供程序。
了解 SQL Server 数据提供程序
那个。SQL Server 的. NET 数据提供程序位于System.Data.SqlClient
命名空间中。虽然您可以使用System.Data.OleDb
来连接 SQL Server,但是微软已经专门设计了用于 SQL Server 的System.Data.SqlClient
名称空间,并且它的工作方式比System.Data.OleDb
更加高效和优化。这种效率和优化方法的原因是,该数据提供者使用其本地网络协议直接与服务器通信,而不是通过多层。
表 10-2 描述了SqlClient
名称空间中的一些重要类。
另一个名称空间System.Data.SqlTypes
将 SQL Server 数据类型映射到。NET 类型,既提高了性能,又使开发人员的工作变得更加容易。
了解 OLE DB 数据提供程序
外面。NET,OLE DB 仍然是微软的高性能数据访问技术。OLE DB 数据提供程序已经存在了许多年。如果您过去曾为 Microsoft Access 编写过程序,您可能还记得使用 Microsoft Jet OleDb 3.5 或 4.0 来连接 Microsoft Access 数据库。您可以使用该数据提供程序来访问以任何格式存储的数据,因此即使在 ADO.NET,它在访问没有自己的 ADO.NET 数据提供程序的数据源时也发挥着重要作用。
的。OLE DB 的. NET Framework 数据提供程序位于命名空间System.Data.OleDb
中。表 10-3 描述了OleDb
名称空间中的一些重要类。
注意两个数据提供者SqlClient
和OleDb
之间的相似性。它们实现中的差异是显而易见的,用户界面基本上是相同的。
ADO.NET OLE DB 数据访问接口要求在连接字符串中指定 OLE DB 访问接口。表 10-4 描述了一些 OLE DB 提供者。
了解 ODBC 数据提供者
ODBC 是微软最初的通用数据访问技术。它仍然广泛用于没有 OLE DB 提供程序或。NET Framework 数据提供程序。ADO.NET 在名称空间System.Data.Odbc
中包含了一个 ODBC 数据提供者。
ODBC 体系结构本质上是一个三层过程。应用使用 ODBC 函数提交数据库请求。ODBC 将函数调用转换为特定于给定数据源的驱动的协议(调用级接口)。驱动程序与数据源通信,将任何结果或错误传递回 ODBC。显然,这比特定于数据库的数据提供者与数据库的直接通信效率要低,所以为了性能,最好避免使用 ODBC 数据提供者,因为它只是提供了一个更简单的 ODBC 接口,但仍然涉及所有的 ODBC 开销。表 10-5 描述了Odbc
名称空间中的一些重要类。
数据提供者是 API
那个。NET Framework 数据提供程序虽然复杂(您将在后面学到很多利用其复杂性的知识),但它们只是用于访问数据源的 API,最常见的是关系数据库。(ADO.NET 本质上是一个大 API,数据提供商是其中的主要部分。)
可以理解,ADO.NET 的新来者经常会对微软的文档感到困惑。他们读到了Connection
、Command
、DataReader
和其他 about 对象,但是他们没有在任何 about 名称空间中看到名为Connection
、Command
或DataReader
的类。原因是数据提供者类在System.Data
名称空间中实现了接口。这些接口定义了 ADO.NET API 的数据提供者方法。
概念很简单。数据提供者,比如System.Data.SqlClient
,由一些类组成,这些类的方法提供了访问特定类型数据源的统一方式。这适用于 ADO.NET 的所有设施,无论您需要访问哪种数据源。
SQL Server 数据提供程序针对访问 SQL Server 进行了优化,不能用于任何其他 DBMS。OLE DB 数据提供程序可以访问任何 OLE DB 数据源。ODBC 数据提供程序允许您使用一种更古老的数据访问技术,同样,您对此一无所知。在这样一个抽象的层次上工作能让你做得更多、更快。
ADO.NET 不仅是一种高效的数据访问技术,也是一种优雅的技术。数据提供者只是其中的一个方面。ADO.NET 编程的艺术更多的是建立在概念化而不是编码上。首先弄清楚 ADO.NET 能提供什么;然后在正确的类中寻找正确的方法,让想法变成现实。
由于概念清晰非常重要,您可以将连接、命令、数据读取器和其他 ADO.NET 组件主要视为抽象,而不仅仅是数据库程序中使用的对象。如果你专注于概念,学习何时以及如何使用相关的对象和方法将会很容易。
总结
在本章中,您看到了开发 ADO.NET 的原因以及它如何取代. NET 中的其他数据访问技术。我们概述了它的体系结构,然后重点介绍了它的核心组件之一,数据提供程序。您构建了三个简单的示例来练习基本的数据提供者用法,并体验了编写数据访问代码的统一方式,而不考虑数据提供者。最后,我们认为概念清晰是理解和使用数据提供者和 ADO.NET API 的关键。接下来,我们将研究 ADO.NET 的细节,从连接开始。
十一、处理异常
对于您编写的程序,您肯定会关心修复语言编译器引起您注意的任何错误或问题。然而,有一种特殊类型的错误在编译时不会发生;相反,它发生在运行时。随着您进行更复杂的应用开发,您有更多的机会出现这样的运行时错误,称为异常。发生这种情况的原因可能是应用试图打开一个与不存在的数据库的连接,打开一个不存在的文件,或者写入一个已经以只读模式打开的文件。本章将帮助你学习更多关于异常的知识,以及当异常发生时如何处理它们。
系统。异常类
英寸 NET 中,所有的异常都是从Exception
类派生的。Exception
类是在System
名称空间中定义的。其他派生的异常类分布在许多其他名称空间中,比如SQLException
、FileNotFoundException
、IndexOutOfRangeException
等等。
因此当你调用一些。NET 功能,并且在运行时出错,函数可能会抛出特定类型的异常。例如,如果您连接到一个不存在的数据库,您将收到一个运行时错误,换句话说,一个类型为SqlException
的异常。类似地,如果您试图打开一个不存在的文件进行读取,您将得到一个FileNotFound
异常。
重要的是要理解所有的异常都是从System.Exception
类派生的。例如,如果您捕捉到了System.Exception
,那么这将涵盖从System.Exception
派生的所有异常。我将在本章的后面演示这一点。表 11-1 显示了System.Exception
类暴露的属性。
什么原因导致异常发生
在我们深入了解如何处理异常的更多细节之前,让我们看看异常发生时是什么样子,以及应用在这种情况下如何表现。
如今,许多组织依靠日志文件来跟踪系统上发生的活动;你甚至可能已经看过或读过一些setup.log
文件。因此,文件处理是一个重要的概念,可以应用于许多情况。例如,您在文本框中输入的任何内容都可以记录到日志文件中,以后可以从磁盘上的存储文件中读取这些信息。
假设您有一个从用户那里读取文件路径和文件名并打开文件的应用。通常,应用工作正常,但是在这种情况下,提供了不正确的文件名或路径,因此会发生异常。
试试看:创建一个文件处理应用
在本练习中,您将创建一个带有四个标签、四个文本框和两个按钮的 Windows 窗体应用。应用将接受一些文本,然后将其保存/写入磁盘上的文件;它还将文件路径作为输入,并为您读取文件内容。
-
创建一个名为 Chapter11 的新 Windows 窗体应用项目。当解决方案资源管理器打开时,保存解决方案。
-
将 Chapter11 项目重命名为 FileHandling,然后将 Form1 重命名为 FileExceptionHandling。
-
将 FileExceptionHandling 窗体的 Text 属性更改为 File-Read/Write。
-
将 Label 控件拖到窗体上,并将其放在左上角。选择此标签控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblPathWrite。
- 设置 Text 属性以输入文件写入路径。
-
将一个 TextBox 控件拖动到刚刚拖动到窗体上的名为 lblPathWrite 的 Label 控件旁边。选择此 TextBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtFileWritePath。
- 将 Size 属性设置为 301,20。
-
将一个 Button 控件拖动到刚刚拖动到窗体上的 TextBox 控件旁边。选择此按钮控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 btnWriteToFile。
- 设置 Text 属性以写入文件。
-
将一个 Label 控件拖到窗体上,并将其放在名为“输入文件写入路径”的 Label 控件下面。选择此标签控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblText。
- 设置 Text 属性以输入文本。
-
将一个 TextBox 控件拖动到刚刚拖动到窗体上的名为 lblText 的 Label 控件旁边。选择此 TextBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtFileText。
- 将 Multiline 属性设置为 True。
- 将 Size 属性设置为 301,60。
-
Drag a Label control onto the form, and position it below the Label control named lblText. Select this Label control, navigate to the Properties window, and set the following properties:
- 将 Name 属性设置为 lblPathRead。
- 设置 Text 属性以输入文件读取路径。
将一个 TextBox 控件拖动到刚刚拖动到窗体上的名为 lblPathWrite 的 Label 控件旁边。选择此 TextBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtFileReadPath。
- 将 Size 属性设置为 301,20。
-
将一个 Button 控件拖动到刚刚拖动到窗体上的 TextBox 控件旁边。选择此按钮控件,导航到“属性”窗口,并设置以下属性:
1. 将 Name 属性设置为 btnReadFile。
2. 将 Text 属性设置为 Read File。 -
将一个 Label 控件拖到窗体上,并将其放在名为“输入文件读取路径”的 Label 控件下面。选择此标签控件,导航到“属性”窗口,并设置以下属性:
1. 将 Name 属性设置为 lblFileContent。
2. 将 Text 属性设置为 File Content。 -
将一个 TextBox 控件拖动到刚刚拖动到窗体上的名为 lblText 的 Label 控件旁边。选择此 TextBox 控件,导航到“属性”窗口,并设置以下属性:
1. 将 Name 属性设置为 txtFileContent。
2. 将 Multiline 属性设为 True。
3. 将 Size 属性设置为 301,90。 -
Your FileExceptionHandling form will look like Figure 11-1.
***图 11-1。**设计视图中的文件处理表单*
- 现在是写代码的时候了。
- 双击“写入文件”按钮,开始添加代码。
- 首先为与文件相关的操作添加一个
using
语句,在顶部的代码编辑器中导航到using
语句,并在末尾添加以下语句:using System.IO;
- Now add the following code for writing text to the file from Listing11-1:
***列表 11-1。** btnWriteToFile_Click*
`StreamWriter sw = new StreamWriter(txtFileWritePath.Text, true);
sw.WriteLine(txtFileText.Text);
sw.Close();`
- 双击 Read File 按钮,并添加下面的代码来从清单 11-2 的文件中读取内容:
Listing11-2: btnReadFile_ClickStreamReader sr = new StreamReader(txtFileReadPath.Text); txtFileContent.Text = sr.ReadToEnd(); sr.Close();
- Save the changes and build the application. You should see the message “Build Succeeded.” Now it’s time to run the application and do a file read-write operation. Press Ctrl+F5 to run the program.
 **注意**重要的是要明白`System.IO`只能读写那些扩展名为`.txt`或`.log file`的文件。此外,这种文件处理程序是基于文件路径和文件名的。出于演示的目的,我使用我的笔记本电脑特定的路径和文件名。这些文件路径和文件名与您系统上的不匹配,因此请相应地修改目录名和文件名。
- When the application launches, enter the file path in the first text box and then type the text you want to save in this file, as shown in Figure 11-2.
 **注意**根据你的电脑使用文件读写路径很重要;此外,请确保您对指定的驱动器/文件夹具有写权限。

***图 11-2。**文件写入操作正在进行*
- Once you’re done, click the Write To File button. You should be able to see your file created, as shown in Figure 11-3.
***图 11-3。**应用创建的日志文件*
- Now switch back to the running application, type the path where your file just got created in the Enter File Read Path text box, and click the Read File button. You should see the output shown in Figure 11-4.
***图 11-4。**文件读写动作*
试试看:引起异常发生,观察行为
在本练习中,您将继续使用已创建的应用,然后创建一个会导致异常发生的场景,以便您可以观察该行为。
-
在 Visual Studio 2012 中打开 FileHandling 项目。
-
要引起异常,您只需对预先创建的文件执行文件读取操作。
-
This time, you will run the application from the file
Chapter11.exe
, which is located in the project’sbin\debug
folder, as shown in Figure 11-5.图 11-5*。*从其指定的
.exe
运行程序从bin\debug
运行到 -
When the application loads, enter the following path in the Enter File Read Path text box: c:\vidyavrat\MyLogFile.log. As you probably recall, you saved the file as
MyFile.log
(refer to the earlier Figure11-2), but here you are intentionally passing the wrong file name, as shown in Figure 11-6.***图 11-6。*提供错误的文件名导致异常
-
Now click the Read File button. Because this file name is incorrect, you will receive a strange-looking dialog with an unhandled exception, as shown in Figure 11-7.
***图 11-7。*通过
.exe
执行代码时出现异常对话框正如您所看到的,这将中止您的应用,并留给您一个不愉快的对话框,这当然不是用户友好的。
-
单击“退出”退出异常对话框。(如果您单击继续,您将切换回应用 UI。)
-
为了深入了解,让我们通过按 Ctrl+F5 从 Visual Studio 运行该项目。
-
When the application loads, repeat the previous steps to enter an incorrect file name, and click Read File. You will get an exception. The difference now by running the program through Visual Studio is that it points to the code and exact details of the exception so you can add exception-handling code, as shown in Figure 11-8.
***图 11-8。*通过 Visual Studio 执行代码时出现异常对话框
如您所见,这对开发人员来说是非常有用的。它明确指出您提供的文件名不存在或没有找到,并抛出一个FileNotFoundException
异常。
现在,一旦发生这种情况,就没有解决办法了。您必须通过按 Shift+F5 来中断应用,这将使您返回到代码视图,应用将停止运行。但是您现在知道发生了什么类型的异常,所以您可以添加代码来处理它。
注意本章的主要目的是异常处理;本章后面的练习将提供详细的操作步骤。现在,理解异常的一些更概念性的方面是很重要的。
探索异常的类型、消息和堆栈跟踪属性
任何。NET 异常中发生的或通过运行 EXE 文件而发生的异常包含大量信息,开发人员可以对其进行处理或进一步调查。Type、Message 和 StackTrace 属性服务于这个伟大的目的。
类型定义了类别,或者发生了哪种异常。每当发生. NET 异常时,它都会在对话框的标题栏中显示异常的类型。可以看到,图-11-8 中对话框的标题栏上提到了FileNotFoundException
。
通过点击对话框底部(在操作下)的查看详细信息,可以找到关于任何异常的更多信息,如图图 11-7 所示。这适用于您在. NET 中遇到的任何异常。
单击查看详细信息后,您将看到另一个窗口打开;它提供了异常快照。展开这个,你会发现很多信息。查找 Message 属性,它保存了发生异常时的确切消息,如图图 11-9 所示。
在大多数情况下,开发人员希望在这个系统生成的消息中添加一些额外的文本。我们将在本章后面介绍如何做到这一点。
***图 11-9。*异常消息属性
其他重要信息由 StackTrace 属性公开,该属性主要对希望调试代码并找出是哪一行代码导致了问题的人有用。此外,许多组织出于监控目的,将此类信息记录到事件查看器或日志文件中。
就在 Message 属性下面,您会发现 StackTrace 选项;选中它,然后点击其描述文本右侧向下的箭头,如图图 11-10 所示。
***图 11-10。*异常的堆栈跟踪属性
您会注意到,这个 StackTrace 属性是预先选定的,因此您可以轻松地复制和粘贴它。大多数 bug 分类会议都有很多关于 StackTrace 的讨论,每当一个手工测试人员谈到一些运行时错误时,大多数开发人员都会说,“请给我提供 StackTrace!”因为它可以准确地指出问题所在。
现在让我们将这个 StackTrace 复制并粘贴到一个记事本文件中并对其进行研究,如图图 11-11 所示。此图仅显示了堆栈跟踪的前半部分。当您复制和粘贴时,您将看到完整的详细信息。
***图 11-11。*正在调查异常的堆栈跟踪细节
如果您查找图中所示的选中行,您将会发现是哪个文件导致了这个异常的发生,以及问题出在哪个路径和行号上。
处理异常
现在您可能已经意识到,异常处理是一种用于避免任何运行时错误并优雅地处理它们的技术,而不是发出一些笨拙的消息或让应用在用户面前挂起。
异常处理主要基于三个关键字:try
、catch
、finally
。任何程序都可以有一个try
,后跟一个或多个catch
块,然后以一个finally
块结束。
try
块保存任何抛出或可能抛出异常的代码。catch
块作为一种防御机制,处理抛出的异常。finally
区块有独特的行为;它将在两种情况下执行:异常没有发生时和异常发生时。因此,finally
的最佳代码语句是关闭文件流、关闭数据库连接,或者甚至向客户告别,等等,但是现实世界的应用包括关闭流和连接。我将在下面的练习中演示这一点。
试试看:添加异常处理语句
在本练习中,您将继续使用创建的应用,然后添加异常处理代码块来处理此类异常,避免以不友好的方式向用户显示。
-
在 Visual Studio 2012 中打开 FileHandling 项目。
-
Now double-click the Read File button and replace the code with the one in Listing 11-3.
清单 11-3。 btnReadFile_Click
`StreamReader sr=null;
try
{ sr = new StreamReader(txtFileReadPath.Text);
txtFileContent.Text = sr.ReadToEnd();
}catch (FileNotFoundException ex)
{
MessageBox.Show(ex.Message + " " + “Please provide valid path and filename”);
}catch (DirectoryNotFoundException ex)
{
MessageBox.Show(ex.Message + " " + “Please provide valid Directory
name”, “File Read Error”);
}finally
{
if (sr != null)
{
sr.Close();
}`
构建应用,并按 Ctrl+F5 运行它。如果你这次传递了错误的文件名,它实际上会抛出一个异常,但是我们正在处理,所以会显示一个用户友好的消息,如图 11-12 所示。
**图 11-12。**使用catch
程序块进行异常处理
如您所见,该对话框显示您输入了错误的路径或文件名。单击 OK,它将带您回到应用,让您正确地修改路径或文件名。
在这样的文件处理应用中,另一个场景是当用户传递错误的目录名时。为了处理这个问题,你需要一个单独的catch
块来处理DirectoryNotFoundException
。你已经添加了,如清单 11-3 所示。现在要测试它,请将路径更改为一个不存在的文件夹名,您将看到一个单独的对话框,提示“提供有效的目录名”
它是如何工作的
这个文件读取代码是基于Stream
对象的,所以您需要创建一个StreamReader
对象。
StreamReader sr=null;
现在您在try
块中使用这个对象来传递文件路径和文件名,以便读取内容。
try { sr = new StreamReader(txtFileReadPath.Text); txtFileContent.Text = sr.ReadToEnd(); }
如果提供了错误的文件路径或文件名,那么它将抛出一个FileNotFoundException
,因此您需要提供一个catch
块来处理这个异常。
catch (FileNotFoundException ex) { MessageBox.Show(ex.Message + " " + "Please provide valid path and filename"); }
如果提供了一个错误的目录名,那么它将抛出一个DirectoryNotFoundException
,所以您需要提供一个catch
块来处理这个异常。
catch (DirectoryNotFoundException ex) { MessageBox.Show(ex.Message + " " + "Please provide valid Directory name", "File Read Error"); }
在任何情况下,无论文件是否被读取,都需要关闭一个Stream
对象。执行这样的强制操作可能是一个finally
块的最佳候选。同样,你会注意到在异常情况下,Stream
对象不会被初始化,因为文件名或路径找不到,所以不能被关闭。
因此,您必须在关闭之前检查您创建的Stream
对象是否为空。
finally { if (sr != null) { sr.Close(); } }
总结
在本章中,你学习了异常处理以及如何处理 C# 文件 I/O 程序抛出的异常。在专门讨论 ADO.NET 的下一章中,您将在整个 ADO 中应用异常处理原则。NET 代码。
具体来说,在下一章中,您将了解如何创建一个到 SQL Server 2012 数据库的 ADO.NET 连接。
十二、建立连接
在对数据库做任何有用的事情之前,您需要与数据库服务器建立一个会话。您可以通过一个名为*连接、*的对象来实现这一点,该对象是一个类的实例,该类为特定的数据提供者实现了System.Data.IDbConnection
接口。在本章中,您将使用各种数据提供者来建立连接,并了解可能出现的问题以及如何解决这些问题。在本章中,我们将介绍以下内容:
- Introduce the data provider connection class
- Use
SqlConnection
- Connecting to SQL Server to Improve the Use of Connection Objects
- Use
OleDbConnection
连接到 SQL Server】
介绍数据提供者连接类
正如您在第十章中看到的,每个数据提供者都有自己的名称空间。每个都有一个实现System.Data.IDbConnection
接口的连接类。表 12-1 总结了微软提供的数据提供商。
如您所见,这些名称遵循一个约定,使用前缀为数据提供者标识符的Connection
。由于所有的连接类都实现了System.Data.IDbConnection
,所以每一个的用法都是相似的。每个都有额外的成员,提供特定于特定数据库的方法。
使用 SqlConnection 连接到 SQL Server 2012
在此示例中,您将连接到 SQL Server 连接到 SQL Server 2012 AdventureWorks 数据库。
尝试一下:使用 SqlConnection
您将编写一个非常简单的程序来打开和检查连接。
-
在 Visual Studio 2011 中,创建一个名为 Chapter12 的新 Windows 控制台应用项目。当解决方案资源管理器打开时,保存解决方案。
-
Rename the Chapter12 project to ConnectionSQL. Rename the
Program.cs
file toConnectionSql.cs
, and replace the generated code with the code in Listing 12-1.***清单 12-1。*T4
ConnectionSql.cs
`using System;
using System.Data;
using System.Data.SqlClient;namespace Chapter12
{
class ConnectionSql
{
static void Main(string[] args)
{
// Connection string (connection string key=value
//might be different for you based on your environment
string connString = @“server = .\sql2012; integrated security = true;”;// Create connection
SqlConnection conn = new SqlConnection(connString);try
{
// Open connection
conn.Open();
Console.WriteLine(“Connection opened.”);
}catch (SqlException ex)
{
// Display error
Console.WriteLine("Error: " + ex.Message + ex.StackTrace);
}finally
{
// Close connection
Console.WriteLine(“Connection closed.”);
}Console.ReadLine();
}
}
}` -
Run the application by pressing Ctrl+F5. If the connection is successful, you’ll see the output in Figure 12-1.
***图 12-1。*打开和关闭数据库连接
如果连接失败,你会看到如图图 12-2 所示的错误信息。(您可以通过首先关闭 SQL Server 服务,在命令提示符下输入net stop mssql$<SQL Server instance name>
来实现这一点。如果你尝试这样做,记得用net start mssql$<SQL Server instance name>
重启它。)或者,简单的方法是尝试将错误的 SQL 实例名传递给连接字符串。
***图 12-2。*连接到 SQL Server 时连接失败的错误
现在不要担心这个消息的细节。连接失败的原因通常与您的代码无关。这可能是因为服务器没有启动,就像在这种情况下,或者因为密码是错误的,或者因为其他一些配置问题存在。您将很快看到建立数据库连接中的常见问题。
它是如何工作的
让我们检查一下清单 12-1 中的代码,以理解连接过程中的步骤。首先,指定 ADO.NET 和 SQL Server 数据提供程序命名空间,以便可以使用其成员的简单名称。
using System; using System.Data; using System.Data.SqlClient;
然后,创建一个连接字符串。一个连接字符串由参数组成——换句话说,由分号分隔的key=value
对——指定连接信息。尽管有些参数对所有数据提供程序都有效,但每个数据提供程序都有它将接受的特定参数,因此了解您所使用的数据提供程序的连接字符串中哪些参数是有效的非常重要;这将在本章后面详细解释。
// Connection string string connString = @"server = .\sql2012; integrated security = true;";
让我们简要地检查一下这个例子中的每个连接字符串参数。server 参数指定要连接的 SQL Server 实例。
server = .\sql2012;
在该语句中,.
(点)表示本地服务器,后面跟有\
(斜线)的名称表示数据库服务器上运行的 SQL Server 实例名称。因此,这里有一个名为 sql2012 的 SQL Server 2012 实例运行在本地服务器上。
提示
(local)
是.
(点号)的替代,用来指定本地机器,所以.\sqlexpress
可以用(local)\sql2012
代替,甚至可以写成localhost\sql2012
。
下一个子句指示您应该使用 Windows 身份验证(即任何有效登录的 Windows 用户都可以登录到 SQL Server)。
integrated security = true;
你也可以用sspi
代替true
,因为它们有相同的效果。其他参数也是可用的。稍后您将使用它来指定您想要连接的数据库。
接下来创建一个连接(一个SqlConnection
对象),向它传递连接字符串。这不会创建数据库会话。它只是创建一个对象,稍后您将使用它来打开一个会话。
// Create connection SqlConnection conn = new SqlConnection(connString);
现在您有了一个连接,但是您仍然需要通过调用连接上的Open
方法来建立与数据库的会话。如果试图打开一个会话失败,将会抛出一个异常,所以您使用一个try
语句来启用异常处理。您在调用Open
后会显示一条消息,但是这条线只有在连接成功打开时才会被执行。
try { // Open connection conn.Open(); Console.WriteLine("Connection opened."); }
在代码的这个阶段,您通常会通过打开的连接发出一个查询或执行一些其他数据库操作。然而,我们将把它留到后面的章节,在这里只关注连接。
接下来是一个异常处理程序,以防Open()
失败,如本章前面的图 12-2 所示。
catch (SqlException ex) { // Display error Console.WriteLine("Error: " + ex.Message + ex.StackTrace); }
每个数据提供程序都有一个特定的异常类用于其错误处理;SqlException
是 SQL Server 数据提供程序的类。异常中提供了关于数据库错误的特定信息,但是这里只显示了它的原始内容。
当您完成数据库时,您调用Close()
来终止会话,然后打印一条消息来显示调用了Close()
。
finally { // Close connection conn.Close(); Console.WriteLine("Connection closed."); }
你在finally
块中调用Close()
,以确保总是被调用。
控制台应用倾向于在短时间内加载带有输出的命令窗口,然后自动关闭。要在屏幕上保持窗口以便阅读和理解输出,调用Console
类的ReadLine()
方法。这将让窗口停留,直到你按下回车键。
Console.ReadLine();
注意建立连接(数据库会话)的成本相对较高。它们使用客户端和服务器上的资源。尽管连接最终可能会通过垃圾收集或超时而关闭,但在不再需要时让一个连接保持打开是一种不好的做法。太多打开的连接会降低服务器速度或阻止建立新的连接。
注意,可以在关闭的连接上调用Close()
,不会抛出异常。因此,如果连接早一点关闭,或者即使它从未打开过,您的消息也会显示出来。参见图 12-2 ,连接失败但仍显示关闭信息。
在一个典型的例子中,多次调用Open()
和Close()
是有意义的。ADO.NET 支持断开的数据处理,即使与数据提供程序的连接已经关闭。模式如下所示:
`try
{
// open connection
conn.Open();
// online processing (e.g., queries) here
//
conn.Close(); // close connection
//
// offline processing here
//
conn.Open(); // reopen connection
//
// online processing(e.g., INSERT/UPDATE/DELETE) here
//
conn.Close(); // reclose connection
}
catch(SqlException ex)
{
// error handling code here
}
finally
{
// close connection
conn.Close();
}`
finally
块仍然调用Close()
,如果没有遇到异常就不必要地调用它,但这不是问题,也不昂贵,而且它确保连接将被关闭。尽管许多程序员在程序终止前一直保持连接,但这通常会浪费服务器资源。有了*连接池,*根据需要打开和关闭连接实际上比一劳永逸地打开更有效。
就这样!您已经完成了第一个连接示例。但是,既然您看到了一个可能的错误,那么让我们来看看连接错误的典型原因。
调试与 SQL Server 的连接
编写使用连接的 C# 代码通常是让连接工作的简单部分。问题通常不在于代码,而在于客户机(您的 C# 程序)和数据库服务器之间的连接参数不匹配。必须使用所有适当的连接参数,并且必须具有正确的值。即使是经验丰富的数据库专业人员,在第一次连接时也经常会遇到问题。
除了这里显示的参数之外,还有更多可用的参数,但你已经明白了。墨菲定律的一个推论适用于关系:如果几件事都可能出错,那么其中肯定会有一件出错。您的目标是检查连接的两端,以确保您所有的假设都是正确的,并且客户端程序指定的所有内容都在服务器上正确匹配。
解决方案通常在服务器端。如果 SQL Server 实例没有运行,客户端将尝试连接到不存在的服务器。如果未使用 Windows 身份验证,并且客户端上的用户名和密码与有权访问 SQL Server 实例的用户的用户名和密码不匹配,则连接将被拒绝。如果连接中请求的数据库不存在,将会出现错误。如果客户端的网络信息与服务器的网络信息不匹配,服务器可能不会收到客户端的连接请求,或者服务器的响应可能不会到达客户端。
对于连接问题,使用调试器来定位发生错误的代码行通常没有帮助——问题几乎总是发生在对Open
方法的调用上。问题是,为什么?您需要查看错误消息。
典型的错误如下:
Unhandled Exception: System.ArgumentException: Keyword not supported...
这是因为使用了无效的参数或值,或者连接字符串中的参数或值拼写错误。确保您输入了您真正想要输入的内容。
图 12-2 早先显示了可能是连接到 SQL Server 时最常见的消息。在这种情况下,很可能 SQL Server 根本没有运行。使用net start mssql$<your sql instance name>
重新启动 SQL Server Express 服务。
此消息的其他可能原因如下:
- The SQL Server instance name is incorrect. For example, you used
.\sqlexpress
, but SQL Server was installed with a different name. It is also possible that SQL Server is installed as the default instance (without instance name) or installed on another computer (see the next section); If this is the case, please correct the instance name.- SQL Server has not been installed—Go back to chapter 1 of and install SQL Server 2012 Express according to the instructions therein.
- There is a security problem-your Windows login and password are invalid on the server. This is unlikely to be a problem when connecting to a local instance of SQL Server Express, but it may happen when trying to connect to an instance of SQL Server on another server. There is a hardware problem-this is also unlikely if you try to connect to a server on the same machine.
SqlConnection 中的安全性和密码
SQL Server 2012 中有两种用户身份验证。首选方法是使用 Windows 身份验证(集成安全性),正如您在遵循本书中的示例时所做的那样。SQL Server 使用您的 Windows 登录名来访问该实例。您的 Windows 登录名必须存在于运行 SQL Server 的计算机上,并且您的登录名必须有权访问 SQL Server 实例,或者是具有访问权限的用户组的成员。
如果在连接字符串中不包含Integrated Security = true
(或Integrated Security = sspi
)参数,则连接默认为 SQL Server 安全,在 SQL Server 中使用单独的登录名和密码。
如何使用 SQL Server 安全性
如果您确实打算使用 SQL Server 安全性,因为您的公司或部门就是这样设置对您的 SQL Server 的访问权限的(可能因为某些客户端是非 Microsoft 的),您需要在连接字符串中指定用户名和密码,如下所示:
thisConnection.ConnectionString = @"server = sqlexpress; user id = sa; password = xly2z3";
用户名sa
是 SQL Server 的默认系统管理员帐户。如果已经设置了特定用户,如george
或payroll
,请指定该名称。sa
的密码在安装 SQL Server 时设置。如果您使用的用户名没有密码,您可以完全省略 password 子句或指定一个空密码,如下所示:
password =;
然而,空白密码是不好的做法,即使在测试环境中也应该避免。
【SqlConnection 的连接字符串参数
表 12-2 总结了 SQL Server 数据提供程序连接字符串的基本参数。
表 12-2 中的别名栏给出了替代参数名称。例如,您可以使用以下任一选项来指定服务器:
data source = .\<sql instance name> server = .\ <sql instance name> address = .\ <sql instance name> addr = .\ <sql instance name> network address = .\ <sql instance name>
连接池
一个值得注意的底层细节是*连接池,尽管您不应该改变它。*回想一下,就内存和时间而言,创建连接是非常昂贵的。使用连接池,关闭的连接不会立即被销毁,而是保存在内存中未使用的连接池中。如果新的连接请求与池中某个未使用的连接的属性相匹配,则该未使用的连接将用于新的数据库会话。
在网络上创建一个全新的连接可能需要几秒钟,而重用一个池化的连接可能需要几毫秒;使用池连接要快得多。连接字符串具有可以改变连接池大小甚至关闭连接池的参数。默认值(例如,缺省情况下连接池是打开的)适用于大多数应用。
提高对连接对象的使用
第一个示例程序中的代码很简单,所以您可以专注于连接如何工作。让我们增强一下。
在连接构造函数中使用连接字符串
在 ConnectionSql 项目中,您在单独的步骤中创建了连接并指定了连接字符串。因为您总是必须指定连接字符串,所以您可以使用将连接字符串作为参数的构造函数的重载版本。
// create connection SqlConnection conn = new SqlConnection(@"server = (local)\sqlexpress; integrated security = true; ");
这个构造函数在创建SqlConnection
对象时设置ConnectionString
属性。您将在下一个示例中尝试它,并在后面的章节中使用它。
显示连接信息
连接有几个提供连接信息的属性。这些属性大多数是只读的,因为它们的目的是显示而不是设置信息。(您可以在连接字符串中设置连接值。)这些属性在调试时通常很有用,可以验证连接属性是否是您所期望的。
这里,我们将描述大多数数据提供者共有的连接属性。
试试看:显示连接信息
在这个例子中,您将看到如何编写一个程序来显示连接信息。
-
将名为 ConnectionDisplay 的 C# 控制台应用项目添加到 Chapter12 解决方案中。
-
Rename
Program.cs
toConnectionDisplay.es
. When prompted to rename all references toProgram
, you can click either Yes or No. Replace the code with that in Listing 12-2.***清单 12-2。*T4
ConnectionDisplay.es
using System; using System.Data; using System.Data.SqlClient; namespace Chapterl2 { class ConnectionDisplay { static void Main() {
` // Create connection
SqlConnection conn = new SqlConnection(@” server = .\sql2012;
integrated security = true; ");try
{
// Open connection
conn.Open();
Console.WriteLine(“Connection opened.”);// Display connection properties
Console.WriteLine(“Connection Properties:”);
Console.WriteLine(“\tConnection String: {0}”,
conn.ConnectionString);
Console.WriteLine( “\tDatabase: {0}”, conn.Database);
Console.WriteLine( “\tDataSource: {0}”, conn.DataSource);
Console.WriteLine(“\tServerVersion: {0}”, conn.ServerVersion);
Console.WriteLine( “\tState: {0}”, conn.State);
Console.Writel_ine(“\tWorkstationld: {0}”, conn.Workstationld);}
catch (SqlException ex)
{
// Display error
Console.WriteLine("Error: " + ex.Message + ex.StackTrace);
}finally
{
// Close connection
conn.Close();
Console.WriteLine(“Connection closed.”);
}Console.ReadLine();
}
}
}` -
To set ConnectionDisplay as the startup project, select the project in Solution Explorer, right-click and select Set as StartUp Project, and run it by pressing Ctrl+F5. If the connection is successful, you’ll see output like that shown in Figure 12-3.
***图 12-3。*显示连接信息
它是如何工作的
ConnectionString
属性可以读写。在这里你只是展示它。
Console.WriteLine("\tConnection String: {0}", conn.ConnectionString);
您可以在逐字字符串中看到您赋予它的值,包括空格。
有什么意义?嗯,在调试连接时,验证连接字符串是否真的包含您认为已经赋值的值是很方便的。例如,如果您尝试不同的连接选项,程序中可能会有不同的连接字符串参数。你可能注释掉了一个,打算以后用,但是忘了。显示ConnectionString
属性有助于查看参数是否丢失。
下一条语句显示了Database
属性。由于每个 SQL Server 实例都有几个数据库,因此该属性显示您在连接时最初使用的数据库。
Console.WriteLine("\tDatabase: {0}",conn.Database);
在这个程序中,它显示
Database: master
由于您没有在连接字符串中指定数据库,所以您连接到了 SQL Server 的默认数据库主服务器。如果您想连接到 AdventureWorks 或您选择的数据库,您需要指定Database
参数,例如:
// connection string string connString = new SqlConnection(@"server = .\sqlexpress; database = northwind "; integrated security = true;)
还可以通过执行以下语句将默认数据库从 master 数据库更改为其他数据库,例如 AdventureWorks:
exec sp_defaultdb 'sa'/adventureworks'
同样,这是一个方便显示的属性,用于调试目的。如果您得到一个错误,说某个特定的表不存在,通常问题不是这个表不存在,而是它不在您所连接的数据库中。显示Database
属性有助于您快速找到这种错误。
提示如果您在连接字符串中指定了一个服务器上不存在的数据库,您可能会看到以下错误消息:“System .Data.SqlClient.SqlException:无法打开登录名请求的数据库“database”。登录失败。
您可以使用ChangeDatabase
方法更改连接上当前使用的数据库,如下所示:
Conn.ChangeDatabase("AdventureWorks");
下一条语句显示了DataSource
属性,该属性给出了 SQL Server 数据库连接的服务器实例名称。
Console.WriteLine( "\tDataSource: {0}", conn.DataSource);
这将显示与您的 SQL 实例名称相同的 SQL Server 实例名称;例如,在我的例子中,它将显示以下内容:
DataSource: .\sql2012
同样,这主要是为了调试的目的。
ServerVersion
属性显示服务器版本信息。
Console.WriteLine("\tServerVersion: {0}",conn.ServerVersion);
它显示了您在第一章中安装的 SQL Server Express 版本。(您的版本可能有所不同。)
ServerVersion: 11.00.1750
版本号对调试很有用。该信息实际上来自服务器,因此它表明连接正在工作。
注意 SQL Server 2008 是版本 10,SQL Server 2005(和 SSE)是版本 9。SQL Server 2000 是版本 8。
**State
属性表示连接是打开的还是关闭的。
Console.WriteLine( "\tState: {0}", conn.State);
因为您在调用Open()
之后显示这个属性,所以它表明连接是打开的。
State: Open
您已经显示了您自己的消息,表明连接是打开的,但是该属性包含当前状态。如果连接关闭,State
属性将是Closed
。
然后显示工作站 ID,它是标识客户端计算机的字符串。Workstationld
属性是特定于 SQL Server 的,可以方便地进行调试。
Console.WriteLine("\tWorkstationId: {0}",conn.WorkstationId);
它默认为计算机名。我的电脑命名为 VIDYAVRAT,但你的,当然,会有所不同。
Workstationld: <YourComputerName>
这对于调试非常有用,因为服务器上的 SQL Server 工具可以显示哪个工作站 ID 发出了特定的命令。如果您不知道是哪台机器导致了问题,您可以修改您的程序来显示Workstationld
属性,并将它们与服务器上显示的工作站 id 进行比较。
您还可以使用 workstation ID 连接字符串参数设置该属性,如下所示,因此,如果您希望建筑物 B 中的所有工作站都显示服务器上的该信息,您可以在程序中指明:
// Connection string string connString = @" server = \sql2012; workstation id = Building B; integrated security = true; ";
关于使用SqlClient
连接到 SQL Server 的基础讨论到此结束。现在让我们看看如何连接另一个数据提供者。
使用 OleDbConnection 连接到 SQL Server
正如您在第十章中看到的,您可以使用 OLE DB 数据提供程序来处理任何 OLE DB 兼容的数据存储。Microsoft 为 Microsoft SQL Server、Microsoft Access (Jet)、Oracle 以及各种其他数据库和数据文件格式提供 OLE DB 数据提供程序。
如果本机数据提供程序可用于特定的数据库或文件格式(如用于 SQL Server 的SqlClient
数据提供程序),通常使用它比使用通用 OLE DB 数据提供程序更好。这是因为 OLE DB 在 C# 程序和数据源之间引入了一个额外的间接层。没有本地数据提供程序的一种常见数据库格式是 Microsoft Access 数据库(.mdb
文件)格式,也称为 Jet 数据库引擎格式,因此在这种情况下,您需要使用 OLE DB(或 ODBC)数据提供程序。
我们不假设您有要连接的 Access 数据库,所以您将在 SQL Server 中使用 OLE DB。
试试看:使用 OLE DB 数据提供程序连接到 SQL Server
若要使用 OLE DB 数据提供程序连接到 SSE,请按照下列步骤操作:
-
添加一个名为 ConnectionOleDb 的 C# 控制台应用项目,并将
Program.cs
重命名为Connection01eDb.cs
。 -
Replace the code in
Connection01eDb.cs
with that in Listing 12-3. This is basically the same code asConnection.cs
, with the changed code in bold.***清单 12-3。*T4
Connection01eDb.cs
`using System;
using System.Data;
using System.Data.OleDb;namespace Chapter12
{
class ConnectionOleDb
{
static void Main()
{
// Create connection
OleDbConnection conn = new OleDbConnection(@“provider = sqloledb;
data source = .\sql2012; integrated security = sspi;”);try
{
// Open connection
conn.Open();
Console.WriteLine(“Connection opened.”);// Display connection properties
Console.WriteLine(“Connection Properties:”);
Console.WriteLine(“\tConnection String: {0}”,conn.ConnectionString);
Console.WriteLine(“\tDatabase: {0}”,conn.Database);
Console.WriteLine(“\tDataSource: {0}”,conn.DataSource);
Console.WriteLine(“\tServerVersion: {0}”,conn.ServerVersion);
Console.WriteLine(“\tState: {0}”,conn.State);
}
catch (OleDbException ex)
{
// Display error
Console.WriteLine("Error: " + ex.Message + ex.StackTrace);
}
finally
{
// Close connection
conn.Close();
Console.WriteLine(“Connection closed.”);
}Console.ReadLine();
}
}
}` -
Make ConnectionOleDb the startup project, and run it by pressing Ctrl+F5. If the connection is successful, you’ll see output like that shown in Figure 12-4.
***图 12-4。*显示 OLE DB 连接信息
它是如何工作的
我们将只讨论这个例子和前一个例子的不同之处。第一步是引用 OLE DB 数据提供程序命名空间。
System.Data.OleDb. using System.Data.OleDb;
接下来,创建一个OleDbConnection
对象,而不是一个SqlConnection
对象。请注意连接字符串的更改。您可以使用Provider
和Data Source
来代替server
参数。注意Integrated Security
参数的值必须是sspi
,而不是true
。
// create connection OleDbConnection conn = new 01eDbConnection(@"provider = sqloledb;data source = .\sql2012; integrated security = sspi;" );
最后,请注意,您在显示中省略了Workstationld
属性。OLE DB 数据提供程序不支持它。
这是使用任何访问任何数据源的模式。NET 数据提供程序。使用特定于数据提供程序的参数指定连接字符串。使用数据提供程序命名空间中的适当对象。仅使用该数据提供程序提供的属性和方法。
总结
在本章中,您使用两个数据提供程序及其相应的连接字符串、参数和值创建、打开和关闭了连接。使用连接属性创建连接后,您显示了有关连接的信息。您还看到了如何处理与连接相关的各种异常。
在下一章,你将看到 ADO.NET 命令,并了解如何使用它们来访问数据。**
十三、执行 ADO.NET 命令来检索数据
一旦建立了与数据库的连接,您就想开始与它交互,让它为您做一些有用的事情。您可能需要检索、添加、更新或删除一些数据,或者以其他方式修改数据库,通常是通过运行查询。无论什么任务,都不可避免地会涉及到一个命令。
在这一章中,我们将解释命令,这些命令是封装您想要执行的操作的 T-SQL 的对象,并提供将它提交给数据库的方法。每个数据提供者都有一个实现System.Data.IDbCommand
接口的命令类。
在本章中,我们将介绍以下内容:
- Create command
- executive order
- Execute commands with multiple results.
- execute statement
- Use stored procedures
在我们的示例中,我们将使用 SQL Server 数据提供程序(System.Data.SqlClient
)。它的命令命名为SqlCommand
。其他数据提供程序的命令工作方式相同。
创建命令
对于要对数据库执行的命令,每个命令都必须与到数据库的连接相关联。通过设置命令的Connection
属性可以做到这一点,为了节省资源,多个命令可以使用同一个连接。您可以使用SqlCommand
构造函数创建一个命令。一旦创建了一个命令,就可以执行与已建立的连接相关联的 SQL 语句了。您将在下一节的语句中看到命令的执行。
给命令分配文本
每个命令都有一个属性CommandText
,它保存您创建的命令对象将执行的 SQL 语句。您可以直接分配给此属性,也可以在构造命令时指定它。让我们看看这些替代方案。
试试看:设置 CommandText 属性
下面的 Windows 应用展示了如何使用SqlCommand
遍历结果集并检索行。
-
创建一个名为 Chapter13 的新 Windows 窗体应用项目。当解决方案资源管理器打开时,保存解决方案。
-
将第十三章项目重命名为 ADO。NET_Command 。将
Form1.cs
文件重命名为CommandText.cs
。 -
通过单击窗体的标题栏选择 CommandText 窗体,并将 Size 属性的宽度设置为 287,高度设置为 176。
-
将 TextBox 控件拖到窗体上,并将其放在窗体的中央。选择此 TextBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtCommandText。
- 将位置属性的 X 设置为 12,Y 设置为 12。
- 将 Multiline 属性设置为 True。
- 将 Size 属性的宽度设置为 244,高度设置为 106。
- 将文本属性留空。
-
Now your CommandText form in the Design view should like Figure 13-1.
***图 13-1。*CommandText 表单的设计视图
-
Double-click the empty surface of the
CommandText.cs
form, and it will open the code editor window, showing theCommandText_Load
event. Modify theCommandText_Load
event to look like Listing 13-1.清单 13-1。
CommandText.cs
` Using System.Data.SqlClient;
private void CommandText_Load(object sender, EventArgs e)
{
// Create connection
SqlConnection conn = new SqlConnection(@“server = .\sql2012;
integrated security = true; database = AdventureWorks”);// Create command
SqlCommand cmd = new SqlCommand();try
{
// Open connection
conn.Open();txtSQL.AppendText(“Connection opened \n” );
txtSQL.AppendText(“Command created.\n”);
// Setting CommandText
cmd.CommandText = @“select Name,ProductNumber
from Production.Product”;txtSQL.AppendText(“Ready to execute SQL Statement: \n\t\t\t” +
cmd.CommandText);}
catch (SqlException ex)
{
MessageBox.Show(ex.Message + ex.StackTrace,“Exception Details”);
}
finally
{
conn.Close();
txtSQL.AppendText(“\nConnection Closed.”);
}
}` -
Build the project, and run it by pressing Ctrl+F5. You should see the results in Figure 13-2.
***图 13-2。*使用
CommandText
显示 SQL 语句
它是如何工作的
属性返回一个字符串,所以您可以像显示任何其他字符串一样显示它。当您最终执行分配给CommandText
属性的 SQL 语句时,它将返回AdventureWorks
产品表中产品的名称和产品编号值。
注意在命令可以执行之前,你必须设置命令的
Connection
和CommandText
属性。
` // Create command
SqlCommand cmd = new SqlCommand();
// Setting CommandText
cmd.CommandText = @“select Name,ProductNumber
from Production.Product”;`
当您使用其构造函数的另一种变体创建命令时,可以设置这两个属性,如下所示:
`// create command (with both text and connection)
String sql = @“select Name,ProductNumber from Production.Product”;
SqlCommand cmd = new SqlCommand(sql, thisConnection);`
这相当于前面显式分配每个属性的代码。这是最常用的SqlCommand
构造函数的变体,您将在本章的剩余部分使用它。
执行命令
除非您可以执行命令,否则命令没有多大用处,所以现在让我们来看看。命令有几种不同的方法来执行 SQL。这些方法之间的差异取决于您对 SQL 的预期结果。查询返回数据行*(结果集*),但是INSERT
、UPDATE
和DELETE
语句不返回。你通过考虑你期望返回的内容来决定使用哪种方法(见表 13-1 )。
您刚刚在示例中使用的 SQL 应该返回一个值,即雇员人数。查看表 13-1 ,可以看到应该使用SqlCommand
的ExecuteScalar()
方法返回这一个结果。让我们试试。
用标量查询执行命令
ExecuteScalar
是用于执行由标量函数组成的 SQL 语句的方法。标量函数是从表中的整组行中只返回一个值的函数。例如,Min( )
、Max( )
、Sum( )
、Count( )
等等,都是标量函数的几个例子。如果从 Employee 执行一个查询,比如Select Min(Salary)
,那么不管表中有多少行,都只会返回一行。现在让我们看看ExecuteScalar( )
方法如何处理这样的 SQL 查询。
尝试一下:使用 ExecuteScalar 方法
要使用ExecuteScalar
方法,请遵循以下步骤:
-
选择 ADO。NET_Command 项目,右击并选择“添加 Windows 窗体”。从打开的对话框中,确保选择了 Windows 窗体,并将
Form1.cs
重命名为CommandScalar.cs
。单击“确定”将该表单添加到 ADO。NET_Command 项目。 -
通过单击窗体的标题栏选择 CommandScalar 窗体,并将 Size 属性的宽度设置为 385,高度设置为 126。
-
将 Label 控件拖到窗体上,并将其放在窗体的左侧。选择此标签控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblRowCount。
- 将位置属性的 X 设置为 4,Y 设置为 35。
- 将 Size 属性的宽度设置为 87,高度设置为 13。
- 将 Text 属性设置为 Total Row Count。
-
将 TextBox 控件拖到窗体上,并将其放置在 Label 控件旁边。选择此 TextBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtScalar。
- 将位置属性的 X 设置为 97,Y 设置为 12。
- 将 Multiline 属性设置为 True。
- 将 ScrollBars 属性设置为 Both。
- 将 Size 属性的宽度设置为 164,高度设置为 65。
- 将文本属性留空。
-
将 Button 控件拖到窗体上,并将其放在 TextBox 旁边。选择此按钮控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 btnRowCount。
- 将位置属性的 X 设置为 269,Y 设置为 30。
- 将 Size 属性的宽度设置为 88,高度设置为 23。
- 设置 Text 属性来计算行数。
-
Now your CommandScalar form in the Design view should like Figure 13-3.
***图 13-3。*命令缩放表单的设计视图
-
Double-click the Button control; it will open the code editor window, showing the
btnRowCount_Click
event. Place the code into theclick
event code template so it looks like Listing 13-2.清单 13-2。
CommandScalar.cs
`using System.Data.SqlClient;
private void btnRowCount_Click(object sender, EventArgs e)
{
// Create connection
SqlConnection conn = new SqlConnection(@“server = .\sql2012;
integrated security = true; database = AdventureWorks”);// Create Scalar query
string sql = @“select count(*)
from Production.Product”;// Create command
SqlCommand cmd = new SqlCommand(sql, conn);
txtScalar.AppendText(“Command created and connected.\n”);try
{
// Open connection
conn.Open();txtScalar.AppendText(“Number of Product is :”);
// Execute Scalar query with ExecuteScalar method
txtScalar.AppendText(cmd.ExecuteScalar().ToString());
txtScalar.AppendText(“\n”);
}catch (SqlException ex)
{
MessageBox.Show(ex.ToString());
}finally
{
conn.Close();
txtScalar.AppendText(“Connection Closed.”);
}
}` -
To set the CommandScalar form as the start-up form, modify the
Program.cs
statement:Application.Run(new CommandText ());
表现为:
Application.Run(new CommandScalar());
构建项目,并通过按 Ctrl+F5 运行它。
-
When the form loads, click the button Count Rows. The result should look like Figure 13-4.
***图 13-4。*执行标量命令
它是如何工作的
您所做的就是在对 TextBox 的AppendText
方法的调用中添加对ExecuteScalar()
的调用:
` txtScalar.AppendText(“Number of Product is :”);
// Execute Scalar query with ExecuteScalar method
txtScalar.AppendText(cmd.ExecuteScalar().ToString());`
ExecuteScalar()
获取CommandText
属性,并使用命令的Connection
属性将其发送到数据库。它将结果作为单个对象返回,您可以用 TextBox 的AppendText
方法显示该对象。
ExecuteScalar()
方法的返回类型是object
,它是。NET Framework,当您记住数据库可以保存任何类型的数据时,这是非常有意义的。所以,如果你想把返回的对象赋给一个特定类型的变量(例如 ??),你必须把对象转换成特定的类型。如果类型不兼容,系统将生成一个运行时错误,指示无效的强制转换。
下面是一个演示这一思想的例子。在其中,您将来自ExecuteScalar()
的结果存储在变量count
中,并将其转换为特定的类型int
。
int count = (int) cmd.ExecuteScalar(); txtScalar.AppendText ("Number of Products is: "+ count);
如果你确定结果的类型总是一个int
(与COUNT(*)
的安全赌注),那么前面的代码是安全的。但是,如果您将int
保留在原位,并将命令的CommandText
更改为以下内容:
select Name from Production.Product where ProductNumber='BA-8327'
然后ExecuteScalar()
将返回字符串“Bearing Ball
”而不是一个整数,您将得到这个异常:
Unhandled Exception: System.InvalidCastException: Specified cast is not valid.
因为你不能把一个string
投射到一个int
。
如果一个查询实际上返回了多行,而您认为它只会返回一行,那么可能会出现另一个问题。在这种情况下,ExecuteScalar( )
只返回结果的第一行,忽略其余的行。如果你使用ExecuteScalar( )
,确保你不仅期望而且实际上得到一个返回值。
执行有多个结果的命令
对于希望返回多行和多列的查询,使用命令的ExecuteReader()
方法。
ExecuteReader()
返回一个数据读取器,这是一个SqlDataReader
类的实例,我们将在下一章学习。数据读取器具有允许您读取结果集中的连续行并检索单个列值的方法。
我们将把数据读取器的细节留到下一章,但是为了便于比较,我们将在这里给出一个简单的例子,使用ExecuteReader()
方法从一个命令创建一个SqlDataReader
来显示查询结果。
试试看:使用 ExecuteReader 方法
要使用ExecuteReader
方法,请遵循以下步骤:
-
选择 ADO。NET_Command 项目,右击,选择【添加 窗口窗体。从打开的对话框中,确保选择了 Windows 窗体,并将
Form1.cs
重命名为CommandReader.cs
。单击“确定”将该表单添加到 ADO。NET_Command 项目。 -
通过单击窗体的标题栏选择 CommandReader 窗体,并将 Size 属性的宽度设置为 455,高度设置为 283。
-
将 TextBox 控件拖动到窗体上。选择此 TextBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtReader。
- 将位置属性的 X 设置为 12,Y 设置为 12。
- 将 Multiline 属性设置为 True。
- 将 ScrollBars 属性设置为垂直。
- 将 Size 属性的宽度设置为 415,高度设置为 223。
- 将文本属性留空。
-
Now your CommandReader form in the Design view should like Figure 13-5.
***图 13-5。*命令阅读器表单的设计视图
-
Now double-click the empty surface of the
CommandReader.cs
form, and it will open the code editor window, showing theCommandReader_Load
event. Modify theCommandReader_Load
event to look like Listing 13-3.***清单 13-3。*T4
CommandReader.cs
`Using System.Data.SqlClient;
private void CommandReader_Load(object sender, EventArgs e)
{
// Create connection
SqlConnection conn = new SqlConnection(@"
server = .\sql2012;
integrated security = true;
database = AdventureWorks");// Create command
string sql = @“select Name,ProductNumber
from Production.Product”;SqlCommand cmd = new SqlCommand(sql, conn);
txtReader.AppendText(“Command created and connected.\n\n”);try
{
// Open connection
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader();while (rdr.Read())
{
txtReader.AppendText(“\nProduct: “);
txtReader.AppendText(rdr.GetValue(1) + “\t\t” + rdr.GetValue(0));
txtReader.AppendText(”\n”);
}
}catch (SqlException ex)
{
MessageBox.Show(ex.Message + ex.StackTrace, “Exception Details”);
}finally
{
conn.Close();
txtReader.AppendText(“Connection Closed.”);
}
}` -
To set the CommandReader form as the start-up form, modify the
Program.cs
statement:Application.Run(new CommandScalar ());
表现为:
Application.Run(new CommandReader());
构建项目,并通过按 Ctrl+F5 运行它。
-
When the form loads, the result should look like Figure 13-6.
***图 13-6。*使用数据阅读器
它是如何工作的
在本例中,您使用ExecuteReader()
方法检索并输出生产中所有产品的名称和产品编号值。产品表。与ExecuteScalar()
一样,ExecuteReader()
获取CommandText
属性,并使用来自Connection
属性的连接将其发送到数据库。
当您使用ExecuteScalar
方法时,您只产生一个标量值。相反,使用ExecuteReader()
会返回一个SqlDataReader
对象。
// execute query SqlDataReader rdr = cmd.ExecuteReader(); while (rdr.Read()) { txtReader.AppendText(rdr.GetValue(1) + "\t\t" + rdr.GetValue(0)); }
SqlDataReader
对象有一个依次获取每一行的Read()
方法和一个获取行中某一列的值的GetValue
方法。它检索其值的特定列由指示该列索引的整数参数给出。注意GetValue
使用的是从零开始的索引,所以第一列是第 0 列,第二列是第 1 列,依此类推。由于查询要求两列,Name 和 ProductNumber,所以在这个查询结果中这两列编号为 0 和 1。
执行非查询语句
ExecuteNonQuery
是用于执行由 DML 语句组成的 SQL 语句的方法。这些语句由 SQL Server 的INSERT
、UPDATE
和DELETE
功能组成。因此,ExecuteNonQuery()
用于向命令提供 DML 语句并执行它。正如您在前面的章节中可能已经注意到的那样,INSERT
、UPDATE
和DELETE
语句不会返回任何记录。现在让我们看看ExecuteNonQuery( )
方法如何处理这样的 SQL 查询。
尝试一下:使用 ExecuteNonQuery 方法
要使用ExecuteNonOuery
方法,请遵循以下步骤:
-
选择 ADO。NET_Command 项目,右击,选择【添加 窗口窗体。从打开的对话框中,确保选择了 Windows 窗体,并将
Form1.cs
重命名为CommandNonQuery.cs
。单击“确定”将该表单添加到 ADO。NET_Command 项目。 -
通过单击窗体的标题栏选择 CommandNonQuery 窗体,并将 Size 属性的宽度设置为 297,高度设置为 277。
-
将 GroupBox 控件拖到窗体上,并将其放在窗体的左侧。选择 GroupBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 gbInsertCurrency。
- 将位置属性的 X 设置为 21,Y 设置为 22。
- 将 Size 属性的宽度设置为 240,高度设置为 201。
- 将 Text 属性设置为插入货币。
-
将 Label 控件拖到名为 gbInsertCurrency 的 GroupBox 中,并将其放在 GroupBox 的左侧。选择此标签控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblCurrencyCode。
- 将位置属性的 X 设置为 16,Y 设置为 30。
- 将 Size 属性的宽度设置为 77,高度设置为 13。
- 将 Text 属性设置为货币代码。
-
将一个 TextBox 控件拖到窗体上,并将其放置在名为 Currency Code 的 Label 控件旁边。选择此 TextBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtCurrencyCode。
- 将位置属性的 X 设置为 99,Y 设置为 30。
- 将 Size 属性的宽度设置为 128,高度设置为 20。
- 将文本属性留空。
-
将另一个 Label 控件拖动到名为 gbInsertCurrency 的 GroupBox 中,并将其放置在货币代码标签的下方,靠近 GroupBox 的左侧。选择此标签控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblName。
- 将位置属性的 X 设置为 19,Y 设置为 64。
- 将 Size 属性的宽度设置为 35,高度设置为 13。
- 将 Text 属性设置为 Name。
-
将 TextBox 控件拖到窗体上,并将其放置在名为 Name 的 Label 控件旁边。选择此 TextBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 txtName。
- 将位置属性的 X 设置为 99,Y 设置为 64。
- 将 Size 属性的宽度设置为 128,高度设置为 20。
- 将文本属性留空。
-
Right now your CommandNonQuery form in the Design view should look like Figure 13-7.
***图 13-7。*command non query 表单的设计视图
-
继续设计表单,将另一个名为 gbInsertCurrency 的 Label 控件拖动到 GroupBox 上,并将其放置在 GroupBox 左侧的 Name 标签下方。选择此标签控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblModifiedDate。
- 将位置属性的 X 设置为 19,Y 设置为 97。
- 将 Size 属性的宽度设置为 73,高度设置为 13。
- 将 Text 属性设置为 Modified Date。
-
将一个 DateTimePicker 控件拖到窗体上,并将其放在修改后的日期标签控件旁边。选择此 DateTimePicker 控件,导航到“属性”窗口,并设置以下属性:
* 将 Name 属性设置为 dtpModifiedDate。
* 将 Format 属性设置为 Short。
* 将位置属性的 X 设置为 99,Y 设置为 97。
* 将 Size 属性的宽度设置为 128,高度设置为 20。 -
将 Button 控件拖到名为 gbInsertCurrency 的 GroupBox 中,并将其放置在 Label 和 TextBox 控件的下方。选择此按钮控件,导航到“属性”窗口,并设置以下属性:
* 将 Name 属性设置为 btnInsertCurrency。
* 将位置属性的 X 设置为 56,Y 设置为 133。
* 将 Size 属性的宽度设置为 128,高度设置为 23。
* 将 Text 属性设置为插入货币。 -
将另一个 Label 控件拖动到名为 gbInsertCurrency 的 GroupBox 中,并将其放置在“插入货币”按钮的下方。选择此标签控件,导航到“属性”窗口,并设置以下属性:
* 将 Name 属性设置为 lblInsertStatus。
* 将 AutoSize 属性设置为 False。
* 将位置属性的 X 设置为 22,Y 设置为 168。
* 将 Size 属性的宽度设置为 205,高度设置为 21。 -
现在你在设计视图中的 CommandNonQuery 表单应该类似于图 13-8 。
-
Double-click the Insert Currency button, and it will open the code editor window, showing the
btnInsertCurrency_Click
event. Modify thebtnInsertCurrency_Click
event to look like Listing 13-4.
***图 13-8。**命令非查询表单的设计视图*
***清单 13-4。**T4`CommandNonOuery.cs`*
`Using System.Data.SqlClient;
private void btnInsertCurrency_Click(object sender, EventArgs e)
{
// Create connection
SqlConnection conn = new SqlConnection(@"server = .\sql2012;
integrated security = true;
database = AdventureWorks");
// Insert Query
string sqlIns = "Insert Into Sales.Currency(CurrencyCode,Name,ModifiedDate)" +
"Values(" + "'" + txtCurrencyCode.Text + "','" +
txtName.Text + "','" + dtpModifiedDate.Value.ToString() + "')";
// Create command
SqlCommand cmd = new SqlCommand(sqlIns, conn);
try
{
// Open connection
conn.Open();` ` cmd.ExecuteNonQuery();
lblInsertStatus.Text = "New Currency Added Successfully!!";
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message + ex.StackTrace, "Exception Details");
}
finally
{
conn.Close();
}
}`
- To set the CommandReader form as the start-up form, modify the
Program.cs
statement:Application.Run(new CommandReader ());
表现为:
`Application.Run(new CommandNonQuery());`
构建项目,并通过按 Ctrl+F5 运行它。
- When the form loads and you are ready to enter currency details, you need to be careful because the table Sales.Currency used in the exercise has the column CurrencyCode defined as a primary key. Hence, if you try entering a currency that might already exist in the table (which will be the case when you try entering most of the well-known currencies), then you will get a primary key violation. For example, when you try to enter USD as the currency code, the moment you click the button Insert Currency, an exception occurs, as shown in Figure 13-9.
***图 13-9。**插入显示主键冲突的语句*
- Trying a successful entry in the table will become a possibility only when you enter a unique key, in other words, a CurrencyCode that doesn’t exist. To access the form again, click OK in the Exception dialog. Modify the Currency Code USD to US (I know this is not a real currency code, but for the sake of our example, it’s worth trying), and click Insert Currency button. You will see a successful insertion, as shown in Figure 13-10.
***图 13-10。**成功插入货币*
它是如何工作的
在这个程序中,您使用一个非查询将货币插入到销售额中。货币表。正如您在 CommandNonQuery 表单的设计视图中看到的,我们有两个 TextBox 控件和一个 DateTimePicker 控件,因此在这些控件中输入的值将通过 SqlCommand 对象提供给 SQL 表。因此,INSERT
查询将如下所示:
// Insert Query string sqlIns = "Insert Into Sales.Currency(CurrencyCode,Name,ModifiedDate)" + "Values(" + "'" + txtCurrencyCode.Text + "','" + txtName.Text + "','" + dtpModifiedDate.Value.ToString() + "')";
然后创建一个封装了INSERT
查询的命令。
// Create command SqlCommand cmd = new SqlCommand(sqlIns, conn); // Execute the SQL statements with a call to the following: cmd.ExecuteNonQuery(); ExecuteNonOuery() executes the INSERT statement, and if executed successfully, it will show the success message in the lblResultStatus control. lblInsertStatus.Text = "New Currency Added Successfully!!";
注意使用
ExecuteNonOuery()
,您几乎可以提交任何 SQL 语句,包括数据定义语言(DDL)语句,来创建和删除数据库对象,如表和索引。但是在工业界,开发人员最常用的是插入、更新和删除行。
正如您到目前为止所看到的,所有执行任务的 SQL 语句,如SELECT
、INSERT
、UPDATE
或DELETE
,都被硬编码到 C# 代码中。但大多数时候,开发人员编写存储过程来执行 SQL 操作;存储过程就像函数一样工作,可以接受参数来执行任务。存储过程的优势之一是在 SQL Server 级别创建了一个统一的 SQL 语句,因为您的 ADO.NET 代码只是调用它。
使用存储过程
正如您在第六章中所学的,存储过程是允许您重复执行某项任务的 SQL 语句的集合。建议使用存储过程,而不是在 C# 代码中硬编码 SQL 语句,因为它利用了 SQL Server 的编译并提供了基于性能的重用。存储过程只是替换执行插入、更新和删除等操作的硬编码代码。
创建存储过程来执行删除操作
在前面的 CommandNonQuery 练习中,您看到了 Sales 上的插入操作。AdventureWorks2008
数据库的货币表。现在让我们在同一个表上做一个删除操作,但是通过一个存储过程。
试试看:创建一个与 C# 一起使用的存储过程
让我们使用 SQL Server Management Studio 创建一个存储过程,该存储过程将 CurrencyCode 值作为参数,从销售中删除一种货币。AdventureWorks
数据库中的货币表。它只需要一个输入参数。
- 打开 SQL Server Management Studio。在“连接到服务器”对话框中,选择 localhost\ 作为服务器名称,然后单击“连接”。
- 在对象资源管理器中,展开“数据库”节点,选择 AdventureWorks 数据库,然后单击“新建查询”窗口。输入以下查询,然后单击执行。您应该会看到如图 13-11 所示的结果。
Create procedure sp_DeleteCurrency @currCode nvarchar(3) As Delete From Sales.Currency Where CurrencyCode = @currCode
***图 13-11。*创建存储过程删除货币
它是如何工作的
CREATE PROCEDURE
语句创建了一个有一个输入参数的存储过程。参数在过程名和AS
关键字之间指定。这里您只指定了参数名和数据类型,所以默认情况下它是一个输入参数。参数名以@
开头。
Create procedure sp_DeleteCurrency @currCode nvarchar(3) As Delete From Sales.Currency
该参数用在查询的WHERE
子句中。
Where CurrencyCode = @currCode
试试看:使用带有 ExecuteNonQuery 方法的存储过程
要使用ExecuteNonQuery
方法,请遵循以下步骤:
-
选择 ADO。NET_Command 项目,右击,选择【添加 窗口窗体。从打开的对话框中,确保选择了 Windows 窗体,并将
Form1.cs
重命名为CommandStoredProcedure.cs
。单击“确定”将该表单添加到 ADO。NET_Command 项目。 -
通过单击窗体的标题栏选择 CommandStoredProcedure 窗体,并将 Size 属性的宽度设置为 288,高度设置为 267。
-
将一个 GroupBox 控件拖到窗体上,并将其放在窗体的左侧。选择 GroupBox 控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 gbDeleteCurrency。
- 将位置属性的 X 设置为 12,Y 设置为 12。
- 将 Size 属性的宽度设置为 248,高度设置为 201。
- 将 Text 属性设置为删除货币。
-
将一个 ListBox 控件拖到名为 gbDeleteCurrency 的 GroupBox 中,并将其放在 GroupBox 的左侧。选择此列表框控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblCurrencyCode。
- 将位置属性的 X 设置为 19,Y 设置为 29。
- 将 Size 属性的宽度设置为 85,高度设置为 121。
-
将 Button 控件拖动到名为 gbLoadCurrency 的 GroupBox 上,并将其放置在 ListBox 控件的右侧。选择此按钮控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 btnLoadCurrency。
- 将位置属性的 X 设置为 118,Y 设置为 45。
- 将 Size 属性的宽度设置为 116,高度设置为 23。
- 设置 Text 属性以加载货币列表。
-
拖动 btnLoadCurrency 正下方的另一个 Button 控件。选择此按钮控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 btnDeleteCurrency。
- 将位置属性的 X 设置为 118,Y 设置为 100。
- 将 Size 属性的宽度设置为 116,高度设置为 23。
- 将 Text 属性设置为删除货币。
-
将 Label 控件拖到名为 gbDeleteCurrency 的 GroupBox 中,并将其放置在 lstCurrency ListBox 的下方。选择此标签控件,导航到“属性”窗口,并设置以下属性:
- 将 Name 属性设置为 lblDeleteStatus。
- 将 AutoSize 属性设置为 False。
- 将位置属性的 X 设置为 16,Y 设置为 165。
- 将 Size 属性的宽度设置为 218,高度设置为 21。
- 将“文本”属性留空。
-
Now your CommandStoredProcedure form in the Design view should like Figure 13-12.
***图 13-12。*命令存储过程表单的设计视图
-
Double-click the Load Currency List button, and it will open the code editior window, showing the
btnLoadCurrency_Click
event. Modify thebtnLoadCurrency_Click
event to look like Listing 13-5.***清单 13-5。*T4
CommandStoredProcedure.cs
Using System.Data.SqlClient; private void btnLoadCurrency_Click(object sender, EventArgs e) { // Create connection SqlConnection conn = new SqlConnection(@"server = .\sql2012; integrated security = true;
` database = AdventureWorks");// Select query
string sqlSelect = @“select CurrencyCode
from Sales.Currency”;SqlCommand cmd = new SqlCommand(sqlSelect, conn);
try
{
// Open connection
conn.Open();
// Execute query via ExecuteReader
SqlDataReader rdr = cmd.ExecuteReader();while (rdr.Read())
{
lstCurrency.Items.Add(rdr[0]);
}
}catch (SqlException ex)
{
MessageBox.Show(ex.Message + ex.StackTrace, “Exception Details”);
}
finally
{
conn.Close();
}
}` -
Now it’s time to add functionality for the Delete Currency button. Double-click the Delete Currency button, and it will open the code editior window, showing the
btndeleteCurrency_Click
event. Modify thebtnDeleteCurrency_Click
event to look like Listing 13-6.
***清单 13-6。**T4`CommandStoredProcedure.cs`*
`private void btnDeleteCurrency_Click(object sender, EventArgs e)
{
// Create connection
SqlConnection conn = new SqlConnection(@"server = .\sql2012;
integrated security = true;
database = AdventureWorks");
// Create command object with Stored Procedure name
SqlCommand cmd = new SqlCommand("sp_DeleteCurrency", conn);
//Set command object for Stored Procedure execution
cmd.CommandType = CommandType.StoredProcedure;` ` cmd.Parameters.Add(new SqlParameter("currCode", SqlDbType.NVarChar, 3));
cmd.Parameters["currCode"].Value = lstCurrency.SelectedItem.ToString();
try
{
// Open connection
conn.Open();
// Delete Query
if (lstCurrency.SelectedIndex == -1)
{
MessageBox.Show("Please Select a Currency before performing Delete
action",
"Information");
}
else
{
cmd.ExecuteNonQuery();
lblDeleteStatus.Text = "Currency is Deleted Successfully!!";
}
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message + ex.StackTrace, "Exception Details");
}
catch (NullReferenceException ex)
{
MessageBox.Show("Load the Currency List first" + ex.StackTrace, "Exception
Details");
}
finally
{
conn.Close();
}
}`
- To set the CommandStoredProcedure form as the start-up form, modify the
Program.cs
statement.Application.Run(new CommandNonQuery ());
表现为:
`Application.Run(new CommandStoredProcedure());`
构建项目,并通过按 Ctrl+F5 运行它。
- 当窗体加载时,首先单击名为“加载货币列表”的按钮控件;这将从销售中加载货币。货币表。
- Now, select a currency (the one that you have not added), and click the Delete Currency button. As you may remember, the Sales.Currency table uses CurrencyCode as a primary key, so this key column is referenced by the Sales.CountryRegionCurrency table, and so a reference constraint–related exception will occur, as shown in Figure 13-13.
***图 13-13。**删除显示引用约束冲突的语句*
- Click OK. As you have seen, you can’t delete the currencies that have referenced entries in the Sales.CountryCurrencyRegion table. Hence, you can only delete the entry that is not related to this referencing table. In the CommandNonQuery exercise, you inserted a CurrencyCode called US, as shown in Figure 13-9. Scroll the currency list until you see US, and then click the Delete Currency button. You will see the successful deletion of the currency, as shown in Figure 13-14.
***图 13-14。**成功删除货币*
- 如果您再次点击加载货币列表,您会看到货币代码 US 并未列出,因为它已被删除。
它是如何工作的
在这个程序中,您使用一个存储过程从销售中删除货币。货币表。正如您在 CommandStoredProcedure 窗体的设计视图中所看到的,您有一个 ListBox 和两个 Button 控件。在列表框中选择的值将被传递给一个存储过程,该存储过程有一个删除查询。因此,通过以下语句为将值传递给存储过程做准备:
` // Create command object with Stored Procedure name
SqlCommand cmd = new SqlCommand(“sp_DeleteCurrency”, conn);
//Set command object for Stored Procedure execution
cmd.CommandType = CommandType.StoredProcedure;`
一旦指定了命令对象将使用存储过程,就该准备存储过程执行所需的参数了。
cmd.Parameters.Add(new SqlParameter("currCode", SqlDbType.NVarChar, 3));
如您所见,我们希望 ListBox 的选择被删除,所以我们将数据作为ListBox.SelectedItem
传递给存储过程参数。
cmd.Parameters["currCode"].Value = lstCurrency.SelectedItem.ToString();
一旦参数准备好了,我们就打开连接并使用ExecuteNonQuery
执行存储过程。
` // Open connection
conn.Open();
// Execute command associated with StoredProcedure
cmd.ExecuteNonQuery();`
总结
在本章中,我介绍了什么是 ADO.NET 命令以及如何创建一个Command
对象。我还讨论了将命令与连接相关联、设置命令文本以及使用ExecuteScalar()
、ExecuteReader()
和ExecuteNonOuery()
语句。您还了解了如何在 C# 代码中使用存储过程来执行 DML 操作。在下一章,你将会看到数据阅读器。