日 历

2008 8.20 Wed
     12
3456789
10111213141516
17181920212223
24252627282930
31      
«» 2008 - 8 «»

文章搜索

日志文章

2007年12月10日 11:37:04

Scott Mitchell 的ASP.NET 2.0数据教程之一继: 创建一个数据访问层

第四步:插入,更新和删除数据

常用的插入,更新和删除数据的模式有两种。第一种模式,我称之为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的InsertCommandUpdateCommand, 和DeleteCommand属性来向数据库发出对应 的INSERTUPDATEDELETE命令。你可以在DataSet设计器里点击TableAdapter,然后在属性窗口查看和改 动InsertCommandUpdateCommand, 和DeleteCommand属性。(确 认你选择了TableAdapter,并且ProductsTableAdapter对象是属性窗口中下拉框里被选中的项)

图23: TableAdapter包含InsertCommandUpdateCommand, 和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表添加新记录的方法

下一个屏显示InsertCommandCommandText属性。在查询语句后面,增添一 个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方法,确认在INSERTSELECT语句间有个分号。

在默认情形下,插入方法调用的是非查询(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表中返回的 是CategoryIDSupplierID的值,但并不包括Categories表 的CategoryName字段和Suppliers表的CompanyName字段,尽管当 我们显示产品信息时,这些很可能是我们想要显示的字段。我们可以扩充TableAdapter的起始方 法GetProducts()来包含CategoryNameCompanyName字段的值, 这方法进而会更新强类型的DataTable来包括这些新的字段。

但这会造成一个问题,因为TableAdapter的插入,更新,删除数据的方法是基于这个起始方法的,幸运的是, 自动生成的插入,更新,删除方法并不会受SELECT子句中的子查询的影响。如果我们注意把 对CategoriesSuppliers的查询添加成子查询,而不是用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个新字段,CategoryNameSupplierName

图30: Products DataTable多了2个新字段

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

如果你使用JOIN句法更新GetProducts()中的SELECT语句的话 ,DataSet设计器不能使用DB直接模式自动生成插入,更新,以及删除数据库记录的方法。你必须手工生成这 些方法,就象本教程早先时候我们对InsertProduct方法的做法一样。此外,你必须手工提供 InsertCommandUpdateCommandDeleteCommand属性值,假如你 想使用批更新模式的话。

添加其他的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 Pagetitle>



<link href="Styles.css"







rel="stylesheet"







type="text/css"







/>



head>



<body>



<form id="form1" runat="server">



<div>



<h1>



Suppliers and Their Productsh1>



<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基础之上。在下一个教程里,我们将定义一堆业务规则,然后看一下如何在一个分开的 业务逻辑层里实现这些规则。

祝编程快乐!

类别: net天空 |  评论(0) |  浏览(1358) |  收藏
发表评论