通过


创建数据访问层 (VB)

作者 :斯科特·米切尔

下载 PDF

在本教程中,我们将从头开始,使用类型化数据集创建数据访问层(DAL),以访问数据库中的信息。

简介

作为 Web 开发人员,我们的生活围绕处理数据。 我们创建数据库来存储数据、用于检索和修改数据的代码,以及用于收集和汇总数据的网页。 这是一个冗长的系列教程中的第一个教程,它将探索在 ASP.NET 2.0 中实现这些常见模式的技术。 首先,我们将使用类型化数据集、强制实施自定义业务规则的业务逻辑层(BLL)和由共享公共页面布局的 ASP.NET 页面组成的呈现层创建由数据访问层(DAL)组成的 软件体系结构 。 完成此后端基础后,我们将进入报告,显示如何显示、汇总、收集和验证来自 Web 应用程序的数据。 这些教程旨在简洁明了,提供了大量屏幕截图的分步说明,让你直观地完成该过程。 每个教程在 C# 和 Visual Basic 版本中都可用,并包括下载使用的完整代码。 (第一篇教程相当长,但其余教程以更易消化的区块形式呈现。

对于这些教程,我们将使用放置在 App_Data 目录中的 Microsoft SQL Server 2005 Express Edition 版本的 Northwind 数据库。 除了数据库文件, App_Data 该文件夹还包含用于创建数据库的 SQL 脚本,以防要使用其他数据库版本。 如果使用其他 SQL Server 版本的 Northwind 数据库,则需要更新 NORTHWNDConnectionString 应用程序文件中的设置 Web.config 。 Web 应用程序是使用 Visual Studio 2005 Professional Edition 作为基于文件系统的网站项目生成的。 但是,所有教程都同样适用于 Visual Studio 2005 免费版 Visual Web 开发人员

在本教程中,我们将从头开始创建数据访问层(DAL),然后在第二个教程中创建 业务逻辑层(BLL), 并在第三个教程中处理 页面布局和导航 。 第三个之后的教程将建立在前三个基础的基础上。 在第一个教程中,我们有很多内容要介绍,因此请启动 Visual Studio,让我们开始吧!

步骤 1:创建 Web 项目并连接到数据库

在创建数据访问层(DAL)之前,首先需要创建网站并设置数据库。 首先,创建新的基于文件系统的 ASP.NET 网站。 为此,请转到“文件”菜单并选择“新建网站”,显示“新建网站”对话框。 选择 ASP.NET 网站模板,将“位置”下拉列表设置为文件系统,选择要放置网站的文件夹,并将语言设置为 Visual Basic。

创建新文件 System-Based 网站

