在ASP.NET 2.0中操作数据之六十五:在TableAdapters中创建新的存储过程
导言:
本教程的data access layer (dal)使用的是类型化的数据集(typed datasets).就像我们在第一章《》里探讨的一样,该类型化的数据集由强类型的datatable和tableadapter构成。datatable描绘的是系统里的逻辑实体而tableadapter引用相关数据库执行数据访问,包括对datatable填充数据、执行返回标量数据(scalar data)的请求、添加,更新,删除数据库里的记录等.
tableadapter执行的sql命令要么是某个特定的sql statements,比如select columnlist from tablename;要么是存储过程.本教程前面部分的tableadapter使用的是sql statements.不过很多开发者和数据库管理员基于安全、便于维护等方面的考虑,偏爱使用存储过程;不过也有的人出于灵活性的考虑偏爱使用sql statement.就我自己而言,我也偏向于存储过程.在前面的文章,出于简化的目的我选用的是sql statements.
当定义一个新tableadapter或添加新方法时,使用tableadapter的设置向导,我们可以很容易的创建新的或使用现有的存储过程.在本文,我们将考察如何使用设置向导自动的生产存储过程。在下一章我们考察如何设置tableadapter的方法使用现有的或手动创建存储过程.
注意:关于讨论到底使用存储过程还是使用sql statements的问题,可参考rob howard的博客文章《don't use stored procedures yet?》(http://weblogs.asp.net/rhoward/archive/2003/11/17/38095.aspx)和frans bouma的博客文章《stored procedures are bad, m'kay?》(http://weblogs.asp.net/fboue/2003/11/18/38178.aspx)
存储过程基础
一个存储过程由一系列的t-sql statement组成,当调用该存储过程时就执行这些t-sql statement.存储过程可以接受0到多个输入参数,返回标量值、输出参数,或最常见的返回select查询值.
注意:存储过程stored procedures也经常引用为“sprocs” or “sps”.
可以使用t-sql statement语句create procedure来创建存储过程.比如下面的t-sql脚本创建了一个名为getproductsbycategoryid的存储过程,它有一个名为 @categoryid的参数,并且将表products里与categoryid值相吻合的那条记录的productid, productname, unitprice,以及discontinued值返回.
create procedure getproductsbycategoryid ( @categoryid int ) as select productid, productname, unitprice, discontinued from products where categoryid = @categoryid
创建后,我们可以用下面的代码调用它:
exec getproductsbycategory categoryid
注意:在下篇文章我们将在visual studio ide集成环境里创建存储过程.不过在本文,我们将用tableadapter向导来自动创建存储过程.
除了返回数据外,我们还可以在一个事务里用存储过程执行多条数据库命令.比如,假如有一个名为deletecategory的存储过程,其包含一个输入参数@categoryid,并执行2个delete statemets,第一个是删除相关的products,第二个是删除category。存储过程里面的多个statements并不是自动的封装在一个事务里的.我们应添加额外的t-sql commands以确保存储过程里的多条数据库命令当成原子操作处理.我们将在后面的内容考察如何用事务来封装存储过程的命令.
当在体系的某个层使用存储过程时,data access layer的方法将调用某个具体的存储过程而不是发出一个sql statement命令.这样一来我们可以发现、分析发出的查询命令.并可以更清楚的看到数据库是如何使用的.有关存储过程基本原理的更多信息,可参考本文结束部分的延伸阅读.
第一步:创建数据访问层高级场景的web页面
在开始之前,让我们花点时间创建本文及后面几篇文章要用到的页面。新建一个名为advanceddal的文件夹,然后添加如下的asp.net页面,记得使用母版页site.master:
default.aspx
newsprocs.aspx
existingsprocs.aspx
joins.aspx
addingcolumns.aspx
computedcolumns.aspx
encryptingconfigsections.aspx
managedfunctionsandsprocs.aspx
图1:添加相关的页面
像其它文件夹一样,default.aspx页面将列出本部分的内容,记得sectionleveltutoriallisting.ascx用户控件提供了该功能。因此,将其从解决资源管理器里拖放到default.aspx页面.
图2:将sectionleveltutoriallisting.ascx用户控件拖到default.aspx页面
最后,将这些页面添加到web.sitemap文件里。特别的,把下面的代码放在“working with batched data”
<sitemapnode>标签后面:
<sitemapnode url="~/advanceddal/default.aspx" title="advanced dal scenarios" description="explore a number of advanced data access layer scenarios."> <sitemapnode url="~/advanceddal/newsprocs.aspx" title="creating new stored procedures for tableadapters" description="learn how to have the tableadapter wizard automatically create and use stored procedures." /> <sitemapnode url="~/advanceddal/existingsprocs.aspx" title="using existing stored procedures for tableadapters" description="see how to plug existing stored procedures into a tableadapter." /> <sitemapnode url="~/advanceddal/joins.aspx" title="returning data using joins" description="learn how to augment your datatables to work with data returned from multiple tables via a join query." /> <sitemapnode url="~/advanceddal/addingcolumns.aspx" title="adding datacolumns to a datatable" description="master adding new columns to an existing datatable." /> <sitemapnode url="~/advanceddal/computedcolumns.aspx" title="working with computed columns" description="explore how to work with computed columns when using typed datasets." /> <sitemapnode url="~/advanceddal/encryptingconfigsections.aspx" title="protected connection strings in web.config" description="protect your connection string information in web.config using encryption." /> <sitemapnode url="~/advanceddal/managedfunctionsandsprocs.aspx" title="creating managed sql functions and stored procedures" description="see how to create sql functions and stored procedures using managed code." /> </sitemapnode>
更新web.sitemap文件后,花点时间在浏览器里查看,左边的菜单将包括本部分的内容.
图3:网站地图现在包含了不部分的页面
第二步:设置tableadapter创建新的存储过程
我们在~/app_code/dal文件夹里创建一个类型化的dataset,名称为northwindwithsprocs.xsd.由于我们在以前的教程里已经详细探讨了创建细节,因此我们这里一笔带过,如果你想知道详细的创建过程请参阅前面的第1章《》在dal文件夹上右击鼠标选“添加新项”,选dataset模板,如图4所示.
图4:新建一个名为northwindwithsprocs.xsd的数据集
这样将会创建一个新的类型化的dataset,打开设计器,创建一个新的tableadapter,展开tableadapter设置向导.向导的第一步是让我们选择要连接的数据库.在下拉列表里有一个连接到northwind数据库的连接字符串,选中它,再点下一步。接下来的界面让我们选择tableadapter以哪种方式访问数据库.在以前的教程里我们选择的是“use sql statements”,不过在本文我们选第二项:“create new stored procedures”,点下一步.
图5:设置tableadpater创建新的存储过程
接下来,我们要指定主查询(main query).我们将创建一个存储过程来包含select查询.
使用下面的select查询:
select productid, productname, supplierid, categoryid, quantityperunit, unitprice, unitsinstock, unitsonorder, reorderlevel, discontinued from products
图6:键入select查询
注意:在名为northwind的数据集里的productstableadapter的主查询与上面本文定义的主查询有所不同。那个主查询还返回了每个产品的category名称和company名称.不过在后面的文章我们将对本文的tableadapter添加这些相关的代码.再点“advanced options”按钮.我们可以指定是否让向导为tableadapter自动生成insert, update和delete statements;是否使用开发式并发操作(optimistic concurrency);是否完成inserts 和 update操作后刷新数据表.在默认情况下,自动选中“generate insert, update and delete statements”选项。另外,本文不用选择“use optimistic concurrency”项.当选择自动创建存储过程时,“refresh the data table”项将被忽略掉.不管是否选中该项,最终的insert 和update存储过程都会检索刚添加或刚更新(just-inserted or just-updated record)的记录,我们将在第三步看到.
图7:选中“generate insert, update and delete statements”项
注意:当选中“use optimistic concurrency”项的时候,向导会在where语句里添加额外的条件,当其它列的值发生改动的话,将阻止数据更新.关于使用tableadapter内置的optimistic concurrency功能请参阅第21章《》输入select主查询并选取“generate insert, update and delete statements”项后,点下一步,接下来的界面,如图8所示,让我们为selecting, inserting, updating, 和deleting数据的存储过程命名.将这些存储过程的名字改为products_select, products_insert, products_update, 和products_delete.
图8:为存储过程重命名
向导创建了4个存储过程,点“preview sql script”按钮,你可以在preview sql script 对话框里将脚本保存在一个文件里或复制到剪贴板.
图9:预览生成的存储过程
对存储过程重命名后,点下一步,对tableadapter相应的方法命名.就像使用sql statements一样,我们可以创建方法来填充一个现有的datatable或返回一个新的datatable;我们也一个指定tableadapter是否采用db-direct模式来插入、更新、删除记录.全选这3项,只不过将return a datatable方法重命名为getproducts,如图10所示:
图10:将方法重命名为fill 和getproducts
点next总览向导将执行的步骤.点finish按钮完成设置.一旦向导结束后,将返回dataset设计器,它此时将包括productsdatatable.
图11:dataset设计器将显示刚刚添加的productsdatatable
第三步:考察刚刚创建的存储过程
我们在第二步里用向导创建了选择、插入、更新、删除数据的存储过程.这些存储过程可以通过visual studio查看或修改.打开服务器资源管理器,点到数据库的存储过程文件夹。如图12所示,northwind数据库包含了4个新的存储过程,products_delete, products_insert, products_select, and products_update.
图12:可以在stored procedures文件夹里找到我们创建的4个存储过程
注意:如果你看不到服务器资源管理器,点“view”菜单,选server explorer项.如果你无法找到新创建的存储过程,右击stored procedures文件夹,选“刷新”.
要查看或修改某个存储过程,在服务器资源管理器里双击其名字或右击该存储过程,选”打开“。如13显示的是打开products_delete存储过程的画面.
图13:可以在visual studio里打开并修改存储过程
products_delete和products_select存储过程的内容很好理解。比如下面的代码构成了products_insert存储过程.
alter procedure dbo.products_insert ( @productname nvarchar(40), @supplierid int, @categoryid int, @quantityperunit nvarchar(20), @unitprice money, @unitsinstock smallint, @unitsonorder smallint, @reorderlevel smallint, @discontinued bit ) as set nocount off; insert into [products] ([productname], [supplierid], [categoryid], [quantityperunit], [unitprice], [unitsinstock], [unitsonorder], [reorderlevel], [discontinued]) values (@productname, @supplierid, @categoryid, @quantityperunit, @unitprice, @unitsinstock, @unitsonorder, @reorderlevel, @discontinued); select productid, productname, supplierid, categoryid, quantityperunit, unitprice, unitsinstock, unitsonorder, reorderlevel, discontinued from products where (productid = scope_identity())
在tableadapter向导里定义的select查询返回products表里的列,这些列又作为存储过程的输入参数并运用到insert statement中.紧接着的是一个select查询,返回products表里最新添加的记录的各列的值(包括productid)。当使用batch update模式添加一个新记录时,刷新功能是很有用的。因为它将最新添加的productrow instances实例的productid属性赋值为数据库指派的自增值.
下面的代码说明了该功能.代码创建了基于northwindwithsprocs数据集的productstableadapter以及productsdatatable。要向数据库添加一个新的产品,我们要创建一个productsrow instance实例,对其赋值,并调用tableadapter的update方法,再传递给productsdatatable.在内部,tableadapter的update方法遍历传递给datatable的所有productsrow instance实例(在本例,只有一个。因为我们只添加了一个产品),并执行相应的insert, update, 或delete命令。此时,执行products_insert存储过程,其向products表添加一条新记录,并返回该记录的详细信息,然后更新productsrow instance实例的productid值。update方法完成后,我们就可以通过productsrow的productid属性访问新添加记录的productid值了.
// create the productstableadapter and productsdatatable northwindwithsprocstableadapters.productstableadapter productsapi = new northwindwithsprocstableadapters.productstableadapter(); northwindwithsprocs.productsdatatable products = new northwindwithsprocs.productsdatatable(); // create a new productsrow instance and set its properties northwindwithsprocs.productsrow product = products.newproductsrow(); product.productname = "new product"; product.categoryid = 1; // beverages product.discontinued = false; // add the productsrow instance to the datatable products.addproductsrow(product); // update the datatable using the batch update pattern productsapi.update(products); // at this point, we can determine the value of the newly-added record's productid int newlyaddedproductidvalue = product.productid;
类似的,products_update存储过程的update statement后面也包含一个select statement,如下:
alter procedure dbo.products_update ( @productname nvarchar(40), @supplierid int, @categoryid int, @quantityperunit nvarchar(20), @unitprice money, @unitsinstock smallint, @unitsonorder smallint, @reorderlevel smallint, @discontinued bit, @original_productid int, @productid int ) as set nocount off; update [products] set [productname] = @productname, [supplierid] = @supplierid, [categoryid] = @categoryid, [quantityperunit] = @quantityperunit, [unitprice] = @unitprice, [unitsinstock] = @unitsinstock, [unitsonorder] = @unitsonorder, [reorderlevel] = @reorderlevel, [discontinued] = @discontinued where (([productid] = @original_productid)); select productid, productname, supplierid, categoryid, quantityperunit, unitprice, unitsinstock, unitsonorder, reorderlevel, discontinued from products where (productid = @productid)
我们注意到该存储过程有2个关于productid的参数,即@original_productid 和@productid,这样以来我们就可以对主键值进行改动了.举个例子:有一个employee(雇员)数据库,每条employee记录都用雇员的社保号码作为其主键值.要想更改某条记录的社保号码,必须提供新的号码以及原始号码.不过对products表来说用不着,因为列productid是一个唯一标识列(identity column),不应对其更改.实际上,products_update存储过程里的update statement并没有包含productid列,因此,如果在update statement的where字句里使用@original_productid的话,显得多此一举,而应该使用@productid参数.当更新某个存储过程的参数时,tableadapter里所有那些调用该存储过程方法都应该进行更新.
第四步:修改存储过程的参数并更新tableadapter
由于@original_productid参数是多余的,让我们将其从products_update存储过程里完全清除.打开products_update存储过程,删除@original_productid参数,在update statement的where字句里将@original_productid改为@productid. 完成上述修改后,该存储过程里的t-sql看起来应该和下面的差不多:
alter procedure dbo.products_update ( @productname nvarchar(40), @supplierid int, @categoryid int, @quantityperunit nvarchar(20), @unitprice money, @unitsinstock smallint, @unitsonorder smallint, @reorderlevel smallint, @discontinued bit, @productid int ) as set nocount off; update [products] set [productname] = @productname, [supplierid] = @supplierid, [categoryid] = @categoryid, [quantityperunit] = @quantityperunit, [unitprice] = @unitprice, [unitsinstock] = @unitsinstock, [unitsonorder] = @unitsonorder, [reorderlevel] = @reorderlevel, [discontinued] = @discontinued where (([productid] = @productid)); select productid, productname, supplierid, categoryid, quantityperunit, unitprice, unitsinstock, unitsonorder, reorderlevel, discontinued from products where (productid = @productid)
按ctrl+s或点工具栏里的“保存”图标,保存更改.此时,products_update存储过程不会执行@original_productid参数,但tableadapter仍然会传递该参数.要想查看tableadapter传递给products_update存储过程的参数,你可以在设计器里选中tableadapter,转到属性窗口,点更新命令的参数集(updatecommand'sparameters collection)里的椭圆型区域,这样将转到parameters collection editor对话框,如图14所示:
图14:对话框里列出了传递给products_update存储过程的参数
要删除参数,只需选中它,再点remove按钮.
要刷新参数的话,你也可以在设计器里选中tableadapter,点右键选“设置”,这将会开启tableadapter设置向导,它列出了用于select, insert, updat和delete的存储过程,并列出了这些存储过程的输入参数.如果你在update下拉列表里选products_update的话,你可以看到该存储过程包含的输入参数里已经没有包含@original_productid了(见图15),点finish将对tableadapter使用的参数集自动更新.
图15:你可以通过使用tableadapter的设置向导来刷新参数集
第五步:添加额外的tableadapter方法
我们在第二步说过,当创建一个新的tableadapter时,很容易自动地生成相应的存储过程,同样我们也可以向tableadapter添加额外的方法.作为演示,让我们向productstableadapter添加一个方法getproductbyproductid(productid),该方法将一个productid作为输入参数,并返回该产品的详细信息.在productstableadapter上点击右键,选择“添加查询”.
图16:向tableadapter添加新查询
这将开启tableadapter查询设置向导。首先,向导将询问以何种方式访问数据库,我们将创建一个新的存储过程,因此选“create a new stored procedure”,再点next.
图17:选中“create a new stored procedure”项
接下来,向导询问我们执行哪种查询,是返回一系列行?一个标量值?又或者执行update, insert,或 delete statement.由于getproductbyproductid(productid)方法将返回一行,我们选择“select which returns row”项,再点next.
图18:选择“select which returns row” 项
接下来的界面将展示tableadapter的主查询,其仅仅列出了存储过程的名字(也就是dbo.products_select).将其删除,替换为如下的select statement,它返回某个具体产品的所有列.
select productid, productname, supplierid, categoryid, quantityperunit, unitprice, unitsinstock, unitsonorder, reorderlevel, discontinued from products where productid = @productid
图19:将存储过程的名字替换为一个select查询.
接下来要对创建的存储过程命名,输入products_selectbyproductid,点next.
图20:将新存储过程命名为products_selectbyproductid
最后一步将要我们对自动生成的名字重新命名,并指定是否使用fill a datatable模式、是否使用return a datatable模式,抑或这2种模式都采用.就本文而言,都选中这2项并将方法重命名为fillbyproductid 和 getproductbyproductid.点next,再点finish完成设置向导.
图21:将tableadapter的方法重命名为fillbyproductid 和 getproductbyproductid
完成向导后,tableadapter将包含一个新的可用方法——getproductbyproductid(productid),当调用该方法时,将执行我们刚刚创建的products_selectbyproductid存储过程.花点时间在服务器资源管理器里查看该存储过程,点stored procedures文件夹,并打开products_selectbyproductid(如果你没看到它,在stored procedures文件夹上右击鼠标,选“刷新”).
请注意,selectbyproductid存储过程将@productid作为输入参数,并执行我们在向导里输入的select statement,如下:
alter procedure dbo.products_selectbyproductid ( @productid int ) as set nocount on; select productid, productname, supplierid, categoryid, quantityperunit, unitprice, unitsinstock, unitsonorder, reorderlevel, discontinued from products where productid = @productid
第六步:创建一个业务逻辑层类
在我们打算从表现层访问产品前,我们首先需要为新添加的数据集创建一个bll class,在~/app_code/bll文件夹里创建一个productsbllwithsprocs.cs文件,如下:
using system; using system.data; using system.configuration; 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 northwindwithsprocstableadapters; [system.componentmodel.dataobject] public class productsbllwithsprocs { private productstableadapter _productsadapter = null; protected productstableadapter adapter { get { if (_productsadapter == null) _productsadapter = new productstableadapter(); return _productsadapter; } } [system.componentmodel.dataobjectmethodattribute (system.componentmodel.dataobjectmethodtype.select, true)] public northwindwithsprocs.productsdatatable getproducts() { return adapter.getproducts(); } [system.componentmodel.dataobjectmethodattribute (system.componentmodel.dataobjectmethodtype.select, false)] public northwindwithsprocs.productsdatatable getproductbyproductid(int productid) { return adapter.getproductbyproductid(productid); } [system.componentmodel.dataobjectmethodattribute (system.componentmodel.dataobjectmethodtype.insert, true)] public bool addproduct (string productname, int? supplierid, int? categoryid, string quantityperunit, decimal? unitprice, short? unitsinstock, short? unitsonorder, short? reorderlevel, bool discontinued) { // create a new productrow instance northwindwithsprocs.productsdatatable products = new northwindwithsprocs.productsdatatable(); northwindwithsprocs.productsrow product = products.newproductsrow(); product.productname = productname; if (supplierid == null) product.setsupplieridnull(); else product.supplierid = supplierid.value; if (categoryid == null) product.setcategoryidnull(); else product.categoryid = categoryid.value; if (quantityperunit == null) product.setquantityperunitnull(); else product.quantityperunit = quantityperunit; if (unitprice == null) product.setunitpricenull(); else product.unitprice = unitprice.value; if (unitsinstock == null) product.setunitsinstocknull(); else product.unitsinstock = unitsinstock.value; if (unitsonorder == null) product.setunitsonordernull(); else product.unitsonorder = unitsonorder.value; if (reorderlevel == null) product.setreorderlevelnull(); else product.reorderlevel = reorderlevel.value; product.discontinued = discontinued; // add the new product products.addproductsrow(product); int rowsaffected = adapter.update(products); // return true if precisely one row was inserted, otherwise false return rowsaffected == 1; } [system.componentmodel.dataobjectmethodattribute (system.componentmodel.dataobjectmethodtype.update, true)] public bool updateproduct (string productname, int? supplierid, int? categoryid, string quantityperunit, decimal? unitprice, short? unitsinstock, short? unitsonorder, short? reorderlevel, bool discontinued, int productid) { northwindwithsprocs.productsdatatable products = adapter.getproductbyproductid(productid); if (products.count == 0) // no matching record found, return false return false; northwindwithsprocs.productsrow product = products[0]; product.productname = productname; if (supplierid == null) product.setsupplieridnull(); else product.supplierid = supplierid.value; if (categoryid == null) product.setcategoryidnull(); else product.categoryid = categoryid.value; if (quantityperunit == null) product.setquantityperunitnull(); else product.quantityperunit = quantityperunit; if (unitprice == null) product.setunitpricenull(); else product.unitprice = unitprice.value; if (unitsinstock == null) product.setunitsinstocknull(); else product.unitsinstock = unitsinstock.value; if (unitsonorder == null) product.setunitsonordernull(); else product.unitsonorder = unitsonorder.value; if (reorderlevel == null) product.setreorderlevelnull(); else product.reorderlevel = reorderlevel.value; product.discontinued = discontinued; // update the product record int rowsaffected = adapter.update(product); // return true if precisely one row was updated, otherwise false return rowsaffected == 1; } [system.componentmodel.dataobjectmethodattribute (system.componentmodel.dataobjectmethodtype.delete, true)] public bool deleteproduct(int productid) { int rowsaffected = adapter.delete(productid); // return true if precisely one row was deleted, otherwise false return rowsaffected == 1; } }
该类和以前章节所创建的productsbll class类差不多,只是它用的是数据集 northwindwithsprocs的productstableadapter 和 productsdatatable object对象。与productsbll类使用using northwindtableadapters不同,productsbllwithsprocs类使用的是using northwindwithsprocstableadapters.同样的,该类的productsdatatable和 productsrow对象使用的是northwindwithsprocs命名空间.我们的productsbllwithsprocs class类提供了2种数据访问方法getproducts() 和getproductbyproductid().另外,还有添加、更新、删除单个产品的方法.
第七步:在表现层出来数据集northwindwithsprocs
此时,我们以及对数据访问层和业务逻辑层做了相关改动,接下来我们要创建一个asp.net页面调用bll的productsbllwithsprocs class类以展示、更新、删除记录.
打开advanceddal文件夹里的newsprocs.aspx页面,从工具箱拖一个gridview控件到页面,设置其id为products. 从gridview的智能标签将其绑定到一个名为productsdatasource的objectdatasource,设置其调用productsbllwithsprocs类.
图22:设置objectdatasource调用productsbllwithsprocs类
select标签的下拉列表里有2个方法,getproducts()和getproductbyproductid().由于我们将在gridview里显示所有的产品,所以我们选getproducts()方法.在update, insert, 和delete标签里都只有一个方法,确保选中它们,点finish按钮。
完成设置后,visual studio会向gridview添加boundfields列以及一个checkboxfield列, 启用gridview控件的“编辑”和“删除”功能.
图23:页面包含一个可以分页和排序的gridview控件.
就像在以前的教程里探讨过的一样,完成objectdatasource的设置后,visual studio 会自动的将oldvaluesparameterformatstring属性设置为“original_{0}”. 为使数据修改功能正常工作,要么将该属性删除,要么将其设置为“{0}”.
在我们完成设置、启用“编辑”和“删除”功能、将oldvaluesparameterformatstring属性设为其默认值后,页面的声明代码看起来应该和下面的差不多:
<asp:gridview id="products" runat="server" autogeneratecolumns="false" datakeynames="productid" datasourceid="productsdatasource"> <columns> <asp:commandfield showdeletebutton="true" showeditbutton="true" /> <asp:boundfield datafield="productid" headertext="productid" insertvisible="false" readonly="true" sortexpression="productid" /> <asp:boundfield datafield="productname" headertext="productname" sortexpression="productname" /> <asp:boundfield datafield="supplierid" headertext="supplierid" sortexpression="supplierid" /> <asp:boundfield datafield="categoryid" headertext="categoryid" sortexpression="categoryid" /> <asp:boundfield datafield="quantityperunit" headertext="quantityperunit" sortexpression="quantityperunit" /> <asp:boundfield datafield="unitprice" headertext="unitprice" sortexpression="unitprice" /> <asp:boundfield datafield="unitsinstock" headertext="unitsinstock" sortexpression="unitsinstock" /> <asp:boundfield datafield="unitsonorder" headertext="unitsonorder" sortexpression="unitsonorder" /> <asp:boundfield datafield="reorderlevel" headertext="reorderlevel" sortexpression="reorderlevel" /> <asp:checkboxfield datafield="discontinued" headertext="discontinued" sortexpression="discontinued" /> </columns> </asp:gridview> <asp:objectdatasource id="productsdatasource" runat="server" deletemethod="deleteproduct" insertmethod="addproduct" selectmethod="getproducts" typename="productsbllwithsprocs" updatemethod="updateproduct"> <deleteparameters> <asp:parameter name="productid" type="int32" /> </deleteparameters> <updateparameters> <asp:parameter name="productname" type="string" /> <asp:parameter name="supplierid" type="int32" /> <asp:parameter name="categoryid" type="int32" /> <asp:parameter name="quantityperunit" type="string" /> <asp:parameter name="unitprice" type="decimal" /> <asp:parameter name="unitsinstock" type="int16" /> <asp:parameter name="unitsonorder" type="int16" /> <asp:parameter name="reorderlevel" type="int16" /> <asp:parameter name="discontinued" type="boolean" /> <asp:parameter name="productid" type="int32" /> </updateparameters> <insertparameters> <asp:parameter name="productname" type="string" /> <asp:parameter name="supplierid" type="int32" /> <asp:parameter name="categoryid" type="int32" /> <asp:parameter name="quantityperunit" type="string" /> <asp:parameter name="unitprice" type="decimal" /> <asp:parameter name="unitsinstock" type="int16" /> <asp:parameter name="unitsonorder" type="int16" /> <asp:parameter name="reorderlevel" type="int16" /> <asp:parameter name="discontinued" type="boolean" /> </insertparameters> </asp:objectdatasource>
此时,我们可以对gridview控件做些修改,比如在编辑界面里使用确认控件,在categoryid 和 supplierid列放置dropdownlist控件,当点击delete按钮时弹出确认框等.由于在以前的教程我们探讨过这些主题,我不打算在此多花笔墨。
不管你做没做这些改进,让我们在浏览器里对页面测试,如图24所示.在gridview控件里每行都可以编辑和删除.
图24:可以通过gridview对产品进行查看、编辑、删除
结语:
类型化数据集里的tableadapters可以通过ad-hoc sql statement或存储过程访问数据库里的数据.当处理存储过程时,我们要么使用现有的存储过程,要么使用tableadapter向导创建一个基于select查询的新的存储过程.在本文,我们考察了如何自动的创建一个存储过程.
虽然自动创建可以节省时间,但是在某些情况下,向导自动创建的存储过程与我们的期望值还是有差距.比如自动创建的products_update存储过程,它包含@original_productid 和 @productid这2个参数,但@original_productid参数对我们来说是多余的.
在接下来的文章,我们将考察tableadapter使用现有的存储过程的情况.
祝编程快乐!
作者简介
本系列教程作者 scott mitchell,著有六本asp/asp.net方面的书,是4guysfromrolla.com的创始人,自1998年以来一直应用 微软web技术。大家可以点击查看全部教程《[翻译]scott mitchell 的asp.net 2.0数据教程》,希望对大家的学习asp.net有所帮助。
推荐阅读
-
在ASP.NET 2.0中操作数据之六十五:在TableAdapters中创建新的存储过程
-
在ASP.NET 2.0中操作数据之五十一:从GridView的页脚插入新记录
-
在ASP.NET 2.0中操作数据之六十:创建一个自定义的Database-Driven Site Map Provider
-
在ASP.NET 2.0中操作数据之六十六:在TableAdapters中使用现有的存储过程
-
在ASP.NET 2.0中操作数据之六十五:在TableAdapters中创建新的存储过程
-
在ASP.NET 2.0中操作数据之七十二:调试存储过程
-
在ASP.NET 2.0中操作数据之七十四:用Managed Code创建存储过程和用户自定义函数(下部分)
-
在ASP.NET 2.0中操作数据之七十三:用Managed Code创建存储过程和用户自定义函数(上部分)
-
在ASP.NET 2.0中操作数据之五十一:从GridView的页脚插入新记录
-
在ASP.NET 2.0中操作数据之六十:创建一个自定义的Database-Driven Site Map Provider