作者 :斯科特·米切尔
使用 TableAdapter 向导创建类型化数据集时,相应的 DataTable 包含主数据库查询返回的列。 但在某些情况下,DataTable 需要包含其他列。 在本教程中,我们将学习在需要额外的 DataTable 列时推荐使用存储过程的原因。
介绍
将 TableAdapter 添加到类型化数据集时,相应的 DataTable 架构由 TableAdapter 的主查询确定。 例如,如果主查询返回数据字段 A、 B 和 C,则 DataTable 将有三个名为 A、 B 和 C 的对应列。除了主查询之外,TableAdapter 还可以包含其他查询,这些查询可能基于某些参数返回数据的子集。 例如,除了 ProductsTableAdapter
返回有关所有产品的信息的主查询外,它还包含类似 GetProductsByCategoryID(categoryID)
和 GetProductByProductID(productID)
返回基于提供的参数的特定产品信息的方法。
如果所有 TableAdapter 方法返回的数据字段与主查询中指定的数据字段相同或更少,则具有 DataTable 架构的模型反映 TableAdapter 的主查询效果良好。 如果 TableAdapter 方法需要返回其他数据字段,则应相应地扩展 DataTable 架构。 在关于使用包含 Details DataList 的主记录项目符号列表进行 Master/Detail 的教程中,我们向CategoriesTableAdapter
中添加了一种方法,它返回主查询中定义的CategoryID
、CategoryName
、Description
数据字段,以及一个附加的数据字段NumberOfProducts
,该字段显示与每个类别相关的产品数量。 我们手动在 CategoriesDataTable
中添加了一列,以获取此新方法中的 NumberOfProducts
数据字段值。
如“上传文件”教程中所述,对于使用即席 SQL 语句的 TableAdapter,必须格外注意其方法中的数据字段是否与主查询精确匹配。 如果重新运行 TableAdapter 配置向导,它将更新所有 TableAdapter 方法,以便其数据字段列表与主查询匹配。 因此,具有自定义列列表的任何方法都将还原到主查询的列列表,而不返回预期数据。 使用存储过程时,不会出现此问题。
本教程介绍如何扩展 DataTable 架构以包含其他列。 由于使用临时 SQL 语句时 TableAdapter 的脆弱性,本教程将使用存储过程。 有关配置 TableAdapter 以使用存储过程的详细信息,请参阅 “为类型化数据集 s TableAdapters 创建新存储过程 ”教程。
步骤 1:将PriceQuartile
列添加到ProductsDataTable
在 “为类型化数据集 s TableAdapters 创建新存储过程 ”教程中,我们创建了一个名为 NorthwindWithSprocs
Typed DataSet 的数据类型。 此数据集当前包含两个 DataTable: ProductsDataTable
和 EmployeesDataTable
。 以下是 ProductsTableAdapter
的三种方法:
-
GetProducts
- 主查询,该查询返回Products
表中的所有记录 -
GetProductsByCategoryID(categoryID)
- 返回具有指定 categoryID 的所有产品。 -
GetProductByProductID(productID)
- 返回具有指定 productID 的特定产品。
主查询和另外两种方法都返回相同的数据字段集,即表中的所有列 Products
。 没有相关子查询或 JOIN
从 Categories
或 Suppliers
表中拉取相关数据。 因此,在Products
表中,每个字段都有一个相应的ProductsDataTable
列。
在本教程中,我们将在ProductsTableAdapter
中添加一个名为GetProductsWithPriceQuartile
的方法,以返回所有产品。 除了标准产品数据字段外,GetProductsWithPriceQuartile
还将包含一个 PriceQuartile
数据字段,用于指示产品价格所属的四分位数。 例如,价格在最昂贵的 25%% 的产品将具有值 1,而价格在最低 25%% 的产品将具有值 4。 在我们担心创建存储过程以返回此信息之前,首先需要更新ProductsDataTable
,以添加一个列来在使用GetProductsWithPriceQuartile
方法时存储PriceQuartile
结果。
打开 NorthwindWithSprocs
数据集并右键单击 ProductsDataTable
。 从上下文菜单中选择“添加”,然后选择“列”。
图 1:向 ProductsDataTable
添加新列 (单击以查看全尺寸图像)
这将向名为 Column1 类型的 System.String
DataTable 添加新列。 我们需要将此列的名称更新为 PriceQuartile 及其类型 System.Int32
,因为它将用于保存介于 1 到 4 之间的数字。 在 ProductsDataTable
中选择新添加的列,然后在“属性”窗口中,将 Name
属性设置为 PriceQuartile,并将 DataType
属性设置为 System.Int32
。
如图 2 所示,可以设置其他属性,例如列中的值是否必须唯一、列是否为自动递增列、是否允许数据库 NULL
值,等等。 将这些值设置为默认值。
步骤 2:创建GetProductsWithPriceQuartile
方法
现在 ProductsDataTable
已更新为包含 PriceQuartile
列,现在我们可以创建 GetProductsWithPriceQuartile
方法了。 首先右键单击 TableAdapter,然后从上下文菜单中选择“添加查询”。 此时会显示 TableAdapter 查询配置向导,该向导首先提示我们是使用即席 SQL 语句还是新的或现有的存储过程。 由于我们还没有返回价格四分位数数据的存储过程,因此允许 TableAdapter 为我们创建此存储过程。 选择“创建新存储过程”选项,然后单击“下一步”。
图 3:指示 TableAdapter 向导为我们创建存储过程(单击以查看全尺寸图像)
在随后的屏幕中,如图 4 所示,向导会询问要添加的查询类型。
GetProductsWithPriceQuartile
由于该方法将返回表中的所有列和记录Products
,请选择返回行选项的 SELECT,然后单击“下一步”。
图 4:查询将是返回 SELECT
多行的语句(单击可查看全尺寸图像)
接下来,系统会提示进行 SELECT
查询。 在向导中输入以下查询:
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
函数 将结果划分为四个组,其中组由 UnitPrice
按降序排序的值决定。
遗憾的是,查询生成器不知道如何分析 OVER
关键字,并在分析上述查询时显示错误。 因此,在向导中的文本框中直接输入上述查询,而无需使用查询生成器。
注释
有关 NTILE 和 SQL Server 2005 的其他排名函数的详细信息,请参阅 SQL Server 2005 联机丛书中的ROW_NUMBER(Transact-SQL)和排名函数部分。
输入 SELECT
查询并单击“下一步”后,向导会要求我们为其创建的存储过程提供一个名称。 为新存储过程 Products_SelectWithPriceQuartile
命名,然后单击“下一步”。
图 5:命名存储过程 Products_SelectWithPriceQuartile
(单击以查看全尺寸图像)
最后,系统会提示我们命名 TableAdapter 方法。 保持“填充数据表”和“返回数据表”复选框被选中,并将方法命名为FillWithPriceQuartile
和GetProductsWithPriceQuartile
。
图 6:命名 TableAdapter 方法并单击“完成”(单击以查看全尺寸图像)
在指定了SELECT
查询并命名存储过程和 TableAdapter 方法后,单击“完成”以完成向导。 此时,您可能会从向导中收到一个或多个警告,指出 OVER
SQL 构造或语句不受支持。 可以忽略这些警告。
完成向导后,TableAdapter 应包含FillWithPriceQuartile
和GetProductsWithPriceQuartile
方法,数据库应包含名为Products_SelectWithPriceQuartile
的存储过程。 花点时间验证 TableAdapter 确实包含此新方法,并且存储过程已正确添加到数据库。 检查数据库时,如果看不到存储过程,请尝试右键单击“存储过程”文件夹并选择“刷新”。
图 7:验证是否已将新方法添加到 TableAdapter
图 8:确保数据库包含 Products_SelectWithPriceQuartile
存储过程(单击以查看全尺寸图像)
注释
使用存储过程而不是即席 SQL 语句的好处之一是重新运行 TableAdapter 配置向导不会修改存储过程列列表。 右键单击 TableAdapter,从上下文菜单中选择“配置”选项以启动向导,然后单击“完成”以完成该向导,来验证这一点。 接下来,转到数据库并查看 Products_SelectWithPriceQuartile
存储过程。 请注意,其列列表尚未修改。 如果我们使用的是即席 SQL 语句,则重新运行 TableAdapter 配置向导会还原此查询的列列表以匹配主查询列列表,从而从该方法使用的 GetProductsWithPriceQuartile
查询中删除 NTILE 语句。
当调用数据访问层的GetProductsWithPriceQuartile
方法时,TableAdapter会执行Products_SelectWithPriceQuartile
存储过程,并为每个返回的记录在ProductsDataTable
中添加一行。 存储过程返回的数据字段映射到 ProductsDataTable
s 列。 由于存在从存储过程返回的数据字段,因此其值被分配到 s PriceQuartile
列中。
对于查询不返回 PriceQuartile
数据字段的 TableAdapter 方法, PriceQuartile
列值是其 DefaultValue
属性指定的值。 如图 2 所示,此值设置为 DBNull
默认值。 如果想要其他默认值,只需相应地设置属性 DefaultValue
。 只需确保在给定的列 PriceQuartile
中,DefaultValue
值是有效的(即对于 DataType
以及 System.Int32
列来说)。
此时,我们执行了向 DataTable 添加其他列所需的步骤。 若要验证此附加列是否按预期工作,让我们创建一个显示每个产品名称、价格和价格四分位数的 ASP.NET 页面。 不过,在执行之前,我们首先需要更新业务逻辑层,以添加一个调用 DAL 中 GetProductsWithPriceQuartile
方法的方法。 我们将在步骤 3 中更新 BLL,然后在步骤 4 中创建 ASP.NET 页。
步骤 3:增强业务逻辑层
在从表示层使用新 GetProductsWithPriceQuartile
方法之前,首先应向 BLL 添加相应的方法。
ProductsBLLWithSprocs
打开类文件并添加以下代码:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, false)]
public NorthwindWithSprocs.ProductsDataTable GetProductsWithPriceQuartile()
{
return Adapter.GetProductsWithPriceQuartile();
}
与其他数据检索方法一 ProductsBLLWithSprocs
样,该方法 GetProductsWithPriceQuartile
只需调用 DAL 的相应 GetProductsWithPriceQuartile
方法并返回其结果。
步骤 4:在 ASP.NET 网页中显示价格四分位数信息
完成 BLL 添加后,我们便可以创建一个 ASP.NET 页面,其中显示了每个产品的价格四分位数。 打开AdvancedDAL
文件夹中的AddingColumns.aspx
页面,并将 GridView 从工具箱拖到设计器上,将其ID
属性设置为 Products
。 从 GridView 智能标记中,将其绑定到名为 ProductsDataSource
的新 ObjectDataSource。 将 ObjectDataSource 配置为使用 ProductsBLLWithSprocs
类 s GetProductsWithPriceQuartile
方法。 由于这是只读网格,因此请将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为 (None) 。
图 9:将 ObjectDataSource 配置为使用 ProductsBLLWithSprocs
类(单击以查看全尺寸图像)
图 10:从 GetProductsWithPriceQuartile
方法检索产品信息(单击以查看全尺寸图像)
完成“配置数据源”向导后,Visual Studio 会自动为方法返回的每个数据字段在 GridView 中添加一个 BoundField 或 CheckBoxField。 其中一个数据字段是 PriceQuartile
,它标识我们在步骤 1 中添加到 ProductsDataTable
的列。
编辑 GridView 的字段,仅保留ProductName
、UnitPrice
和 PriceQuartile
BoundFields,其余的全部删除。 将 UnitPrice
BoundField 配置为将其值格式化为货币,并将 UnitPrice
BoundField 和 PriceQuartile
BoundField 分别配置为右对齐和居中对齐。 最后,将剩余的 BoundFields 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 显示通过浏览器访问此页面。 请注意,最初,产品按价格降序订购,每个产品都分配了适当的 PriceQuartile
值。 当然,这些数据可以根据其他标准进行排序,同时价格四分位数列的值仍会反映产品在价格方面的排名(见图 12)。
图 11:产品按价格订购(单击以查看全尺寸图像)
图 12:产品按其名称排序(单击以查看全尺寸图像)
注释
通过几行代码,我们可以扩充 GridView,以便根据其值为产品行 PriceQuartile
着色。 我们可能会将第一个四分位的产品着色为浅绿色,第二个四分位的产品着色为浅黄色,依此类推。 我鼓励你花点时间添加此功能。 如果需要对 GridView 进行格式设置的刷新,请参阅 基于数据的自定义格式设置 教程。
替代方法 - 创建另一个 TableAdapter
正如我们在本教程中看到的,当向 TableAdapter 添加一个方法时,该方法返回主查询没有定义的数据字段,我们可以向 DataTable 添加相应的列。 如果 TableAdapter 中的方法较少且返回不同数据字段,并且这些备用数据字段与主查询中的字段差异不大,则这种方法效果很好。
与其向 DataTable 添加列,不如向 DataSet 添加另一个 TableAdapter,该 TableAdapter 包括第一个 TableAdapter 的方法,这些方法返回不同的数据字段。 对于本教程,我们可以添加一个额外的 TableAdapter 到名为ProductsWithPriceQuartileTableAdapter
的 DataSet 中,使用Products_SelectWithPriceQuartile
存储过程作为其主查询,而不是将PriceQuartile
列添加到仅被GetProductsWithPriceQuartile
方法使用的ProductsDataTable
中。 ASP.NET页面需要获取具有价格四分位数的产品信息时,会使用ProductsWithPriceQuartileTableAdapter
,而不需要获取此类信息的页面可以继续使用ProductsTableAdapter
。
通过添加新的 TableAdapter,DataTables 保持原样,其各列精确反映其 TableAdapter 方法返回的数据字段。 但是,额外的 TableAdapters 可能会引入重复的任务和功能。 例如,如果显示PriceQuartile
列的那些 ASP.NET 页也需要提供插入、更新和删除支持,ProductsWithPriceQuartileTableAdapter
则需要正确配置其InsertCommand
UpdateCommand
属性和DeleteCommand
属性。 虽然这些属性会镜像 ProductsTableAdapter
s,但此配置引入了额外的步骤。 此外,现在有两种方法可以通过ProductsTableAdapter
类和ProductsWithPriceQuartileTableAdapter
类来更新、删除产品或向数据库添加产品。
本教程的下载内容包括ProductsWithPriceQuartileTableAdapter
类在NorthwindWithSprocs
DataSet 中,该类演示了这种替代方法。
概要
在大多数情况下,TableAdapter 中的所有方法都将返回相同的数据字段集,但有时特定方法或两种方法可能需要返回其他字段。 例如,在 Master/Detail 中使用带有 Details DataList 的“项目符号主记录列表 ”教程中,除了主查询的数据字段外,我们还向该列表添加了一个方法 CategoriesTableAdapter
,返回了一个 NumberOfProducts
字段,该字段报告了与每个类别关联的产品数。 在本教程中,我们探讨了如何在 ProductsTableAdapter
中添加一种方法,该方法除了主查询的数据字段外还返回一个 PriceQuartile
字段。 若要捕获 TableAdapter 方法返回的其他数据字段,我们需要向 DataTable 添加相应的列。
如果计划手动将列添加到 DataTable,建议 TableAdapter 使用存储过程。 如果 TableAdapter 使用即席 SQL 语句,则每当运行 TableAdapter 配置向导时,所有方法数据字段列表都将还原为主查询返回的数据字段。 此问题不会扩展到存储过程,这就是为什么建议在本教程中使用它们的原因。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams 工作室24小时自学 ASP.NET 2.0》。 可以通过 mitchell@4GuysFromRolla.com 联系他。
特别感谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是兰迪·施密特、杰基·古尔、伯纳黛特·利和希尔顿·吉森诺。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com