ASP.NET 2.0中的数据操作:使用两个DropDownList过滤的主/从报表
英文原版 | 本教程中的代码
导言
在前面的指南中我们研究了如何显示一个简单的主/从报表, 该报表使用DropDownList和GridView控件, DropDownList填充类别,GridView显示选定类别的产品. 这类报表用于显示具有一对多关系的记录时非常合适, 同时它也可以很容易的被扩展以显示多个一对多关系的数据. 比如, 一个订单系统应该包含表示客户,订单和订单明细的表. 一个客户也许有多个订单,每个订单又包含多条订单项. 这样的数据可以使用两个DropDownList和一个GridView呈现给用户. 第一个DropDownList应该包含数据库中所有客户的列表, 第二个DropDownList的内容是选定客户的订单. GridView用于列出所选定订单的订单明细项.
Step 1: 创建DropDownList并使用类别数据填充
我们的第一个目标是添加一个能够列出类别的DropDownList. 这些步骤在前面的指南中已经做详细的分析, 但为了保持本篇指南的完整性有必要在这里简单概括一下.
打开Filtering文件夹中的MasterDetailsDetails.aspx, 在页面上添加一个DropDownList, 设置它的ID为Categories,然后单击智能标记上的 Configure Data Source链接. 在数据源配置向导中选择新增一个数据源.
图 1: 为DropDownList增加一个新的数据源
当然, 新的数据源应该是ObjectDataSource. 把新的ObjectDataSource命名为CategoriesDataSource并且让他调用CategoriesBLL对象的GetCategories()方法
图 2: 选择使用CategoriesBLL类
图3: 配置ObjectDataSource使用GetCategories()方法
配置完ObjectDataSource后还需要指定要在DropDownList中显示的数据字段,以及作为数据项的值(value for the list item)的数据字段.我们指定CategoryName为要显示的列,指定CategoryID为数据项的值字段
图4: 指定DropDownList 显示CategoryName列并且使用CategoryID列作为数据项的值
这时,我们就有了一个使用Categories表中的记录来填充的DropDownList控件. 当用户在DropDownList中选择一个新的类别时, 我们需要一次回发,这样可以刷新在第二步中我们要创建的产品DropDownList控件. 所以, categoriesDropDownList的智能标记上选中AutoPostBack选项.
图 5: 选中Categories DropDownList 的AutoPostBack
Step 2: 在第二个DropDownList 中显示选中类别的产品
Categories DropDownList完成后, 下一步就需要一个显示属于选定类别的产品的DropDownList了. 要完成这个功能, 再增加一个DropDownList控件并命名为ProductsByCategory. 与Categories DropDownList一样, 为ProductsByCategory DropDownList创建一个新的ObjectDataSource并命名为ProductsByCategoryDataSource.
图 6: 为ProductsByCategory DropDownList添加新数据源
图7: 创建一个新的 ObjectDataSource 并命名为ProductsByCategoryDataSource
由于ProductsByCategory DropDownList 需要显示属于选定类别的产品, 让ObjectDataSource 调用ProductsBLL对象的GetProductsByCategoryID(categoryID)方法.
图8: 选择使用ProductsBLL类
图9: 配置ObjectDataSource 使用GetProductsByCategoryID(categoryID)方法
在向导的最后一步需要指定categoryID的值. 我们把Categories DropDownList的已选择的数据项项作为该参数值.
图10: 使用categoryID参数从Categories DropDownList接收值
ObjectDataSource配置完成后, 剩下的就是指定DropDownList要显示的字段和作为值的字段了. 设置为显示ProductName并把ProductID做为值字段
图11: 指定 DropDownList数据项的文本和值使用的数据源字段
ObjectDataSource 和ProductsByCategory DropDownList配置完成后页面上会有两个DropDownList: 第一个列出所有的类别,第二个列出属于选定类别的产品. 当用户在第一个DropDownList上选择了一个新的类别后, 将会发生一次回发(postback),第二个DropDownList将会重新绑定以显示属于新选定类别的产品. 图12 和图13显示了在浏览器中看到的MasterDetailsDetails.aspx页面.
图12: 第一次访问页面时Beverages 类别是选中的.
图13: 选择一个不同的类别时显示该类别的产品
图14: 激活productsByCategory DropDownList的 AutoPostBack属性
Step 3: 使用DetailsView 显示选中产品的详细信息
最后一个步骤是在DetailsView中显示选中产品的详细信息. 要完成该功能, 添加一个DetailsView到页面上, 设置它的ID属性为ProductDetails, 给它创建一个新的ObjectDataSource. 配置ObjectDataSource使它通过ProductsBLL类的GetProductByProductID(productID)方法填充数据,使用ProductsByCategory DropDownList的已选择项的值作为productID参数的值.
图15: 选择使用ProductsBLL类
图16: 配置 ObjectDataSource 使用GetProductByProductID(productID)方法
图17: 使用ProductsByCategory DropDownList的值作为productID参数的值
你可以选择在DetailsView显示的任何有效的字段. 我决定不显示ProductID, SupplierID, 和CategoryID字段并且对其余的字段重新排序及格式化.另外, 我去掉了DetailsView的Height和Width属性设置, 允许DetailsView可以扩展到需要的宽度, 这样比把它限制在指定的大小会更好的显示数据. 下面便是全部的标记性语言(markup)
<asp:DetailsView ID="ProductDetails" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID"
DataSourceID="ObjectDataSource1" EnableViewState="False">
<Fields>
<asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" />
<asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" />
<asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True" SortExpression="SupplierName" />
<asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" />
<asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" HeaderText="Price"
HtmlEncode="False" SortExpression="UnitPrice" />
<asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="Units In Stock" />
<asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="Units On Order" />
<asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="Reorder Level" />
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
</Fields>
</asp:DetailsView>
花点时间在浏览器中测试一下MasterDetailsDetails.aspx页面. 乍一看好像一切如预计的那样顺利, 但是却有一个小问题. 当你选择了一个新类别, ProductsByCategory DropDownList会更新显示选中类别的产品, 但是ProductDetails DetailsView还是显示上一个产品的信息. 当选择了一个选中类别的不同的产品时DetailsView应该被更新. 此外,如果你的测试足够彻底, 你将会发现如果你不断的选择新类别(比如在Categories DropDownList中选择Beverages,然后选择Condiments,然后是Confections)每个类别的选择都会使ProductDetails DetailsView被刷新.
为了让这个问题更具体一些, 让我们看一个例子. 当你第一次访问该页面时, Beverages类别是选中的并且与之相关的产品在ProductsByCategory DropDownList中列出. Chai是当前选中的产品,他的详细信息显示在ProductDetails DetailsView中, 如图18.
图18: DetailsView显示了选中产品的详细信息
如果你把类别选项Beverages 改成 Condiments, 便会发生一次回发, ProductsByCategory DropDownList会相应的更新, 但是DetailsView仍然会显示Chai的详细信息.
图19:上一次选择产品的详细信息仍然在显示
在列表中选择一个产品会如预期地一样刷新DetailsView, 如果你改变了产品之后选择了一个新的类别, DetailsView又一次没有刷新. 然后如果你选择一个新类别而不是选择一个产品, DetailsView则会刷新. 究竟是怎么回事呢?
这个问题是由页面生命周期的时间调度引发的. 当页面被请求时会经过一系列的处理后呈现出来. 这其中的一项处理便是ObjectDataSource控件检查他的SelectParameters值是否发生变化. 如果发生了变化, WEB控件绑定到ObjectDataSource的数据便会刷新显示. 比如, 当一个新类别被选中时, ProductsByCategoryDataSource ObjectDataSource发现它的参数值已经发生了变化,然后ProductsByCategory DropDownList就会重新绑定,获取所选中类别的产品.
在这种情况下发生的该问题是由于在页面的生命周期中ObjectDataSources检查参数是否发生改变的动作是在关联的WEB控件重新绑定之前. 因此,当选择了一个新的类别ProductsByCategoryDataSource ObjectDataSource会检查到它的参数值发生了变化. 但是, ProductDetails DetailsView使用的ObjectDataSource并没有发生任何变化,因为ProductsByCategory DropDownList还没有重新绑定. 在稍后的生命周期中ProductsByCategory DropDownList重新绑定到它的ObjectDataSource,获取刚刚选中类别的产品. 当ProductsByCategory DropDownList的值发生变化的时候ProductDetails DetailsView的 ObjectDataSource已经完成了它的参数值检查工作. 因此, DetailsView还是显示先前的数据. 图20描述了这个交互的过程.
图20: T ProductDetails DetailsView的 ObjectDataSource的检查工作结束后ProductsByCategory DropDownList的值才改变.
要解决这个问题,我们需要在ProductsByCategory DropDownList绑定后显式的重新绑定ProductDetails DetailsView. 我们可以在ProductsByCategory DropDownList的DataBound事件中调用ProductDetails DetailsView的DataBind()方法. 把下面的事件处理代码添加到MasterDetailsDetails.aspx页面的代码后置类:
protected void ProductsByCategory_DataBound(object sender, EventArgs e)
{
ProductDetails.DataBind();
}
添加对ProductDetails DetailsView的DataBind()方法的显式调用后,就一切正常了. 图21高亮显示了该方法是如何解决这个问题的.
图21:在ProductsByCategory DropDownList的DataBound事件中ProductDetails DetailsView被显示的刷新.
DropDownList控件是一个非常理想的用于主/从报表(主记录和从记录存在一对多关系)的用户接口元素(user interface element ). 在前面的教程中我们看到了如何使用单个DropDownList对选中的类别进行产品过滤. 在本教程中我们使用GridView代替了DropDownList做为产品列表, 使用DetailsView显示选中产品的详细信息. 本教程所讨论的概念可以很容易的扩展到包含多个一对多关系的数据模型, 比如客户,订单及订单项. 通常,在一对多的关系中你总是可以使用DropDownList 来表示"主"实体(一对多中的一,主/从关系的主记录)
祝编程愉快!