|
第四步:插入,更新和删除数据常用的插入,更新和删除数据的模式有两种。第一种模式,我称之为DB直接模式,涉及的方法被调用时,会向数据库里发出一个INSERT, 或UPDATE,或DELETE命令,这个命令只对单个数据库记录做操作。象这样的方法一般接受一系列对应于插入,更新或删除的值的标量参数(譬如整数,字符串,布尔值,日期时间等)。譬如,用这个模式来操作Products表的话,删除方法会接受一个整数参数,代表所需要删除的记录的ProductID,而插入方法则会接受一个对应于ProductName的字符串,对应 于UnitPrice的decimal值,对应于UnitsOnStock的整数等等。 
图 21: 每个插入,更新,和删除请求都被立刻发送到数据库 另外一个模式,我称之为批更新模式,可以在一个方法调用里更新整个DataSet,或者整个DataTable,或 者一个DataRow集合。在这个模式里,开发人员在一个DataTable中删除,插入,修改DataRow,然后把这 些DataRow或整个DataTable传给一个更新方法。然后这个方法会轮循传入的DataRow们,通过DataRow的RowState属 性属性来决定这些DataRow是否被改动过,或是新记录,或是被删除的记录,然后为每个记录发出合适的 数据库命令。 
图 22: 在Update 方法调用之后,所有的变动都与数据库同步了 在默认情形下,TableAdapter采用批更新模式,但也支持DB直接模式。因为我们在创建我们的TableAdapter时的高级选项中选择了“生成插入,更新,和删除语句” 这个选项,ProductsTableAdapter 包含了一个 Update()方法,该方法实现了批 更新模式。具体地说,TableAdapter包含了一个Update() 方法,可以传入一个强类型 的DataSet,或者一个强类型的DataTable,或者一个和多个DataRow。假如你在一开始创建TableAdapter时的选项中没有清除“生成DB直接方法(GenerateDBDirectMethods)”复选框的话,DB直接模 式也会通过Insert(),Update()和Delete()方法来实现。 这两种数据修改模式都使用 了TableAdapter的InsertCommand,UpdateCommand, 和DeleteCommand属性来向数据库发出对应 的INSERT,UPDATE和DELETE命令。你可以在DataSet设计器里点击TableAdapter,然后在属性窗口查看和改 动InsertCommand,UpdateCommand, 和DeleteCommand属性。(确 认你选择了TableAdapter,并且ProductsTableAdapter对象是属性窗口中下拉框里被选中的项) 
图23: TableAdapter包含InsertCommand,UpdateCommand, 和DeleteCommand等属性 想查看或改动这些数据库命令的属性的话,点击CommandText子属性,这会启动对应的查询 生成器。 
图 24: 在查询生成器里配置插入,更新,删除语句 下面的编码例子示范了如何使用批更新模式来把没被终止的,且库存等于或少于25个单元的产品的价格加 倍: | C# | 1
2
3
4
5
6
7
8
9
10
11
12
| 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直接模式删除一个产品,更新一个产品,然后添加一个新的产品: | C# | 1
2
3
4
5
6
7
8
9
10
11
12
| 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(自增)的字段值。 要创建这样的自定义方法,回到DataSet设计器。在TableAdapter上按右鼠标,选择“添加查询”,然后回 到TableAdapter配置向导。在第二屏上,我们可以指明要生成的查询的类型。让我们生成一个添加新 的product(产品)记录,然后返回新添加记录的ProductID值的方法。因此,选择生成一个插 入(INSERT)型查询。 
图25: 创建一个给Products表添加新记录的方法 下一个屏显示InsertCommand的CommandText属性。在查询语句后面,增添一 个SELECT SCOPE_IDENTITY()的查询,这查询将返回当前同一个操作范围内插 入IDENTITY字段的最后那个identity 值。(详见技术文档中关 于SCOPE_IDENTITY()的内容以及为什么你应该http://weblogs.sqlteam.com/travisl/archive/2003/10/29/405.aspx)。确认在添加SELECT语句前,你在INSERT语句后面添一个分号 。 
图26: 增添查询返回SCOPE_IDENTITY()值 最后,把这个新方法命名为InsertProduct。 
图 27:放方法名字设成InsertProduct 当你返回DataSet设计器时,你将看到ProductsTableAdapter多了一个新的方 法,InsertProduct。如果对应Products表的每个字段,这个新的方法没有对应的参数的话,非常可能的原因是,你忘了给INSERT语句的结尾添加一个分号(semi-colon)。重新配 置InsertProduct方法,确认在INSERT和SELECT语句间有个分号。 在默认情形下,插入方法调用的是非查询(non-query)方法,意即,他们只返回受影响的记录数。但是,我们想要让InsertProduct方法返回一个查询返回的值,而不是受影响的记录数。这可以把InsertProduct方法的ExecuteMode属性改 成Scalar(标量)来实现。 
图 28:把ExecuteMode属性改成Scalar 下面的编码示范如何使用这个新的InsertProduct方法: | C# | 1
2
3
4
5
6
7
| 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);
|
第五步:完成数据访问层注意,ProductsTableAdapters类从Products表中返回的 是CategoryID和SupplierID的值,但并不包括Categories表 的CategoryName字段和Suppliers表的CompanyName字段,尽管当 我们显示产品信息时,这些很可能是我们想要显示的字段。我们可以扩充TableAdapter的起始方 法GetProducts()来包含CategoryName和CompanyName字段的值, 这方法进而会更新强类型的DataTable来包括这些新的字段。 但这会造成一个问题,因为TableAdapter的插入,更新,删除数据的方法是基于这个起始方法的,幸运的是, 自动生成的插入,更新,删除方法并不会受SELECT子句中的子查询的影响。如果我们注意把 对Categories和Suppliers的查询添加成子查询,而不是用JOIN语 句的话,我们可以避免重做这些修改数据的方法。在ProductsTableAdapter中的GetProducts()方法上按右鼠标,选择“配置”,然后,把SELECT子句改成: | SQL | 1
2
3
4
5
6
7
| 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将包含2个新字段,CategoryName和SupplierName。 
图30: Products DataTable多了2个新字段 花点时间把GetProductsByCategoryID(categoryID)方法中的SELECT 子句也更新一下。 如果你使用JOIN句法更新GetProducts()中的SELECT语句的话 ,DataSet设计器不能使用DB直接模式自动生成插入,更新,以及删除数据库记录的方法。你必须手工生成这 些方法,就象本教程早先时候我们对InsertProduct方法的做法一样。此外,你必须手工提供 InsertCommand,UpdateCommand和DeleteCommand属性值,假如你 想使用批更新模式的话。 添加其他的TableAdapter到目前为止,我们只讨论了针对单个数据表的单个TableAdapter。但是,Northwind数据库里含有我们需要在 我们的web应用中使用的几个相关的表。一个强类型的DataSet可以包含多个相关的DataTable。因此,为了完 成我们的DAL,我们需要为这些我们将来要用到的数据表添加相应的DataTable。步骤如下,打开 DataSet设计 器,在设计器上按右鼠标,选择“添加/TableAdapter”。这会生成一个新的DataTable和TableAdapter,然后我 们早先讨论过的配置向导会指引你完成配置。 花上几分钟,创建对应于下列查询的TableAdapter及其方法。注意,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.ProductID) 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.ProductID) 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.ProductID) 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.ProductID) 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:添加了四个TableAdapter后的DataSet设计器 给DAL添加定制编码添加到强类型DataSet中的TableAdapter和DataTable是在一个XML Schema定义文 件(Northwind.xsd)中定义的。你可以在解决方案资源管理器里在Northwind.xsd 文件上按右鼠标,选择“查看编码(View Code)”,打开这个Schema文件来查看其中内容。 
图32:Northwinds强类型DataSet的XML Schema定义文件 这个schema信息在设计时编译之后会被翻译成C#或Visual Basic 编码,或者如果有必要的话,会在运行时 翻译,然后你就能在调试器里单步遍历执行。想查看这些自动生成的编码的话,在类视图里,展 开TableAdapter 类或者强类型的DataSet 类。如果在屏幕上看不到类视图的话,在“查看”(View)菜单里选择“ 类视图”,或者按键组合Ctrl+Shift+C。在类视图里,你能看到强类型的DataSet类和TableAdapter类的属性,方法和事件。想看某个特定的方法的编码话,在类视图双击对应方法的名字或者在方法上按右鼠标,选 择“移至定义区(Go To Definition)”。 
图33:在类视图里选择“移至定义区(Go To Definition)”,查看自动生成的编码 虽然自动生成的编码省时省力,但这样的编码往往是非常通用化的(generic),为满足一个应用程序特有的需 求需要做些定制。但扩展自动生成的编码的风险在于,如果生成这些编码的工具决定该是重新生成这些编码的 时候了,则会把你定制的编码冲掉。使用.NET 2.0中的一个新的部分(partial)类的概念,很容易将一个类的 定义分写在几个文件里。这允许我们给自动生成的类添加我们自己的方法,属性,和事件,而不用担心Visual Studio会冲掉我们的定制编码。 为示范如何定制DAL起见,让我们来给SuppliersRow 添加一个GetProducts()方法。这 个SuppliersRow类代表了Suppliers表的个别记录,每个供应商(supplier)可以 提供0个到多个产品,所以GetProducts()将返回指定的供应商的这些产品。做法如 下,在App_Code文件夹里添加一个新的类文件,将其命名为SuppliersRow.cs, 然后在其中添加下列编码: | C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 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);
}
}
}
|
这个部分(partial)类指示编译器在编译Northwind.SuppliersRow类时,应该包含我们刚定义的这个GetProducts()方法。如果你编译你的项目,然后返回类视图,你就会看到GetProducts()已被列为Northwind.SuppliersRow的一个方法。 
图34: GetProducts()方法成为Northwind.SuppliersRow类的一部 分 GetProducts()方法现在就能用来枚举一个指定供应商的产品列单,如下列编码所示:
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 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(" ");
// List the products for this supplier
Northwind.ProductsDataTable products = supplier.GetProducts();
foreach (Northwind.ProductsRow product in products)
Response.Write("" +
product.ProductName + "");
Response.Write("");
}
|
This data can also be displayed in any of ASP.NET's data Web controls. The following page uses a GridView control with two fields:数据也可以在任何一种ASP.NET的Web控件中显示。下面这个网页 使用了含有2个字段的GridView 控件: - 一个BoundField用以显示每个供应商的名字,
- 另一个TemplateField,包含了一个BulletedList控件,用来绑定针对每个供应商调用 的
GetProducts()方法返回的结果
我们将在以后的教程里讨论怎样来显示这样的主/从(master-detail)报表。在这里,这个例子的目的是用 来示范如何使用添加到Northwind.SuppliersRow类中的自定义的方法的。 SuppliersAndProducts.aspx | ASP.NET | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
<!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=""
DataTextField="ProductName">
< />asp:BulletedList>
< />ItemTemplate>
< />asp:TemplateField>
< />Columns>
< />asp:GridView>
< />p>
< />div>
< />form>
< />body>
< />html>
|
SuppliersAndProducts.aspx.cs | C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| 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的话,创建基于强类型DataSet的DAL是个可以不写一行编码,在10到15分钟内就可完成的任务。以后的 教程将建立在这个DAL基础之上。在下一个教程里,我们将定义一堆业务规则,然后看一下如何在一个分开的 业务逻辑层里实现这些规则。 祝编程快乐!
|
一共有 0 条评论