在ASP.NET 2.0中操作数据之六十八:为DataTable添加额外的列
导言:
当向类型化的数据集(typed dataset)添加一个tableadapter时,相应的datatable的构架已经由tableadapter的主查询定义好了.比如,如果主查询返回a, b,c这3个域,那么 datatable将有对应的3个列a, b,和c.除了主查询以外,tableadapter还可以包含其他的查询,可能是返回基于某些参数的数据。比如,productstableadapter的主查询返回所有产品的信息,此外,productstableadapter还包含诸如getproductsbycategoryid(categoryid) 和 getproductbyproductid(productid)的方法,它们根据指派的参数返回特定的产品信息.
如果tableadapter的方法返回的列涵盖在主查询里,工作起来没有问题。但如果返回的列并没有涵盖在主查询,那么我们就需要对datatable的构架进行扩充.在第35章《使用repeater和datalist单页面实现主/从报表》里,我们向categoriestableadapter添加方法以返回 categoryid, categoryname, description和numberofproducts列。其中前3列是涵盖在主查询里的,而numberofproducts列没有在主查询里定义,它返回的是每个category相关产品的数目.我们可以向categoriesdatatable手工添加一列,以便于统计从新方法返回的numberofproducts列的值.
在第52章《使用fileupload上传文件》我们探讨过,对使用ad-hoc sql statements构建且其方法返回的列超出主查询范围的tableadapters必须多加留意.因为一旦重新运行设置向导的话,它将对tableadapter的方法进行更新,使其返回的列与主查询相匹配.不过如果使用存储过程的话就不会出现这种情况.
在本文我们将考察如何扩展datatable的构架以包含额外的列。我们都知道使用ad-hoc sql statements构架的tableadapter不稳定,本文我们将用存储过程来构架.你可以参考第65章《在tableadapters中创建新的存储过程》和第66章《在tableadapters中使用现有的存储过程》来获取设置tableadapter使用存储过程的更多信息.
第一步:向productsdatatable添加一个pricequartile列
在第67章里我们创建了一个名为northwindwithsprocs的类型化的数据集.该数据集目前包含2个datatables:productsdatatable以及 employeesdatatable。其中productstableadapter包含3个方法:
.getproducts——主查询,返回products表的所有记录
.getproductsbycategoryid(categoryid)——根据指定的categoryid值返回所有产品
.getproductbyproductid(productid)——根据指定的productid值返回所有的产品
主查询及另外2个方法都返回相同的数据列,也就是products表的所有列,并没有返回categories 以及suppliers表的相关数据.
在本文,我们将向productstableadapter添加一个名为getproductswithpricequartile 的方法,它返回所有的产品.除了标准的数据列外,它还返回pricequartile列,它用四分位数来衡量产品价格下跌程度.如果产品价格上升了25%,那么其值为1,如果下降为25%,那么其值为4.在我们创建一个存储过程来返回这种信息之前,我们首先需要更新productsdatatable,新添一列来包含getproductswithpricequartile方法返回的 pricequartile值.
打开northwindwithsprocs数据集,在productsdatatable上右键单击,选择“ add” ,再选择“column”.
图1:向productsdatatable新添一列
这将向datatable新添一列,名为“column1”,类型为system.string.我们需要将该列的名称改为“pricequartile”,类型改为system.int32,因为它的值介于1到4之间.在productsdatatable 选中我们新添加的列,在属性窗口里设置其name属性为 “pricequartile”,datatype属性为system.int32.
图2:设置该新列的name 和 datatype属性
就像图2所示,我们还可以设置其它的属性.比如,是否该列的值必须为unique;如果该列为自增列,其值是否允许为null等等.不过我们这里使用其默认值.
第二步:创建getproductswithpricequartile方法
现在我们已经对productsdatatable进行了更新以包含pricequartile列,我们将要创建一个getproductswithpricequartile方法.在tableadapter上单击右键,再选择“add query”.这将开启tableadapter查询设置向导,它首先询问我们是使用ad-hoc sql statements还是使用现有的存储过程或新建一个存储过程.我们选择“create new stored procedure”,再点next.
图3:在tableadapter向导里创建新的存储过程
接下来,如图4所示,向导询问我们添加的是那种类型的查询,由于getproductswithpricequartile方法将返回products表的所有记录以及所有列,我们选择“select which returns rows”项,再点next.
图4:查询将是一个返回多个行的select statement
接下来,我们在向导里键入如下的查询:
select productid, productname, supplierid, categoryid, quantityperunit, unitprice, unitsinstock, unitsonorder, reorderlevel, discontinued, ntile(4) over (order by unitprice desc) as pricequartile from products
上述查询使用了sql server 2005新增的ntile function函数,它将结果划分为4组,将unitprice值按降序分组.
不幸的是,查询构造器(query builder)不能解析关键字over,并抛出一个错误信息。因此,直接在向导的文本框里键入上述代码,而不要使用查询构造器.
注意:关于ntile以及sql server 2005的其它函数的更多信息,你可参阅文章《returning ranked results with microsoft sql server 2005》(http://www.4guysfromrolla.com/webtech/010406-1.shtml)以及sql server 2005 books online的《ranking functions section》部分(http://msdn2.microsoft.com/en-us/library/ms189798.aspx)
完成后,点next, 向导将要我们为新存储过程重命名,我们取名为products_selectwithpricequartile再点next.
图5:将新存储过程命名为products_selectwithpricequartile
最后我们要为tableadapter的方法命名,选中“fill a datatable” 和 “return a datatable”两项,并重命名为 fillwithpricequartile 和getproductswithpricequartile.
图6:对tableadapter的方法命名并点finish
当指定了select查询,并对存储过程和tableadapter的方法命名后,点finish完成向导。这时你将看到1到2条警告信息,说“the over sql construct or statement is not supported.” 不必理会它.
完成向导后,该tableadapter将会包含fillwithpricequartile 和getproductswithpricequartile方法,并且数据库将包含一个名为products_selectwithpricequartile的存储过程。花点时间来验证一下,检查数据库,如果你没有看到我们刚添加的存储过程,在存储过程文件夹上右键单击,选“刷新”.
图7:验证新方法是否添加到tableadapter
图8:确保数据库包含products_selectwithpricequartile存储过程
注意:使用存储过程来替换ad-hoc sql statements的好处之一是重新运行tableadapter设置向导的话并不会改动存储过程返回的列.我们可以作一个验证,在tableadapter上右键单击,选“configure”项,以启动向导,然后点finish完成向导。接下来,我们在数据库里查看products_selectwithpricequartile存储过程.我们注意到其返回的列并没有发生变化.如果我们使用的是ad-hoc sql statements的话,重新运行向导将会使查询返回的列与主查询的列相匹配,因此它将把getproductswithpricequartile方法里使用的查询里的ntile statement删除掉.
当调用数据访问层的getproductswithpricequartile方法时,tableadapter将执products_selectwithpricequartile存储过程,并为返回的每条记录向productsdatatable添加对应的row.存储过程返回的数据域(data fields)将映射到productsdatatable的列.因为该存储过程要返回一个pricequartile数据域,所以它的值将分配给productsdatatable的pricequartile列.
对于那些不返回pricequartile数据域的方法而言,pricequartile列的值由其defaultvalue属性指定. 如图2所示,该默认值为dbnull。如果你想指定为其他值,仅仅改动defaultvalue属性即可,但一定要是一个有效的值(比如,pricequartile列的值一定要是一个system.int32类型的值).
现在我们完成了向datatable添加额外列的必要的步骤,接下来我们要创建一个asp.net 页面来展示每个产品的 name, price,以及price quartile.不过我们要先对业务逻辑层进行更新,以包含一个方法来调用数据访问层的getproductswithpricequartile方法.我们将在第3步更新业务逻辑层,在第4步创建一个asp.net页面.
第三步:更新业务逻辑层
我们在表现层调用新添加的getproductswithpricequartile方法以前,必须在业务逻辑层添加相应的方法,打开productsbllwithsprocs class类文件,添加如下的代码:
[system.componentmodel.dataobjectmethodattribute (system.componentmodel.dataobjectmethodtype.select, false)] public northwindwithsprocs.productsdatatable getproductswithpricequartile() { return adapter.getproductswithpricequartile(); }
就像其它方法一样,getproductswithpricequartile仅仅调用数据访问层对应的getproductswithpricequartile方法并返回其结果.
第四步:在一个asp.net页面展示price quartile信息
完成对业务逻辑层的修改后,我们将创建一个asp.net页面来显示每个产品的price quartile信息.打开advanceddal文件夹里的addingcolumns.aspx页面,从工具箱拖一个 gridview控件到页面,设置其id为products.在其智能标签里将其绑定到一个名为productsdatasource的新的objectdatasource控件,设置该控件调用productsbllwithsprocs class类的getproductswithpricequartile方法,在update, insert,和delete标签里选“(none)”.
图9:设置objectdatasource调用productsbllwithsprocs类
图10:调用getproductswithpricequartile方法获取产品信息
完成设置向导后, visual studio会为gridview添加boundfield或checkboxfield列,其中包括pricequartile列. 将productname, unitprice,pricequartile以外的列全部删除,设置unitprice列为货币格式.并将unitprice 和 pricequartile列放在右边,居中。 最后分别将这3列的headertext属性设置为“product”, “price”,“price quartile”。同时启用gridview控件的排序功能.
作上述修改后, gridview 和 objectdatasource控件的声明代码看起来和下面的差不多:
<asp:gridview id="products" runat="server" allowsorting="true" autogeneratecolumns="false" datakeynames="productid" datasourceid="productsdatasource"> <columns> <asp:boundfield datafield="productname" headertext="product" sortexpression="productname" /> <asp:boundfield datafield="unitprice" dataformatstring="{0:c}" headertext="price" htmlencode="false" sortexpression="unitprice"> <itemstyle horizontalalign="right" /> </asp:boundfield> <asp:boundfield datafield="pricequartile" headertext="price quartile" sortexpression="pricequartile"> <itemstyle horizontalalign="center" /> </asp:boundfield> </columns> </asp:gridview> <asp:objectdatasource id="productsdatasource" runat="server" oldvaluesparameterformatstring="original_{0}" selectmethod="getproductswithpricequartile" typename="productsbllwithsprocs"> </asp:objectdatasource>
如图11为在浏览器里登录该页面的情况,我们注意到,最开始产品按price的降序排列,同时每个产品都有相应的pricequartile值,当然这些数据也可以按其它标准来排序,如图12所示。
图11:产品按prices来排序
图12:产品按名称来排序.
注意:只需要很少的代码,我们就可以根据每行pricequartile值的不同而显示不同的颜色,比如对值为1的行显示为浅绿色,对值为2的行显示为浅黄色,以此类推.你可以花点时间来实现该功能,如果有必要的话,你可以参阅第11章《》
另一种途径——创建另一个tableadapter
正如我们在本文看到的,当向tableadapter添加的方法返回的列超出了主查询范围的时候,我们可以向datatable添加相应的列即可.对tableadapter而言,如果当其包含的返回“额外列”的方法较少且“额外列”不是很多的时候,这种途径才能正常工作。
除了往datatable添加列以外,我们还可以对dataset添加另外的tableadapter,其包含的方法就是那些需要返回“额外列”的方法.就本问而言,我们可以向dataset添加另一个名为productswithpricequartiletableadapter的tableadapter,它将products_selectwithpricequartile存储过程作为它的主查询,对要获取price quartile信息的asp.net页面来说,只需调用 productswithpricequartiletableadapter即可;而不需要获取price quartile信息的页面只需要调用productstableadapter即可.
这种另外新添加的tableadapters可能导致某些功(functionality)、作业(task)重复.比如,如果那些展示pricequartile列的页面也要启用insert, update,delete功能的话,那么就要对productswithpricequartiletableadapter的insertcommand, updatecommand,deletecommand属性进行适当的设置.而我们已经对productstableadapter的这3个属性进行过设置了,这时就有2种方法来对数据库里的产品进行添加、更新、删除操作了——使用productstableadapter类或 productswithpricequartiletableadapter类.
本文供下载的代码里,在northwindwithsprocs数据集里包含了productswithpricequartiletableadapter class类,演示了这2种方法.
总结:
在大多数情况下,tableadapter的所有方法返回的数据列都是相同的,但有极少数方法会返回主查询没有包含的“额外列”.比如我们在第35章《使用repeater和datalist单页面实现主/从报表》里,我们向categoriestableadapter添加了一个方法,该方法除了返回主查询里的列外,还返回了一个numberofproducts列.而在本文,我们考察类了向 productstableadapter 添加一个方法以返回一个没有包含在主查询里的pricequartile列.对这种返回来的“额外列”,我们需要向datatable添加一个对应的列.
如果你打算手工向datatable添加列,我们建议一使用存储过程.如果用ad-hoc sql statements的话,任何时候只要重新运行tableadapter设置向导,用户所做的所有定制都要被覆盖.而用存储过程的话就不会出现这种情况.
祝编程快乐!
作者简介
本系列教程作者 scott mitchell,著有六本asp/asp.net方面的书,是4guysfromrolla.com的创始人,自1998年以来一直应用 微软web技术。大家可以点击查看全部教程《[翻译]scott mitchell 的asp.net 2.0数据教程》,希望对大家的学习asp.net有所帮助。