创建数据访问层 (C#)

作者 :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 数据库,则需要更新应用程序的 Web.config 文件中的 NORTHWNDConnectionString 设置。 Web 应用程序是使用 Visual Studio 2005 Professional Edition 作为基于文件系统的网站项目生成的。 但是,所有教程都同样适用于免费版 Visual Studio 2005 Visual Web Developer

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

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

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

System-Based 网站创建新文件

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

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

创建网站后,下一步是在 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 文件夹中的 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 数据库具有“产品”和“类别”表,用于记录待售产品及其所属类别。 在 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 类的类组成。 除了强类型的 DataTable,类型化数据集现在还包括 TableAdapters,这些类包含用于填充 DataSet 的 DataTable 以及将 DataTable 中的修改传播回数据库的方法。

注意

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

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

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

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

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

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

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

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

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

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

请记住,强类型 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 中的表中的列。 在向导结束时,我们将为此查询提供方法名称。 完成此操作后,可以从表示层调用此方法。 方法将执行定义的查询并填充强类型 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 或 DataRows 数组。 (如果已从图 9 中的高级属性中取消选中“生成插入、更新和删除语句”选项,则此复选框的设置将不起作用。) 让我们保留选中此复选框。

将方法名称从 GetData 更改为 GetProducts

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

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

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

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

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

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
    new NorthwindTableAdapters.ProductsTableAdapter();
Northwind.ProductsDataTable products;
products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow productRow in products)
    Response.Write("Product: " + productRow.ProductName + "<br />");

此代码不要求我们编写一位特定于数据访问的代码。 我们不必实例化任何 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="C#" AutoEventWireup="true" CodeFile="AllProducts.aspx.cs"
    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>
        <h2>
            All Products</h2>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

AllProducts.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class AllProducts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ProductsTableAdapter productsAdapter = new
         ProductsTableAdapter();
        GridView1.DataSource = productsAdapter.GetProducts();
        GridView1.DataBind();
    }
}

产品列表显示在 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 查询。 由于我们只想返回属于特定类别的产品,因此我使用 GetProducts () 中的同一 SELECT 语句,但添加以下 WHERE 子句:WHERE CategoryID = @CategoryID@CategoryID 参数向 TableAdapter 向导指示,我们创建的方法需要相应类型的输入参数 (即) 可为 null 的整数。

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

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

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

选择 TableAdapter 方法的名称

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

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

现在可以按类别查询产品

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

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

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

显示属于饮料类别的产品

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

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

Beverages.asp

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Beverages.aspx.cs"
    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>
        <h2>Beverages</h2>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

Beverages.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class Beverages : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ProductsTableAdapter productsAdapter = new
         ProductsTableAdapter();
        GridView1.DataSource =
          productsAdapter.GetProductsByCategoryID(1);
        GridView1.DataBind();
    }
}

显示饮料类别中的那些产品

图 20:饮料类别中的这些产品 (单击查看全尺寸图像)

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

有两种模式通常用于插入、更新和删除数据。 第一种模式(我将调用数据库直接模式)涉及创建方法,这些方法在调用时向在单一数据库记录上运行的数据库发出 INSERTUPDATEDELETE 命令。 此类方法通常传入一系列标量值, (整数、字符串、布尔值、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,然后转到属性窗口来检查和修改 InsertCommandUpdateCommandDeleteCommand 属性。 (确保已选择 TableAdapter,并且 ProductsTableAdapter 对象是在 属性窗口.)

TableAdapter 具有 InsertCommand、UpdateCommand 和 DeleteCommand 属性

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

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

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

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

下面的代码示例演示如何使用批量更新模式将未停产且库存数量不超过 25 个单位的所有产品的价格加倍:

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
  new NorthwindTableAdapters.ProductsTableAdapter();
// For each product, double its price if it is not discontinued and
// there are 25 items in stock or less
Northwind.ProductsDataTable products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow product in products)
   if (!product.Discontinued && product.UnitsInStock <= 25)
      product.UnitPrice *= 2;
// Update the products
productsAdapter.Update(products);

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

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
    new NorthwindTableAdapters.ProductsTableAdapter();
