创建数据访问层 (VB)

作者 :Scott Mitchell

下载 PDF

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

简介

作为 Web 开发人员,我们的生活围绕着处理数据。 我们创建用于存储数据的数据库、用于检索和修改数据的代码,以及用于收集和汇总数据的网页。 这是长篇系列教程中的第一个教程,该教程将探讨在 ASP.NET 2.0 中实现这些常见模式的技术。 我们将首先使用类型化数据集创建由数据访问层 (DAL) 组成的 软件体系结构 、强制实施自定义业务规则的业务逻辑层 (BLL) ,以及由共享公共页面布局的 ASP.NET 页组成的表示层。 完成此后端基础后,我们将进入报告,展示如何显示、汇总、收集和验证来自 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 Developer)的免费版本相同。

在本教程中,我们将从头开始创建数据访问层 (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 数据库添加到服务器资源管理器的步骤取决于是要使用 文件夹中的 SQL Server 2005 Express Edition 数据库App_Data,还是要改用 Microsoft SQL Server 2000 或 2005 数据库服务器设置。

在文件夹中使用数据库App_Data

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

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

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

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

步骤 2:创建数据访问层

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

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

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

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

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

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

注意

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

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

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

图 3:所有数据访问代码都归为 DAL (单击以查看全尺寸图像)

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

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

选择“向项目添加新数据集”

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

单击“添加”后,当系统提示将数据集添加到文件夹时 App_Code ,选择“是”。 随后将显示类型化数据集的Designer,并启动 TableAdapter 配置向导,以便将第一个 TableAdapter 添加到类型化数据集。

类型化数据集充当强类型数据集合;它由强类型 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”将创建 Insert()TableAdapter 的 、 Update()Delete() 方法。 如果未选中此选项,则所有更新都需要通过 TableAdapter 的唯 Update() 一方法完成,该方法采用类型化数据集、DataTable、单个 DataRow 或 DataRow 数组。 (如果已从图 9 中的高级属性中取消选中“生成插入、更新和删除语句”选项,则此复选框的设置将不起作用。) 让我们保持选中此复选框。

将方法名称从 GetData 更改为 GetProducts

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

单击“完成”完成向导。 向导关闭后,我们将返回到 DataSet Designer其中显示了刚刚创建的 DataTable。 可以查看 DataTable (、 等) 列的列表Products,以及 (Fill()GetProducts()) 的方法ProductsTableAdapterProductNameProductID

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

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

此时,我们有一个具有单个 DataTable () Northwind.Products 的类型化数据集和一个强类型的 DataAdapter 类, (NorthwindTableAdapters.ProductsTableAdapter 使用 GetProducts() 方法) 。 这些对象可用于从以下代码访问所有产品的列表:

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 和其他几个控件。 下面的示例演示了将 方法返回的 GetProducts() DataTable 绑定到 GridView,只需在事件处理程序中的三行代码中 Page_Load

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 添加新方法,请返回到 DataSet Designer,右键单击 ProductsTableAdapter 部分,然后选择“添加查询”。

右键单击 TableAdapter 并选择“添加查询”

图 14:在 TableAdapter 上 Right-Click 并选择“添加查询”

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

选择创建返回行的 SELECT 语句

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

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

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

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

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

选择 TableAdapter 方法的名称

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

完成向导后,DataSet Designer包含新的 TableAdapter 方法。

现在可以按类别查询产品

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

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

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

显示属于饮料类别的产品

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

GetProductsByCategoryID(categoryID)使用 DAL 中的 方法,我们现在可以创建一个 ASP.NET 页面,该页面仅显示指定类别中的这些产品。 以下示例显示“饮料”类别中的所有产品,其 a 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:插入、更新和删除数据

有两种模式通常用于插入、更新和删除数据。 第一种模式(我将调用数据库直接模式)涉及创建方法,这些方法在调用时向在单一 INSERT数据库记录上运行的数据库发出 、 UPDATEDELETE 命令。 此类方法通常传入一系列标量值, (整数、字符串、布尔值、DateTimes 等) ,这些值对应于要插入、更新或删除的值。 例如,对于表的此模式 Products ,delete 方法将采用整数参数,指示 ProductID 要删除的记录的 ,而 insert 方法将采用 字符串 ProductName,小数点表示 UnitPrice,整数为 UnitsOnStock,等等。

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

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

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

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

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

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

这两种数据修改模式都使用 TableAdapter 的 InsertCommandUpdateCommandDeleteCommand 属性向数据库发出其 INSERTUPDATEDELETE 命令。 可以通过单击 DataSet Designer中的 TableAdapter,然后转到属性窗口来检查和修改 InsertCommandUpdateCommand、 和 DeleteCommand 属性。 (请确保已选择 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)

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

Insert()DB 直接方法创建的 、 Update()Delete() 方法可能有点麻烦,尤其是对于包含许多列的表。 查看前面的代码示例,如果没有 IntelliSense 的帮助,则不太清楚哪些Products表列映射到 和 Insert() 方法的每个输入参数Update()。 有时,我们可能只想更新单列或两列,或者需要一个自定义 Insert() 方法,以便返回新插入记录的 IDENTITY (自动递增) 字段的值。

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

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

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

在下一个屏幕上,InsertCommand将显示 。CommandText 通过在查询末尾添加 SELECT SCOPE_IDENTITY() 来扩充此查询,这将返回插入到同一范围内的列中的最后一个 IDENTITY 标识值。 (请参阅 技术文档 ,详细了解 SCOPE_IDENTITY() 和为何要使用 SCOPE_IDENTITY () 代替 @@IDENTITY.) 在添加 SELECT 语句之前,请确保用分号结束INSERT语句。

扩充查询以返回SCOPE_IDENTITY () 值

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

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

将“新方法名称”设置为 InsertProduct

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

返回到 DataSet Designer将看到 ProductsTableAdapter 包含新方法 InsertProduct。 如果此新方法没有表中每一列 Products 的参数,则可能忘记用分号终止 INSERT 语句。 配置 方法并确保 InsertProduct 有一个分号分隔 INSERTSELECT 语句。

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

将 ExecuteMode 属性更改为标量

图 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 值,但不包括表中的CategoryNameCategories或表中的CompanyNameSuppliers列,尽管这些列可能是我们想要在显示产品信息时显示的列。 我们可以扩充 TableAdapter 的初始方法 GetProducts(),以同时 CategoryName 包含 和 CompanyName 列值,这将更新强类型的 DataTable 以包含这些新列。

但是,这会带来问题,因为 TableAdapter 用于插入、更新和删除数据的方法基于此初始方法。 幸运的是,用于插入、更新和删除的自动生成方法不受 子查询中的 SELECT 子查询的影响。 通过谨慎地将 Categories 查询添加到 和 Suppliers 作为子查询, 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 DataTable 有两个新列

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

如果使用语法更新 GetProducts()SELECTJOIN ,DataSet Designer将无法自动生成使用 DB 直接模式插入、更新和删除数据库数据的方法。 相反,你必须手动创建它们,就像本教程前面对 方法所做的那样 InsertProduct 。 此外,如果要使用批量更新模式,则必须手动提供 InsertCommandUpdateCommandDeleteCommand 属性值。

添加剩余的 TableAdapters

到目前为止,我们只关注使用单个 TableAdapter 处理单个数据库表。 但是,Northwind 数据库包含多个相关表,我们需要在 Web 应用程序中使用这些表。 类型化数据集可以包含多个相关的 DataTable。 因此,若要完成 DAL,我们需要为将在这些教程中使用的其他表添加 DataTable。 若要向类型化数据集添加新的 TableAdapter,请打开 DataSet Designer,右键单击Designer,然后选择“添加/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
      
  • CategoriesTableAdapter

    • 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
      
  • EmployeesTableAdapter

    • 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
      

添加四个 TableAdapter 之后的数据集Designer

图 31:添加四个 TableAdapters 之后的数据集Designer (单击以查看全尺寸图像)

将自定义代码添加到 DAL

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

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

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

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

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

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

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

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

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,以及
  • 一个 TemplateField,其中包含一个 BulletedList 控件,该控件绑定到方法为每个供应商返回 GetProducts() 的结果。

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

SuppliersAndProducts.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 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Ron Green、Hilton Giesenow、Dennis Patterson、Liz Shulok、Abel Gomez 和 Carlos Santos。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。