创建数据访问层 (C#)
作者 :斯科特·米切尔
在本教程中,我们将从头开始,使用类型化数据集创建数据访问层(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 数据库,则需要更新应用程序的 Web.config 文件中的 NORTHWNDConnectionString 设置。 Web 应用程序是使用 Visual Studio 2005 Professional Edition 作为基于文件系统的网站项目生成的。 但是,所有教程都同样适用于 Visual Studio 2005 免费版 Visual Web 开发人员。
在本教程中,我们将从头开始创建数据访问层(DAL),然后在第二个教程中创建业务逻辑层(BLL),并在第三个教程中处理页面布局和导航。 第三个之后的教程将建立在前三个基础的基础上。 在第一个教程中,我们有很多内容要介绍,因此请启动 Visual Studio,让我们开始吧!
步骤 1:创建 Web 项目并连接到数据库
在创建数据访问层(DAL)之前,首先需要创建网站并设置数据库。 首先,创建新的基于文件系统的 ASP.NET 网站。 为此,请转到“文件”菜单并选择“新建网站”,显示“新建网站”对话框。 选择 ASP.NET 网站模板,将“位置”下拉列表设置为文件系统,选择要放置网站的文件夹,并将语言设置为 C# 。
图 1:创建新的基于文件系统的网站(单击以查看全尺寸图像)
这将创建一个新网站,其中包含 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 文件夹(NORTHWND)中的 Northwind 数据库的 SQL Server 2005 Express Edition 版本。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 中的服务器资源管理器,右键单击“数据连接”节点,然后选择“添加连接”。 如果未看到服务器资源管理器转到视图/服务器资源管理器,或按 Ctrl+Alt+S。 此时会显示“添加连接”对话框,可在其中指定要连接的服务器、身份验证信息和数据库名称。 成功配置数据库连接信息并单击“确定”按钮后,数据库将添加为数据连接节点下面的节点。 可以展开数据库节点以浏览其表、视图、存储过程等。
图 2:向数据库服务器的 Northwind 数据库添加连接
步骤 2:创建数据访问层
使用数据时,一个选项是将特定于数据的逻辑直接嵌入到呈现层(在 Web 应用程序中,ASP.NET 页面构成呈现层)。 这可能采用在 ASP.NET 页的代码部分或使用标记部分中的 SqlDataSource 控件编写 ADO.NET 代码的形式。 无论哪种情况,此方法都紧密耦合数据访问逻辑与呈现层。 但是,建议的方法是将数据访问逻辑与表示层分开。 此单独的层称为“数据访问层”,简称“DAL”,通常作为单独的类库项目实现。 此分层体系结构的优点已得到很好的记录(请参阅本教程末尾的“进一步阅读”部分,了解这些优势),是我们将在本系列中采用的方法。
特定于基础数据源的所有代码(如创建与数据库的连接、发出 SELECT、 INSERT、 UPDATE 和 DELETE 命令等)都应位于 DAL 中。 表示层不应包含对此类数据访问代码的任何引用,而是应针对任何和所有数据请求调用 DAL。 数据访问层通常包含用于访问基础数据库数据的方法。 例如,Northwind 数据库具有 “产品 ”和 “类别” 表,用于记录要销售的产品及其所属类别。 在我们的 DAL 中,我们将有如下方法:
- GetCategories(), 它将返回有关所有类别的信息
- GetProducts(),它将返回有关所有产品的信息
- GetProductsByCategoryID(categoryID),它将返回属于指定类别的所有产品
- GetProductByProductID(productID),它将返回有关特定产品的信息
调用这些方法时,将连接到数据库、发出适当的查询并返回结果。 如何返回这些结果非常重要。 这些方法可能只是返回由数据库查询填充的数据集或 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 说明了使用类型化数据集的应用程序的不同层之间的工作流。
图 3:所有数据访问代码将降级到 DAL(单击以查看全尺寸图像)
创建类型化数据集和表适配器
若要开始创建 DAL,首先将类型化数据集添加到项目中。 为此,请右键单击解决方案资源管理器中的项目节点,然后选择“添加新项”。 从模板列表中选择“数据集”选项并将其命名为 Northwind.xsd。
图 4:选择向项目添加新数据集(单击以查看全尺寸图像)
单击“添加”后,当系统提示将数据集添加到 App_Code 文件夹中时,请选择“是”。 然后将显示类型化数据集的设计器,TableAdapter 配置向导将启动,使你能够将第一个 TableAdapter 添加到 Typed DataSet。
类型化数据集充当强类型数据集合;它由强类型 DataTable 实例组成,每个实例又由强类型 DataRow 实例组成。 我们将为每个基础数据库表创建强类型 DataTable,我们需要在本教程系列中处理这些表。 首先,创建 DataTable for the Products 表。
请记住,强类型 DataTable 不包含有关如何从其基础数据库表访问数据的任何信息。 为了检索数据以填充 DataTable,我们使用 TableAdapter 类,该类充当数据访问层。 对于产品 DataTable,TableAdapter 将包含 GetProducts()、GetProductByCategoryID(categoryID)等方法,我们将从呈现层调用。 DataTable 的角色是充当用于在层之间传递数据的强类型对象。
TableAdapter 配置向导首先提示选择要使用的数据库。 下拉列表显示服务器资源管理器中的这些数据库。 如果未将 Northwind 数据库添加到服务器资源管理器,可以单击此时的“新建连接”按钮执行此操作。
图 5:从下拉列表中选择 Northwind 数据库(单击以查看全尺寸图像)
选择数据库并单击“下一步”后,系统会询问是否要将连接字符串保存到 Web.config 文件中。 通过保存连接字符串,可以避免在 TableAdapter 类中对其进行硬编码,如果将来连接字符串信息发生更改,这会简化操作。 如果选择在配置文件中保存连接字符串,则会将其放置在 <connectionStrings> 节中,该节可以选择性地加密,以改进安全性,或者稍后通过 IIS GUI 管理工具中的新 ASP.NET 2.0 属性页进行修改,这对于管理员来说更理想。
图 6:将连接字符串 保存到 Web.config (单击可查看全尺寸图像)
接下来,我们需要为第一个强类型 DataTable 定义架构,并为在填充强类型数据集时要使用的 TableAdapter 提供第一种方法。 这两个步骤是通过创建一个查询来同时完成的,该查询返回要反映在 DataTable 中的表中的列。 在向导的末尾,我们将为此查询提供方法名称。 完成此操作后,可以从呈现层调用此方法。 该方法将执行定义的查询并填充强类型 DataTable。
若要开始定义 SQL 查询,必须先指示希望 TableAdapter 如何发出查询。 可以使用即席 SQL 语句、创建新的存储过程或使用现有存储过程。 对于这些教程,我们将使用即席 SQL 语句。
图 7:使用即席 SQL 语句查询数据(单击以查看全尺寸图像)
此时,我们可以手动键入 SQL 查询。 在 TableAdapter 中创建第一个方法时,通常需要让查询返回需要在相应的 DataTable 中表示的列。 我们可以通过创建一个查询来返回 Products 表中的所有列和所有行来实现此目的:
图 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 中高级属性中的“生成插入、更新和删除语句”选项,此复选框的设置将不起作用。让我们选中此复选框。
图 11:将方法名称从 GetData 更改为 GetProducts (单击以查看全尺寸图像)
单击“完成”完成向导。 向导关闭后,我们将返回到数据集设计器,其中显示了我们刚刚创建的 DataTable。 可以查看 Products DataTable(ProductID、ProductName 等)中的列列表,以及 ProductsTableAdapter(Fill() 和 GetProducts()的方法。
图 12:产品 DataTable 和 ProductsTableAdapter 已添加到类型化数据集(单击以查看全尺寸图像)
此时,我们有一个类型化数据集,其中包含单个 DataTable(Northwind.Products)和具有 GetProducts() 方法的强类型 DataAdapter 类(NorthwindTableAdapters.ProductsTableAdapter)。 这些对象可用于从代码访问所有产品的列表,例如:
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();
}
}
图 13:产品列表显示在 GridView 中(单击以查看全尺寸图像)
虽然此示例要求我们在 ASP.NET 页的 Page_Load 事件处理程序中编写三行代码,但在将来的教程中,我们将介绍如何使用 ObjectDataSource 以声明方式从 DAL 检索数据。 使用 ObjectDataSource,我们不必编写任何代码,也会收到分页和排序支持!
步骤 3:向数据访问层添加参数化方法
此时,ProductsTableAdapter 类有一种方法 GetProducts(),该方法返回数据库中的所有产品。 虽然能够处理所有产品绝对有用,但有时我们需要检索有关特定产品或属于特定类别的所有产品的信息。 若要向数据访问层添加此类功能,可将参数化方法添加到 TableAdapter。
让我们添加 GetProductsByCategoryID(categoryID) 方法。 若要向 DAL 添加新方法,请返回到数据集设计器,右键单击 ProductsTableAdapter 部分,然后选择“添加查询”。
图 14:右键单击 TableAdapter 并选择“添加查询”
我们首先会提示我们是要使用即席 SQL 语句还是新的或现有的存储过程来访问数据库。 让我们选择再次使用即席 SQL 语句。 接下来,系统会询问要使用的 SQL 查询类型。 由于我们想要返回属于指定类别的所有产品,因此需要编写返回 行的 SELECT 语句。
图 15:选择创建返回 行的 SELECT 语句(单击以查看全尺寸图像)
下一步是定义用于访问数据的 SQL 查询。 由于我们希望仅返回属于特定类别的产品,因此我使用 GetProducts()中的同一 SELECT 语句,但添加以下 WHERE WHERE 子句:WHERE CategoryID = @CategoryID。 @CategoryID参数向 TableAdapter 向导指示,我们创建的方法需要相应类型的输入参数(即可为 null 的整数)。
图 16:输入查询以仅返回指定类别中的产品(单击以查看全尺寸图像)
在最后一步中,我们可以选择要使用的数据访问模式,并自定义生成的方法的名称。 对于填充模式,让我们将名称更改为 FillByCategoryID,对于返回 DataTable 返回模式(GetX 方法),让我们使用 GetProductsByCategoryID。
图 17:选择 TableAdapter 方法的名称(单击以查看全尺寸图像)
完成向导后,数据集设计器包括新的 TableAdapter 方法。
图 18:产品现在可以按类别查询
花点时间使用相同的技术添加 GetProductByProductID(productID) 方法。
可以直接从数据集设计器测试这些参数化查询。 右键单击 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:插入、更新和删除数据
有两种模式通常用于插入、更新和删除数据。 第一个模式,我将调用数据库直接模式,涉及创建方法,在调用时,向对单一数据库记录操作的数据库发出 INSERT、 UPDATE 或 DELETE 命令。 此类方法通常传入一系列标量值(整数、字符串、布尔值、DateTimes 等),这些值对应于要插入、更新或删除的值。 例如,对于 Products 表,delete 方法采用整数参数,指示要删除的记录的 ProductID,而 insert 方法将采用 ProductName 的字符串、UnitPrice 的十进制数、UnitsOnStock 的整数等。
图 21:每次插入、更新和删除请求都会立即发送到数据库(单击以查看全尺寸图像)
另一种模式(我将称为批处理更新模式)是在一个方法调用中更新 DataRows 的整个数据集、DataTable 或集合。 使用此模式,开发人员删除、插入和修改 DataTable 中的 DataRows,然后将这些 DataRows 或 DataTable 传递到更新方法。 然后,此方法枚举传入的 DataRows,确定它们是否已修改、添加或删除(通过 DataRow 的 RowState 属性值 ),并为每个记录发出相应的数据库请求。
图 22:调用 Update 方法时,所有更改都与数据库同步(单击以查看全尺寸图像)
TableAdapter 默认使用批处理更新模式,但也支持 DB 直接模式。 由于我们在创建 TableAdapter 时从高级属性中选择了“生成插入、更新和删除语句”选项, ProductsTableAdapter 包含一个 Update() 方法,该方法可实现批处理更新模式。 具体而言,TableAdapter 包含一个 Update() 方法,该方法可以传递类型化数据集、强类型 DataTable 或一个或多个 DataRows。 如果在首次创建 TableAdapter 时选中了“GenerateDBDirectMethods”复选框,则 DB 直接模式也将通过 Insert()、 Update()和 Delete() 方法实现。
这两种数据修改模式都使用 TableAdapter 的 InsertCommand、UpdateCommand 和 DeleteCommand 属性向数据库发出 INSERT、UPDATE 和 DELETE 命令。 可以通过单击数据集设计器中的 TableAdapter,然后转到属性窗口来检查和修改 InsertCommand、UpdateCommand 和 DeleteCommand 属性。 (请确保已选择 TableAdapter,并且ProductsTableAdapter 对象是在 属性窗口 下拉列表中选择的对象。
图 23:TableAdapter 具有 InsertCommand、UpdateCommand 和 DeleteCommand 属性(单击可查看全尺寸图像)
若要检查或修改这些数据库命令属性中的任何一个,请单击 CommandText 子属性,这将打开查询生成器。
图 24:在查询生成器中配置 INSERT、 UPDATE 和 DELETE 语句(单击以查看全尺寸图像)
下面的代码示例演示如何使用批处理更新模式将未停产且库存不足 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 的帮助,它并不特别清楚 哪些 Products 表列映射到每个输入参数到 Update() 和 Insert() 方法。 有时,我们只想更新一列或两列,或者想要自定义 的 Insert() 方法,也许返回新插入记录的 IDENTITY (自动递增)字段的值。
若要创建此类自定义方法,请返回到数据集设计器。 右键单击 TableAdapter 并选择“添加查询”,返回到 TableAdapter 向导。 第二个屏幕上,我们可以指示要创建的查询类型。 让我们创建一个添加新产品的方法,然后返回新添加记录的 ProductID 的值。 因此,选择创建 INSERT 查询。
图 25:创建向产品表添加新行的方法(单击以查看全尺寸图像)
在下一个屏幕上, 将显示 InsertCommand 的 CommandText 。 通过在查询末尾添加 SELECT SCOPE_IDENTITY()来增强此查询,该查询将返回在同一范围内插入到 IDENTITY 列中的最后一个标识值。 (请参阅 技术文档 ,了解有关 SCOPE_IDENTITY() 的详细信息,以及你可能想要 使用SCOPE_IDENTITY()而不是@@IDENTITY。 在添加 SELECT 语句之前,请确保使用分号结束 INSERT 语句。
图 26:扩充查询以返回 SCOPE_IDENTITY() 值(单击以查看全尺寸图像)
最后,将新方法 命名为 InsertProduct。
图 27:将新方法名称设置为 InsertProduct (单击可查看全尺寸图像)
返回到数据集设计器时,你将看到 ProductsTableAdapter 包含新方法 InsertProduct。 如果此新方法没有 Products 表中每一列的参数,则你很可能忘记用分号终止 INSERT 语句。 配置 InsertProduct 方法,并确保具有分隔 INSERT 和 SELECT 语句的分号。
默认情况下,插入方法发出非查询方法,这意味着它们返回受影响的行数。 但是,我们希望 InsertProduct 方法返回查询返回的值,而不是受影响的行数。 为此,请将 InsertProduct 方法的 ExecuteMode 属性调整为 Scalar。
图 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 表中返回 CategoryID 和 SupplierID 值,但不包括 Categories 表中的 CategoryName 列或供应商表中的 CompanyName 列,尽管这些列可能是显示产品信息时要显示的列。 我们可以扩充 TableAdapter 的初始方法 GetProducts()以包括 CategoryName 和 CompanyName 列值,这将更新强类型 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
图 29:更新 GetProducts 的 SELECT 语句 () 方法(单击以查看全尺寸图像)
更新 GetProducts() 方法以使用此新查询后,DataTable 将包含两个新列: CategoryName 和 SupplierName。
图 30: 产品 数据表有两个新列
花点时间更新 GetProductsByCategoryID(categoryID) 方法中的 SELECT 子句。
如果使用 JOIN 语法更新 GetProducts() SELECT,则数据集设计器将无法使用 DB 直接模式自动生成插入、更新和删除数据库数据的方法。 相反,必须像本教程前面的 InsertProduct 方法一样手动创建它们。 此外,如果要使用批处理更新模式,则必须手动提供 InsertCommand、UpdateCommand 和 DeleteCommand 属性值。
添加剩余的 TableAdapters
到目前为止,我们只查看了对单个数据库表使用单个 TableAdapter。 但是,Northwind 数据库包含几个相关表,我们需要在 Web 应用程序中使用这些表。 类型化数据集可以包含多个相关的 DataTable。 因此,若要完成 DAL,我们需要为将在这些教程中使用的其他表添加 DataTable。 若要向类型化数据集添加新的 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
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
图 31:添加四个 TableAdapters 后的数据集设计器(单击以查看全尺寸图像)
将自定义代码添加到 DAL
添加到类型化数据集的 TableAdapters 和 DataTable 表示为 XML 架构定义文件(Northwind.xsd)。 可以通过右键单击解决方案资源管理器中的 Northwind.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 类表示“供应商”表中的单个记录;每个供应商都可以提供零到多个产品,因此 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 的方法。
图 34:GetProducts() 方法现在是 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:供应商的公司名称在左侧列中列出,其产品在右侧(单击以查看全尺寸图像)
总结
在生成创建 DAL 的 Web 应用程序时,应是开始创建呈现层之前发生的第一个步骤之一。 使用 Visual Studio,基于类型化数据集创建 DAL 是一项任务,无需编写代码行即可在 10-15 分钟内完成。 前进的教程将基于此 DAL。 在下 一教程 中,我们将定义一些业务规则,并了解如何在单独的业务逻辑层中实现它们。
快乐编程!
深入阅读
有关本教程中讨论的主题的详细信息,请参阅以下资源:
- 在 VS 2005 中使用强类型表和 DataTable 构建 DAL,ASP.NET 2.0
- 设计数据层组件并通过层传递数据
- 加密 ASP.NET 2.0 应用程序中的配置信息
- TableAdapter 概述
- 使用类型化数据集
- 在 Visual Studio 2005 和 ASP.NET 2.0 中使用强类型数据访问
- 如何扩展 TableAdapter 方法
本教程中包含的主题视频培训
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0。 他可以通过他的博客联系到mitchell@4GuysFromRolla.com他,可以在该博客中找到http://ScottOnWriting.NET。
特别感谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是罗恩·格林、希尔顿·吉塞诺、丹尼斯·帕特森、莉兹·舒洛克、阿贝尔·戈麦斯和卡洛斯·桑托斯。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请把我扔一条线。mitchell@4GuysFromRolla.com