图 1:创建新文件 System-Based 网站(单击以查看全尺寸图像

这将创建一个包含 Default.aspx ASP.NET 页、 App_Data 文件夹和 Web.config 文件的新网站。

创建网站后,下一步是在 Visual Studio 的服务器资源管理器中添加对数据库的引用。 通过将数据库添加到服务器资源管理器,可以从 Visual Studio 中添加表、存储过程、视图等。 还可以通过查询生成器手动或图形方式查看表数据或创建自己的查询。 此外,在为 DAL 生成类型化数据集时,需要将 Visual Studio 指向应从中构造类型化数据集的数据库。 虽然我们可以在该时间点提供此连接信息,但 Visual Studio 会自动填充服务器资源管理器中已注册的数据库的下拉列表。

将 Northwind 数据库添加到服务器资源管理器的步骤取决于您是要在 App_Data 文件夹中使用 SQL Server 2005 Express Edition 数据库,还是想使用现有的 Microsoft SQL Server 2000 或 2005 数据库服务器安装。

在文件夹中使用数据库App_Data

如果没有要连接到的 SQL Server 2000 或 2005 数据库服务器,或者只想避免将数据库添加到数据库服务器,则可以使用位于下载网站文件夹(App_Data)中的 NORTHWND.MDF Northwind 数据库的 SQL Server 2005 Express Edition 版本。

放置在 App_Data 文件夹中的数据库会自动添加到“服务器资源管理器”中。 假设您的计算机上安装了 SQL Server 2005 Express Edition,您应该会在服务器资源管理器中看到一个名为 NORTHWND.MDF 的节点。您可以展开该节点,浏览其中的表、视图、存储过程等(请参阅图 2)。

App_Data 文件夹还可以存放 Microsoft Access .mdb 文件,这些文件与 SQL Server 的对应文件一样,会自动添加到服务器资源管理器中。 如果不想使用任何 SQL Server 选项,始终可以 安装 Northwind Traders 数据库和应用 ,并将其拖放到 App_Data 目录中。 但是,请记住,Access 数据库不如 SQL Server 功能丰富,并且不会设计为在网站方案中使用。 此外,35 多个教程中的几个将利用 Access 不支持的某些数据库级功能。

连接到 Microsoft SQL Server 2000 或 2005 数据库服务器中的数据库

或者,可以连接到安装在数据库服务器上的 Northwind 数据库。 如果数据库服务器尚未安装 Northwind 数据库,则首先必须通过运行本教程下载中包含的安装脚本将其添加到数据库服务器。

安装数据库后,转到 Visual Studio 中的服务器资源管理器,右键单击“数据连接”节点,然后选择“添加连接”。 如果未看到服务器资源管理器转到视图/服务器资源管理器,或按 Ctrl+Alt+S。 此时会显示“添加连接”对话框,可在其中指定要连接的服务器、身份验证信息和数据库名称。 成功配置数据库连接信息并单击“确定”按钮后,数据库将添加为数据连接节点下面的节点。 可以展开数据库节点以浏览其表、视图、存储过程等。

为您的数据库服务器的 Northwind 数据库添加一个连接

图 2:向数据库服务器的 Northwind 数据库添加连接

步骤 2:创建数据访问层

使用数据时,一个选项是将特定于数据的逻辑直接嵌入到呈现层(在 Web 应用程序中,ASP.NET 页面构成呈现层)。 这可能采用在 ASP.NET 页的代码部分或使用标记部分中的 SqlDataSource 控件编写 ADO.NET 代码的形式。 无论哪种情况,此方法都紧密耦合数据访问逻辑与呈现层。 但是,建议的方法是将数据访问逻辑与表示层分开。 此单独的层称为“数据访问层”,简称“DAL”,通常作为单独的类库项目实现。 此分层体系结构的优点已得到很好的记录(请参阅本教程末尾的“进一步阅读”部分,了解这些优势),是我们将在本系列中采用的方法。

特定于基础数据源的所有代码(例如创建与数据库的连接、发出SELECTINSERTUPDATEDELETE和命令等)都应位于 DAL 中。 表示层不应包含对此类数据访问代码的任何引用,而是应调用 DAL 来处理所有数据请求。 数据访问层通常包含用于访问基础数据库数据的方法。 例如,Northwind 数据库包含 ProductsCategories 表,用于记录要销售的产品及其所属类别。 在我们的 DAL 中,我们将有如下方法:

  • GetCategories(), 这将返回有关所有类别的信息
  • GetProducts(),这将返回有关所有产品的信息
  • GetProductsByCategoryID(categoryID),这将返回属于指定类别的所有产品
  • GetProductByProductID(productID),这将返回有关特定产品的信息

调用这些方法时,将连接到数据库、发出适当的查询并返回结果。 如何返回这些结果非常重要。 这些方法可能只是返回由数据库查询填充的数据集或 DataReader,但理想情况下,应该使用强类型对象来返回这些结果。 强类型对象是其架构在编译时严格定义的对象,而相反的是松散类型对象,是架构在运行时之前未知的对象。

例如,DataReader 和 DataSet(默认情况下)是松散类型的对象,因为它们的架构由用于填充它们的数据库查询返回的列定义。 若要从松散类型的 DataTable 访问特定列,需要使用如下语法: DataTable.Rows(index)("columnName") 在此示例中,DataTable 的松散键入通过以下事实显示:我们需要使用字符串或序号索引访问列名。 另一方面,强类型数据表将每个列实现为属性,从而生成如下代码。 DataTable.Rows(index).columnName

若要返回强类型对象,开发人员可以创建自己的自定义业务对象或使用类型化数据集。 业务对象由开发人员实现为类,其属性通常反映业务对象所表示的基础数据库表的列。 类型化数据集是 Visual Studio 基于数据库架构生成的类,其成员根据此架构进行强类型化。 类型化数据集本身由扩展 ADO.NET DataSet、DataTable 和 DataRow 类的类组成。 除了强类型化 DataTable,类型化数据集现在还包括 TableAdapters,这些类包含用于填充 DataSet 的 DataTable 的方法,并将 DataTable 中的修改传播回数据库。

注意

有关使用类型化数据集与自定义业务对象的优点和缺点的详细信息,请参阅 设计数据层组件和通过层传递数据

我们将为这些教程的架构使用强类型的数据集。 图 3 说明了使用类型化数据集的应用程序的不同层之间的工作流。

所有数据访问代码将降级到 DAL

图 3:所有数据访问代码将降级到 DAL(单击以查看全尺寸图像

创建类型化数据集和表适配器

若要开始创建 DAL,首先将类型化数据集添加到项目中。 为此,请右键单击解决方案资源管理器中的项目节点,然后选择“添加新项”。 从模板列表中选择“数据集”选项并将其命名 Northwind.xsd

选择向项目添加新数据集

图 4:选择向项目添加新数据集(单击以查看全尺寸图像

单击“添加”后,当系统提示将数据集添加到 App_Code 文件夹时,请选择“是”。 然后将显示类型化数据集的设计器,TableAdapter 配置向导将启动,使你能够将第一个 TableAdapter 添加到 Typed DataSet。

类型化数据集充当强类型数据集合;它由强类型 DataTable 实例组成,每个实例又由强类型 DataRow 实例组成。 我们将在本教程系列中为需要处理的每个基础数据库表创建一个强类型 DataTable。 首先,为 Products 表创建一个 DataTable。

请记住,强类型的数据表不包含任何关于如何从其基础数据库表中访问数据的方法信息。 为了检索数据以填充 DataTable,我们使用 TableAdapter 类,该类充当数据访问层。 对于我们的 Products DataTable,TableAdapter 将包含GetProducts()GetProductByCategoryID(categoryID) 等方法,我们将在呈现层调用这些方法。 DataTable 的角色是充当用于在层之间传递数据的强类型对象。

TableAdapter 配置向导首先提示选择要使用的数据库。 下拉列表显示服务器资源管理器中的这些数据库。 如果未将 Northwind 数据库添加到服务器资源管理器,可以单击此时的“新建连接”按钮执行此操作。

从 Drop-Down 列表中选择 Northwind 数据库

图 5:从 Drop-Down 列表中选择 Northwind 数据库(单击以查看全尺寸图像

选择数据库并单击“下一步”后,系统会询问是否要将连接字符串Web.config保存到文件中。 通过保存连接字符串,可以避免在 TableAdapter 类中对其进行硬编码,如果将来连接字符串信息发生更改,这会简化操作。 如果选择将连接字符串保存在配置文件中,则将其放置在该节中<connectionStrings>,则可以选择加密以改进安全性,或者稍后通过 IIS GUI 管理工具中的新 ASP.NET 2.0 属性页进行修改,这对于管理员来说更理想。

将连接字符串保存到 Web.config

图 6:将连接字符串 Web.config 保存到 (单击以查看全尺寸图像

接下来,我们需要为第一个强类型 DataTable 定义架构,并为在填充强类型数据集时要使用的 TableAdapter 提供第一种方法。 这两个步骤是通过创建一个查询来同时完成的,该查询返回要反映在 DataTable 中的表中的列。 在向导的末尾,我们将为此查询提供方法名称。 完成此操作后,可以从呈现层调用此方法。 该方法将执行所定义的查询并填充一个强类型的数据表。

若要开始定义 SQL 查询,必须先指示希望 TableAdapter 如何发出查询。 可以使用即席 SQL 语句、创建新的存储过程或使用现有存储过程。 对于这些教程,我们将使用即席 SQL 语句。

使用即席 SQL 语句查询数据

图 7:使用即席 SQL 语句查询数据(单击以查看全尺寸图像

此时,我们可以手动键入 SQL 查询。 在 TableAdapter 中创建第一个方法时,通常需要让查询返回需要在相应的 DataTable 中表示的列。 我们可以通过创建一个查询来返回表中的所有列和所有行 Products 来实现此目的:

在文本框中输入 SQL 查询

图 8:在文本框中输入 SQL 查询(单击以查看全尺寸图像

或者,使用查询生成器并以图形方式构造查询,如图 9 所示。

通过查询编辑器以图形方式创建查询

图 9:通过查询编辑器以图形方式创建查询(单击以查看全尺寸图像

创建查询后,但在转到下一个屏幕之前,单击“高级选项”按钮。 在网站项目中,“生成插入、更新和删除语句”是默认选择的唯一高级选项;如果从类库或 Windows 项目运行此向导,则还将选择“使用乐观并发”选项。 现在先不要勾选“使用乐观并发”选项。 我们将在将来的教程中研究乐观并发。

仅选择“生成插入”、“更新”和“删除”语句选项

图 10:仅选择“生成插入”、“更新”和“删除”语句选项(单击以查看全尺寸图像

验证高级选项后,单击“下一步”转到最终屏幕。 在这里,我们被要求选择要添加到 TableAdapter 的方法。 填充数据有两种模式:

  • 使用此方法填充 DataTable 时,会创建一个方法,该方法采用 DataTable 作为参数,并根据查询结果填充它。 例如,ADO.NET DataAdapter 类使用其 Fill() 方法实现此模式。
  • 使用此方法返回 DataTable ,该方法会为你创建并填充 DataTable,并在方法返回值时返回它。

你可以让 TableAdapter 实现其中一种或两种模式。 还可以重命名此处提供的方法。 让我们同时选中这两个复选框,尽管我们在整个教程过程中只会使用后一种模式。 此外,让我们将相当泛型 GetData 的方法重命名为 GetProducts

如果选中,最后一个复选框“GenerateDBDirectMethods”将为 TableAdapter 创建Insert()Update()Delete()方法。 如果未选中此选项,则需要通过 TableAdapter 的唯一的 Update() 方法完成所有更新,该方法可以接受类型化数据集、DataTable、单个 DataRow 或 DataRow 数组。 (如果已取消选中图 9 中高级属性中的“生成插入、更新和删除语句”选项,此复选框的设置将不起作用。让我们选中此复选框。

将方法名称从 GetData 更改为 GetProducts

图 11:将方法名称从 GetData 更改为 GetProducts单击以查看全尺寸图像

点击“完成”以结束向导。 向导关闭后,我们将返回到数据集设计器,其中显示了我们刚刚创建的 DataTable。 可以查看 DataTable (ProductsProductID 等) 中的列列表,以及 ProductsTableAdapter (Fill()GetProducts()) 的方法。

产品 DataTable 和 ProductsTableAdapter 已添加到类型化数据集

图 12Products DataTable 并 ProductsTableAdapter 已添加到类型化数据集(单击以查看全尺寸图像

此时,我们有一个类型化 DataSet,其中包含单个 DataTable(Northwind.Products)和具有 GetProducts() 方法的强类型 DataAdapter 类(NorthwindTableAdapters.ProductsTableAdapter)。 这些对象可用于从代码访问所有产品的列表,例如:

Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
Dim products as Northwind.ProductsDataTable
products = productsAdapter.GetProducts()
For Each productRow As Northwind.ProductsRow In products
    Response.Write("Product: " & productRow.ProductName & "<br />")
Next

此代码不需要我们编写任何与数据访问相关的代码。 我们不必实例化任何 ADO.NET 类,也不必引用任何连接字符串、SQL 查询或存储过程。 相反,TableAdapter 为我们提供了低级别数据访问代码。

此示例中使用的每个对象也是强类型化的,允许 Visual Studio 提供 IntelliSense 和编译时类型检查。 TableAdapter 返回的所有 DataTable 都可以绑定到 ASP.NET 数据 Web 控件,例如 GridView、DetailsView、DropDownList、CheckBoxList 等。 下面的示例演示了在事件处理程序 Page_Load 中将该方法 GetProducts() 返回的 DataTable 绑定到 GridView,只需三行简短的代码。

AllProducts.aspx

<%@ Page Language="VB" AutoEventWireup="true" CodeFile="AllProducts.aspx.vb"
    Inherits="AllProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>View All Products in a GridView</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>
            All Products</h1>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

AllProducts.aspx.vb

Imports NorthwindTableAdapters
Partial Class AllProducts
    Inherits System.Web.UI.Page
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles Me.Load
        Dim productsAdapter As New ProductsTableAdapter
        GridView1.DataSource = productsAdapter.GetProducts()
        GridView1.DataBind()
    End Sub
End Class

产品列表显示在 GridView 中

图 13:产品列表显示在 GridView 中(单击以查看全尺寸图像

虽然此示例要求我们在 ASP.NET 页的 Page_Load 事件处理程序中编写三行代码,但在将来的教程中,我们将介绍如何使用 ObjectDataSource 以声明方式从 DAL 检索数据。 使用 ObjectDataSource,我们不必编写任何代码,也会收到分页和排序支持!

步骤 3:向数据访问层添加参数化方法

此时,我们的 ProductsTableAdapter 类只包含一种方法, GetProducts()该方法返回数据库中的所有产品。 虽然能够处理所有产品绝对有用,但有时我们需要检索有关特定产品或属于特定类别的所有产品的信息。 若要向数据访问层添加此类功能,可将参数化方法添加到 TableAdapter。

让我们添加该方法 GetProductsByCategoryID(categoryID) 。 若要向 DAL 添加新方法,请返回到数据集设计器,右键单击该 ProductsTableAdapter 部分,然后选择“添加查询”。

选择 TableAdapter 上的Right-Click 并选择“添加查询”

图 14:在 TableAdapter 上选择 Right-Click,然后选择添加查询

我们首先会提示我们是要使用即席 SQL 语句还是新的或现有的存储过程来访问数据库。 让我们选择再次使用即席 SQL 语句。 接下来,系统会询问要使用的 SQL 查询类型。 由于我们想要返回属于指定类别的所有产品,因此需要编写返回 SELECT 行的语句。

选择创建返回行的 SELECT 语句

图 15:选择创建返回 SELECT 行的语句(单击以查看全尺寸图像

下一步是定义用于访问数据的 SQL 查询。 由于我们希望仅返回属于特定类别的产品,因此我使用相同的SELECT语句GetProducts(),但添加以下WHERE子句: WHERE CategoryID = @CategoryID@CategoryID 参数向 TableAdapter 向导指示,要创建的方法需要具有相应类型的输入参数(即可为 null 的整数)。

输入查询以仅返回指定类别中的产品

图 16:输入查询以仅返回指定类别中的产品(单击以查看全尺寸图像

在最后一步中,我们可以选择要使用的数据访问模式,并自定义生成的方法的名称。 对于填充模式,我们将名称更改为 FillByCategoryID,而对于返回 DataTable 的返回模式(GetX 方法),我们使用 GetProductsByCategoryID

选择 TableAdapter 方法的名称

图 17:选择 TableAdapter 方法的名称(单击以查看全尺寸图像

完成向导后,数据集设计器包括新的 TableAdapter 方法。

产品现在可以按类别查询

图 18:产品现在可以按类别查询

请花点时间使用相同的技术来添加 GetProductByProductID(productID) 方法。

可以直接从数据集设计器测试这些参数化查询。 右键单击 TableAdapter 中的方法,然后选择“预览数据”。 接下来,输入要用于参数的值,然后单击“预览”。

显示属于饮料类别的这些产品

图 19:显示属于饮料类别的产品(单击查看全尺寸图像

GetProductsByCategoryID(categoryID)使用 DAL 中的方法,现在可以创建一个 ASP.NET 页面,该页面仅显示指定类别中的这些产品。 以下示例显示“饮料”类别中的所有产品,其值为 CategoryID 1。

Beverages.aspx

<%@ Page Language="VB" AutoEventWireup="true" CodeFile="Beverages.aspx.vb"
    Inherits="Beverages" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>Beverages</h1>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

Beverages.aspx.vb

Imports NorthwindTableAdapters
Partial Class Beverages
    Inherits System.Web.UI.Page
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles Me.Load
        Dim productsAdapter As New ProductsTableAdapter
        GridView1.DataSource =
         productsAdapter.GetProductsByCategoryID(1)
        GridView1.DataBind()
    End Sub
End Class

“饮料类别”中的这些产品会显示

图 20:显示“饮料类别”中的这些产品(单击以查看全尺寸图像

步骤 4:插入、更新和删除数据

有两种模式通常用于插入、更新和删除数据。 第一个模式,我称之为数据库直接模式,涉及创建方法,当方法被调用时,会向数据库发送INSERTUPDATEDELETE命令,操作的是单个数据库记录。 此类方法通常传入一系列标量值(整数、字符串、布尔值、DateTimes 等),这些值对应于要插入、更新或删除的值。 例如,对于 Products 表,delete 方法将在删除方法中采用整数参数,以指示要删除记录的 ProductID;而 insert 方法将采用字符串参数用于 ProductName,小数参数用于 UnitPrice,整数参数用于 UnitsOnStock,依此类推。

每次插入、更新和删除请求都会立即发送到数据库

图 21:每次插入、更新和删除请求都会立即发送到数据库(单击以查看全尺寸图像

另一种模式,我将其称为批量更新模式,是在一次方法调用中更新整个 DataSet、DataTable 或一组 DataRows。 使用此模式,开发人员可以删除、插入和修改数据表(DataTable)中的数据行(DataRows),然后将这些数据行或数据表传递到更新方法中。 然后,此方法枚举传入的 DataRows,确定它们是否已修改、添加或删除(通过 DataRow 的 RowState 属性值 ),并为每个记录发出相应的数据库请求。

调用 Update 方法时,所有更改都与数据库同步

图 22:调用 Update 方法时,所有更改都与数据库同步(单击以查看全尺寸图像

TableAdapter 默认使用批处理更新模式,但也支持 DB 直接模式。 由于我们在创建 TableAdapter 时从高级属性中选择了“生成插入、更新和删除语句”选项, ProductsTableAdapter 因此包含一个 Update() 实现批处理更新模式的方法。 具体而言,TableAdapter 包含 Update() 可以传递类型化数据集、强类型 DataTable 或一个或多个 DataRows 的方法。 如果在首次创建 TableAdapter 时选中“GenerateDBDirectMethods”复选框,那么 DB 直接模式也会通过Insert()Update()Delete() 方法来实现。

两种数据修改模式都使用 TableAdapter 的InsertCommandUpdateCommandDeleteCommand属性向数据库发出其INSERTUPDATEDELETE命令。 可以通过单击数据集设计器中的 TableAdapter,然后转到属性窗口来检查和修改InsertCommandUpdateCommandDeleteCommand属性。 (请确保已选择 TableAdapter,并且该ProductsTableAdapter对象是属性窗口下拉列表中选择的对象。

TableAdapter 具有 InsertCommand、UpdateCommand 和 DeleteCommand 属性

图 23:TableAdapter 具有InsertCommandUpdateCommandDeleteCommand属性(单击以查看全尺寸图像

若要检查或修改这些数据库命令属性中的任何一个,请单击 CommandText 将打开查询生成器的子属性。

在查询生成器中配置 INSERT、UPDATE 和 DELETE 语句

图 24:在查询生成器中配置INSERTUPDATEDELETE语句(单击以查看全尺寸图像

下面的代码示例演示如何使用批处理更新模式将未停产且库存不足 25 个产品的价格加倍:

Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
Dim products As Northwind.ProductsDataTable = productsAdapter.GetProducts()
For Each product As Northwind.ProductsRow In products
   If Not product.Discontinued AndAlso product.UnitsInStock <= 25 Then
      product.UnitPrice *= 2
   End if
Next
productsAdapter.Update(products)

下面的代码演示如何使用 DB 直接模式以编程方式删除特定产品,然后更新一个产品,然后添加新产品:

Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
productsAdapter.Delete(3)
productsAdapter.Update( _
    "Chai", 1, 1, "10 boxes x 20 bags", 18.0, 39, 15, 10, false, 1)
productsAdapter.Insert( _
    "New Product", 1, 1, "12 tins per carton", 14.95, 15, 0, 10, false)

创建自定义插入、更新和删除方法

由 DB 直接方法创建的Insert()Update()Delete()方法可能有点繁琐,尤其是对于包含多个列的表。 查看前面的代码示例,没有 IntelliSense 的帮助,具体哪个 Products 表列与 Update()Insert() 方法的每个输入参数相对应并不特别清晰。 有时,我们只想更新一列或两列,或者希望自定义 Insert() 方法可能返回新插入记录的 IDENTITY (自动递增)字段的值。

若要创建此类自定义方法,请返回到数据集设计器。 右键单击 TableAdapter 并选择“添加查询”,返回到 TableAdapter 向导。 第二个屏幕上,我们可以指示要创建的查询类型。 让我们创建一个添加新产品的方法,然后返回新添加记录的值 ProductID。 因此,选择创建 INSERT 查询。

创建向 Products 表添加新行的方法

图 25:创建向表添加新行 Products 的方法(单击以查看全尺寸图像

在下一个屏幕上,InsertCommandCommandText 出现。 通过在查询末尾添加 SELECT SCOPE_IDENTITY() 来扩充此查询,该查询将返回插入到同一范围内的列的最后一个 IDENTITY 标识值。 (请参阅 技术文档 ,了解有关 SCOPE_IDENTITY() 的更多信息,以及为什么你可能更倾向于使用 SCOPE_IDENTITY() 而不是 @@IDENTITY。) 在添加SELECT语句之前,请确保使用分号结束INSERT语句。

扩充查询以返回 SCOPE_IDENTITY() 值

图 26:扩充查询以返回 SCOPE_IDENTITY() 值(单击可查看全尺寸图像

最后,将新方法 InsertProduct命名为 。

将新方法名称设置为 InsertProduct

图 27:将“新建方法名称 InsertProduct ”设置为(单击可查看全尺寸图像

返回到数据集设计器时,会看到 ProductsTableAdapter 包含新方法 InsertProduct。 如果此新方法没有表中每个列 Products 的参数,则你很可能忘记使用分号终止 INSERT 语句。 配置InsertProduct方法,并确保INSERT语句和SELECT语句之间用分号分隔。

默认情况下,插入方法发出非查询方法,这意味着它们返回受影响的行数。 但是,我们希望 InsertProduct 该方法返回查询返回的值,而不是受影响的行数。 为此,请调整方法的属性为。

将 ExecuteMode 属性更改为 Scalar

图 28:将 ExecuteMode 属性更改为 Scalar单击以查看全尺寸图像

以下代码演示了此新 InsertProduct 方法的操作:

Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
Dim new_productID As Integer = Convert.ToInt32(productsAdapter.InsertProduct( _
    "New Product", 1, 1, "12 tins per carton", 14.95, 10, 0, 10, false))
productsAdapter.Delete(new_productID)

步骤 5:完成数据访问层

请注意,ProductsTableAdapters类会返回Products表中的CategoryIDSupplierID值,但不包括Categories表中的CategoryName列或Suppliers表中的CompanyName列,尽管这些列可能是我们在显示产品信息时想要显示的列。 我们可以扩充 TableAdapter 的初始方法,GetProducts(),以包括 CategoryNameCompanyName 列值,这样就可以更新强类型 DataTable,从而包含这些新列。

但是,这可能导致问题,因为 TableAdapter 插入、更新和删除数据的方法基于此初始方法。 幸运的是,用于插入、更新和删除的自动生成的方法不受子句中的 SELECT 子查询的影响。 通过小心地将查询作为子查询添加到 CategoriesSuppliers 中,而不是作为 JOIN,我们将避免不得不重新处理这些修改数据的方法。 在 ProductsTableAdapter 中右键单击 GetProducts() 方法,然后选择“配置”。 然后,调整 SELECT 子句,使其如下所示:

SELECT     ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName FROM Categories
WHERE Categories.CategoryID = Products.CategoryID) as CategoryName,
(SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName
FROM         Products

更新 GetProducts 方法的 SELECT 语句

图 29:更新 SELECT 方法的 GetProducts() 语句(单击以查看全尺寸图像

更新 GetProducts() 方法以使用此新查询后,DataTable 将包含两个新列: CategoryNameSupplierName

该产品数据表有新增两列

图 30Products数据表新增了两列

花点时间更新 SELECT 方法中的 GetProductsByCategoryID(categoryID) 子句。

如果使用 JOIN 语法更新 GetProducts()SELECT ,数据集设计器将无法通过 DB 直接模式自动生成插入、更新和删除数据库数据的方法。 相反,你必须像我们在本教程之前使用InsertProduct方法时那样手动创建它们。 此外,如果要使用批处理更新模式,则必须手动提供InsertCommandUpdateCommandDeleteCommand属性值。

将剩余的 TableAdapters 添加上去

到目前为止,我们只探讨了如何为单个数据库表使用单个 TableAdapter。 但是,Northwind 数据库包含几个相关表,我们需要在 Web 应用程序中使用这些表。 类型化数据集可以包含多个相关的数据表。 因此,为了完成我们的 DAL,我们需要为将在这些教程中使用的其他表添加数据表。 若要向类型化数据集添加新的 TableAdapter,请打开数据集设计器,在设计器中右键单击,然后选择“添加/TableAdapter”。 这将创建一个新的数据表 (DataTable) 和表适配器 (TableAdapter),并引导您完成我们在本教程前面讨论的向导。

花几分钟时间使用以下查询创建以下 TableAdapters 和方法。 请注意,ProductsTableAdapter 中的查询包括用于获取每个产品的类别和供应商名称的子查询。 此外,如果您一直在关注,那么您已经添加了ProductsTableAdapter类的GetProducts()GetProductsByCategoryID(categoryID)方法。

  • ProductsTableAdapter

    • GetProducts

      SELECT     ProductID, ProductName, SupplierID, 
      CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, 
      UnitsOnOrder, ReorderLevel, Discontinued, 
      (SELECT CategoryName FROM Categories WHERE
      Categories.CategoryID = Products.CategoryID) as 
      CategoryName, (SELECT CompanyName FROM Suppliers
      WHERE Suppliers.SupplierID = Products.SupplierID) 
      as SupplierName
      FROM         Products
      
    • GetProductsByCategoryID

      SELECT     ProductID, ProductName, SupplierID, CategoryID,
      QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
      ReorderLevel, Discontinued, (SELECT CategoryName
      FROM Categories WHERE Categories.CategoryID = 
      Products.CategoryID) as CategoryName,
      (SELECT CompanyName FROM Suppliers WHERE
      Suppliers.SupplierID = Products.SupplierID)
      as SupplierName
      FROM         Products
      WHERE      CategoryID = @CategoryID
      
    • GetProductsBySupplierID

      SELECT     ProductID, ProductName, SupplierID, CategoryID,
      QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
      ReorderLevel, Discontinued, (SELECT CategoryName
      FROM Categories WHERE Categories.CategoryID = 
      Products.CategoryID) as CategoryName, 
      (SELECT CompanyName FROM Suppliers WHERE 
      Suppliers.SupplierID = Products.SupplierID) as SupplierName
      FROM         Products
      WHERE SupplierID = @SupplierID
      
    • GetProductByProductID

      SELECT     ProductID, ProductName, SupplierID, CategoryID,
      QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
      ReorderLevel, Discontinued, (SELECT CategoryName 
      FROM Categories WHERE Categories.CategoryID = 
      Products.CategoryID) as CategoryName, 
      (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) 
      as SupplierName
      FROM         Products
      WHERE ProductID = @ProductID
      
  • 分类表适配器

    • GetCategories

      SELECT     CategoryID, CategoryName, Description
      FROM         Categories
      
    • GetCategoryByCategoryID

      SELECT     CategoryID, CategoryName, Description
      FROM         Categories
      WHERE CategoryID = @CategoryID
      
  • SuppliersTableAdapter

    • GetSuppliers

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      
    • GetSuppliersByCountry

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      WHERE Country = @Country
      
    • GetSupplierBySupplierID

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      WHERE SupplierID = @SupplierID
      
  • 员工表适配器

    • GetEmployees

      SELECT     EmployeeID, LastName, FirstName, Title,
      HireDate, ReportsTo, Country
      FROM         Employees
      
    • GetEmployeesByManager

      SELECT     EmployeeID, LastName, FirstName, Title, 
      HireDate, ReportsTo, Country
      FROM         Employees
      WHERE ReportsTo = @ManagerID
      
    • GetEmployeeByEmployeeID

      SELECT     EmployeeID, LastName, FirstName, Title,
      HireDate, ReportsTo, Country
      FROM         Employees
      WHERE EmployeeID = @EmployeeID
      

添加了四个 TableAdapters 后的数据集设计器

图 31:添加四个 TableAdapters 后的数据集设计器(单击以查看全尺寸图像

将自定义代码添加到 DAL

添加到类型化数据集中的 TableAdapters 和 DataTables 表示为 XML 架构定义文件 (Northwind.xsd)。 可以通过右键单击Northwind.xsd解决方案资源管理器中的文件并选择“查看代码”来查看此架构信息。

Northwinds 类型化数据集的 XML 架构定义 (XSD) 文件

图 32:Northwinds 类型化数据集的 XML 架构定义 (XSD) 文件(单击以查看全尺寸图像

编译或运行时(如果需要),此架构信息将在设计时转换为 C# 或 Visual Basic 代码,此时可以使用调试器逐步执行它。 若要查看此自动生成的代码,请转到类视图并向下钻取到 TableAdapter 或 Typed DataSet 类。 如果未在屏幕上看到“类视图”,请转到“视图”菜单,然后从那里选择它,或按 Ctrl+Shift+C。 在类视图中,可以看到类型化数据集和 TableAdapter 类的属性、方法和事件。 若要查看特定方法的代码,请双击类视图中的方法名称,或右键单击它,然后选择“转到定义”。

通过从类视图中选择“转到定义”来检查自动生成的代码

图 33:通过从类视图中选择“转到定义”来检查自动生成的代码

虽然自动生成的代码可以节省大量时间,但代码通常非常通用,需要自定义以满足应用程序的独特需求。 不过,扩展自动生成的代码的风险在于,生成代码的工具可能会决定是时候“重新生成”并覆盖自定义项了。 使用 .NET 2.0 的新部分类概念,可以轻松地将一个类拆分为多个文件。 这使我们可以将自己的方法、属性和事件添加到自动生成的类,而无需担心 Visual Studio 覆盖自定义项。

为了演示如何自定义 DAL,让我们向类添加一个 GetProducts() 方法 SuppliersRow 。 该 SuppliersRow 类表示表中的单个记录 Suppliers ;每个供应商都可以提供零到多个产品,因此 GetProducts() 将返回指定供应商的这些产品。 为此, App_Code 请在名为 SuppliersRow.vb 的文件夹中创建一个新的类文件,并添加以下代码:

Imports NorthwindTableAdapters
Partial Public Class Northwind
    Partial Public Class SuppliersRow
        Public Function GetProducts() As Northwind.ProductsDataTable
            Dim productsAdapter As New ProductsTableAdapter
            Return productsAdapter.GetProductsBySupplierID(Me.SupplierID)
        End Function
    End Class
End Class

此分部类指示编译器在构建 Northwind.SuppliersRow 类时包含我们刚刚定义的 GetProducts() 方法。 如果在生成项目后返回到类视图,现在可以看到 GetProducts() 被列为 Northwind.SuppliersRow 的一个方法。

GetProducts() 方法现在是 Northwind.SuppliersRow 类的一部分

图 34:方法 GetProducts() 现在是类的 Northwind.SuppliersRow 一部分

GetProducts()该方法现在可用于枚举特定供应商的产品集,如以下代码所示:

Dim suppliersAdapter As New NorthwindTableAdapters.SuppliersTableAdapter()
Dim suppliers As Northwind.SuppliersDataTable = suppliersAdapter.GetSuppliers()
For Each supplier As Northwind.SuppliersRow In suppliers
    Response.Write("Supplier: " & supplier.CompanyName)
    Response.Write("<ul>")
    Dim products As Northwind.ProductsDataTable = supplier.GetProducts()
    For Each product As Northwind.ProductsRow In products
        Response.Write("<li>" & product.ProductName & "</li>")
    Next
    Response.Write("</ul><p> </p>")
Next

此数据还可以显示在任何 ASP.NET 的数据 Web 控件中。 以下页面使用包含两个字段的 GridView 控件:

  • 一个 BoundField,用于显示每个供应商的名称,以及
  • 一个包含 BulletedList 控件的 TemplateField,该控件绑定到每个供应商的方法 GetProducts() 返回的结果。

我们将探讨如何在将来的教程中显示此类大纲详细信息报告。 目前,此示例旨在说明如何使用添加到类的 Northwind.SuppliersRow 自定义方法。

供应商与产品.aspx

<%@ Page Language="VB" CodeFile="SuppliersAndProducts.aspx.vb"
    AutoEventWireup="true" Inherits="SuppliersAndProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>
            Suppliers and Their Products</h1>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             AutoGenerateColumns="False"
             CssClass="DataWebControlStyle">
                <HeaderStyle CssClass="HeaderStyle" />
                <AlternatingRowStyle CssClass="AlternatingRowStyle" />
                <Columns>
                    <asp:BoundField DataField="CompanyName"
                      HeaderText="Supplier" />
                    <asp:TemplateField HeaderText="Products">
                        <ItemTemplate>
                            <asp:BulletedList ID="BulletedList1"
                             runat="server" DataSource="<%# CType(CType(Container.DataItem, System.Data.DataRowView).Row, Northwind.SuppliersRow).GetProducts() %>"
                                 DataTextField="ProductName">
                            </asp:BulletedList>
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

SuppliersAndProducts.aspx.vb

Imports NorthwindTableAdapters
Partial Class SuppliersAndProducts
    Inherits System.Web.UI.Page
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles Me.Load
        Dim suppliersAdapter As New SuppliersTableAdapter
        GridView1.DataSource = suppliersAdapter.GetSuppliers()
        GridView1.DataBind()
    End Sub
End Class

供应商的公司名称列在左侧列,其产品在右侧

图 35:供应商的公司名称在左侧列中列出,其产品在右侧(单击以查看全尺寸图像

总结

在构建 Web 应用程序时,创建 DAL 应是您在开始创建展示层之前采取的第一步之一。 使用 Visual Studio,基于类型化数据集创建 DAL 是一项任务,无需编写代码行即可在 10-15 分钟内完成。 接下来的教程将基于此 DAL。 在下一教程中,我们将定义一些业务规则,并了解如何在单独的业务逻辑层中实现它们。

快乐编程!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

涵盖本教程中主题的视频培训

关于作者

斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆自学系列:24小时学会 ASP.NET 2.0。 可以通过 mitchell@4GuysFromRolla.com 联系到他。

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是罗恩·格林、希尔顿·吉塞诺、丹尼斯·帕特森、莉兹·舒洛克、阿贝尔·戈麦斯和卡洛斯·桑托斯。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com