// Delete the product with ProductID 3
productsAdapter.Delete(3);
// Update Chai (ProductID of 1), setting the UnitsOnOrder to 15
productsAdapter.Update("Chai", 1, 1, "10 boxes x 20 bags",
  18.0m, 39, 15, 10, false, 1);
// Add a new product
productsAdapter.Insert("New Product", 1, 1,
  "12 tins per carton", 14.95m, 15, 0, 10, false);

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

由 DB 直接方法创建的 Insert () Update () Delete () 方法可能有点麻烦,尤其是对于包含多个列的表。 查看前面的代码示例,如果没有 IntelliSense 的帮助,它并不特别清楚 “产品 ”表列映射到 Update () Insert () 方法的每个输入参数。 有时,我们可能只想更新单列或两列,或者需要自定义 的 Insert () 方法,该方法可能会返回新插入记录的 IDENTITY 的值, (自动递增) 字段。

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

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

图 25:创建方法以将新行添加到 产品 表 (单击以查看全尺寸图像)

在下一个屏幕上, 将显示 InsertCommandCommandText 。 通过在查询末尾添加 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 属性更改为 标量 (单击以查看全尺寸图像)

以下代码演示了此新的 InsertProduct 方法的运行情况:

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
    new NorthwindTableAdapters.ProductsTableAdapter();
// Add a new product
int new_productID = Convert.ToInt32(productsAdapter.InsertProduct
    ("New Product", 1, 1, "12 tins per carton", 14.95m, 10, 0, 10, false));
// On second thought, delete the product
productsAdapter.Delete(new_productID);

步骤 5:完成数据访问层

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

但是,这会带来问题,因为 TableAdapter 用于插入、更新和删除数据的方法基于此初始方法。 幸运的是,用于插入、更新和删除的自动生成方法不受 SELECT 子查询中的子查询的影响。 通过谨慎地将查询作为子查询添加到 类别供应商 ,而不是 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:更新 GetProducts () 方法的 SELECT 语句 (单击以查看全尺寸图像)

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

产品数据表有两个新列

图 30产品 数据表包含两个新列

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

如果使用 JOIN 语法更新 GetProducts () SELECT,则 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,让我们将 GetProducts () 方法添加到 SuppliersRow 类。 SuppliersRow 类表示 Suppliers 表中的单个记录;每个供应商都可以提供零到多个产品,因此 GetProducts () 将返回指定供应商的产品。 若要完成此操作,请在名为SuppliersRow.cs 的 App_Code 文件夹中创建一个新的类文件,并添加以下代码:

using System;
using System.Data;
using NorthwindTableAdapters;
public partial class Northwind
{
    public partial class SuppliersRow
    {
        public Northwind.ProductsDataTable GetProducts()
        {
            ProductsTableAdapter productsAdapter =
             new ProductsTableAdapter();
            return
              productsAdapter.GetProductsBySupplierID(this.SupplierID);
        }
    }
}

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

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

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

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

NorthwindTableAdapters.SuppliersTableAdapter suppliersAdapter =
    new NorthwindTableAdapters.SuppliersTableAdapter();
// Get all of the suppliers
Northwind.SuppliersDataTable suppliers =
  suppliersAdapter.GetSuppliers();
// Enumerate the suppliers
foreach (Northwind.SuppliersRow supplier in suppliers)
{
    Response.Write("Supplier: " + supplier.CompanyName);
    Response.Write("<ul>");
    // List the products for this supplier
    Northwind.ProductsDataTable products = supplier.GetProducts();
    foreach (Northwind.ProductsRow product in products)
        Response.Write("<li>" + product.ProductName + "</li>");
    Response.Write("</ul><p> </p>");
}

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

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

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

SuppliersAndProducts.aspx

<%@ Page Language="C#" CodeFile="SuppliersAndProducts.aspx.cs"
    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>
        <h2>
            Suppliers and Their Products</h2>
        <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="<%# ((Northwind.SuppliersRow) ((System.Data.DataRowView) Container.DataItem).Row).GetProducts() %>"
                                 DataTextField="ProductName">
                            </asp:BulletedList>
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

SuppliersAndProducts.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class SuppliersAndProducts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        SuppliersTableAdapter suppliersAdapter = new
          SuppliersTableAdapter();
        GridView1.DataSource = suppliersAdapter.GetSuppliers();
        GridView1.DataBind();
    }
}

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

图 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放置一行。