创建数据访问层 (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。

创建新的基于文件系统的网站

图 1:创建新的基于文件系统的网站(单击以查看全尺寸图像

这将创建一个包含 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 数据库服务器,或者只想避免将数据库添加到数据库服务器,则可以使用位于下载网站文件夹(NORTHWND.MDF)中的 App_Data 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 不包含有关如何从其基础数据库表访问数据的任何信息。 为了检索数据以填充 DataTable,我们使用 TableAdapter 类,该类充当数据访问层。 Products对于 DataTable,TableAdapter 将包含方法GetProducts()GetProductByCategoryID(categoryID),依此,我们将从呈现层调用。 DataTable 的角色是充当用于在层之间传递数据的强类型对象。

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

从下拉列表中选择 Northwind 数据库

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

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

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

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

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

产品 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 添加新方法,请返回到数据集设计器,右键单击该 ProductsTableAdapter 部分,然后选择“添加查询”。

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

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

我们首先会提示我们是要使用即席 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:插入、更新和删除数据

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

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

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

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

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

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

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

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

TableAdapter 具有 InsertCommand、UpdateCommand 和 DeleteCommand 属性

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

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

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

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

下面的代码示例演示如何使用批处理更新模式将未停产且库存不足 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 表列映射到每个输入参数到 Update()Insert() 方法。 有时,我们只想更新一列或两列,或者希望自定义 Insert() 方法可能返回新插入记录的 IDENTITY (自动递增)字段的值。

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

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

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

在下一个屏幕上, InsertCommand将显示“s CommandText ”。 通过在查询末尾添加 SELECT SCOPE_IDENTITY() 来扩充此查询,该查询将返回插入到同一范围内的列的最后一个 IDENTITY 标识值。 (请参阅技术文档,了解有关你可能想要使用SCOPE_IDENTITY()而不是@@IDENTITY的详细信息SCOPE_IDENTITY()。 在添加SELECT语句之前,请确保使用分号结束INSERT该语句。

扩充查询以返回 SCOPE_IDENTITY() 值

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

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

将新方法名称设置为 InsertProduct

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

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

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

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

但是,这可能导致问题,因为 TableAdapter 插入、更新和删除数据的方法基于此初始方法。 幸运的是,用于插入、更新和删除的自动生成的方法不受子句中的 SELECT 子查询的影响。 通过小心地将查询添加到 Categories 子查询和 Suppliers 子查询中, JOIN 我们将避免重新处理这些方法来修改数据。 右键单击方法 GetProducts()ProductsTableAdapter 然后选择“配置”。 然后,调整 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

Products DataTable 有两个新列

图 30Products DataTable 有两个新列

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

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

添加剩余的 TableAdapters

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

花几分钟时间使用以下查询创建以下 TableAdapters 和方法。 请注意,包含子查询中的查询 ProductsTableAdapter ,用于获取每个产品的类别和供应商名称。 此外,如果一直在关注,则已添加 ProductsTableAdapterGetProducts()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
      

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

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

将自定义代码添加到 DAL

添加到类型化数据集的 TableAdapters 和 DataTable 表示为 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,显示每个供应商的名称,以及
  • 一个 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:供应商的公司名称在左侧列中列出,其产品在右侧(单击以查看全尺寸图像

总结

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

快乐编程!

深入阅读

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

本教程中包含的主题视频培训

关于作者

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

特别感谢

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