添加其他 DataTable 列 (C#)
使用 TableAdapter 向导创建类型化数据集时,相应的 DataTable 包含main数据库查询返回的列。 但在某些情况下,DataTable 需要包含其他列。 在本教程中,我们将了解在需要其他 DataTable 列时为何建议使用存储过程。
简介
将 TableAdapter 添加到类型化数据集时,相应的 DataTable 架构由 TableAdapter main 查询确定。 例如,如果main查询返回数据字段 A、B 和 C,则 DataTable 将具有三个名为 A、B 和 C 的相应列。除了main查询之外,TableAdapter 还可以包含其他查询,这些查询可能基于某个参数返回数据的子集。 例如,除了ProductsTableAdapter
返回有关所有产品的信息的 main 查询外,它还包含 和 GetProductByProductID(productID)
等GetProductsByCategoryID(categoryID)
方法,这些方法基于提供的参数返回特定产品信息。
如果所有 TableAdapter 方法返回的数据字段都与main查询中指定的字段相同或更少,则让 DataTable 架构反映 TableAdapter main 查询的模型效果良好。 如果 TableAdapter 方法需要返回其他数据字段,则应相应地展开 DataTable 的架构。 在母版/详细信息使用带详细信息数据列表的主记录项目符号列表教程中,我们向 CategoriesTableAdapter
添加了一个方法,该方法返回CategoryID
CategoryName
了 main 查询中定义的 、 和数据Description
字段,以及NumberOfProducts
报告与每个类别关联的产品数量的附加数据字段。 我们手动向 CategoriesDataTable
添加了一个新列,以便从此新方法捕获 NumberOfProducts
数据字段值。
如上传文件教程中所述,对于使用即席 SQL 语句的 TableAdapter,并且具有数据字段与main查询不精确匹配的方法,必须格格谨慎。 如果重新运行 TableAdapter 配置向导,它将更新所有 TableAdapter 方法,使其数据字段列表与main查询匹配。 因此,任何具有自定义列列表的方法都将还原到main查询列列表,并且不返回预期的数据。 使用存储过程时不会出现此问题。
本教程介绍如何扩展 DataTable 架构以包含其他列。 由于使用临时 SQL 语句时 TableAdapter 的脆性,本教程将使用存储过程。 有关配置 TableAdapter 以使用存储过程的详细信息,请参阅为 Typed DataSet s TableAdapters 教程创建新的 存储过程。
步骤 1:将PriceQuartile
列添加到ProductsDataTable
在 为类型化数据集创建新的存储过程的 TableAdapters 教程中,我们创建了名为 的类型 NorthwindWithSprocs
化数据集。 此数据集当前包含两个 DataTable: ProductsDataTable
和 EmployeesDataTable
。 有 ProductsTableAdapter
以下三种方法:
GetProducts
- main查询,该查询返回表中的所有记录Products
GetProductsByCategoryID(categoryID)
- 返回具有指定 categoryID 的所有产品。GetProductByProductID(productID)
- 返回具有指定 productID 的特定产品。
main查询和两个附加方法都返回相同的数据字段集,即表中的所有列Products
。 没有相关子查询或 JOIN
从 Categories
或 Suppliers
表拉取相关数据。 因此, 表中 ProductsDataTable
每个字段 Products
都有相应的列。
在本教程中,让我们将 ProductsTableAdapter
一个返回所有产品的 名为 GetProductsWithPriceQuartile
的方法添加到 。 除了标准产品数据字段外, GetProductsWithPriceQuartile
还将包含一个 PriceQuartile
数据字段,用于指示产品价格下降的四分位数。 例如,价格在最昂贵的 25% 的产品的 PriceQuartile
值为 1,而价格低于 25% 的产品的值为 4。 但是,在担心创建存储过程以返回此信息之前,我们首先需要更新 ProductsDataTable
以包含一个列,以在使用 方法时GetProductsWithPriceQuartile
保存PriceQuartile
结果。
打开 NorthwindWithSprocs
数据集并右键单击 ProductsDataTable
。 从上下文菜单中选择“添加”,然后选择“列”。
图 1:将新列添加到 ProductsDataTable
(单击以查看全尺寸图像)
这会向 DataTable 中添加一个名为 Column1 的新列,类型为 System.String
。 我们需要将此列的名称更新为 PriceQuartile 及其类型, System.Int32
因为它将用于保存介于 1 和 4 之间的数字。 选择 中ProductsDataTable
新添加的列,并从属性窗口将 属性设置为 Name
PriceQuartile,将 DataType
属性设置为 System.Int32
。
图 2:设置“新建列 Name
”和 DataType
“属性” (单击以查看全尺寸图像)
如图 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
关键字 (keyword) ,在分析上述查询时将显示错误。 因此,无需使用查询生成器,直接在向导的文本框中输入上述查询。
注意
有关 NTILE 和 2005 SQL Server其他排名函数的详细信息,请参阅 SQL Server 2005 联机丛书中的 ROW_NUMBER (Transact-SQL) 和排名函数部分。
输入 SELECT
查询并单击“下一步”后,向导会要求我们提供它将创建的存储过程的名称。 将新存储过程 Products_SelectWithPriceQuartile
命名为 ,然后单击“下一步”。
图 5:命名存储过程 Products_SelectWithPriceQuartile
(单击以查看全尺寸图像)
最后,系统会提示我们命名 TableAdapter 方法。 选中“填充 DataTable”和“返回 DataTable”复选框,并将方法和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 配置向导会还原此查询的列列表以匹配main查询列列表,从而从 方法使用的GetProductsWithPriceQuartile
查询中删除 NTILE 语句。
调用数据访问层 方法 GetProductsWithPriceQuartile
时,TableAdapter 将执行 Products_SelectWithPriceQuartile
存储过程,并为每个返回的记录添加一行 ProductsDataTable
。 存储过程返回的数据字段映射到 ProductsDataTable
s 列。 由于存储过程返回了一个 PriceQuartile
数据字段,因此其值将 ProductsDataTable
分配给 s PriceQuartile
列。
对于查询不返回 PriceQuartile
数据字段的 TableAdapter 方法, PriceQuartile
列值是其 DefaultValue
属性指定的值。 如图 2 所示,此值设置为 DBNull
默认值。 如果希望使用不同的默认值,只需相应地设置 DefaultValue
属性。 只需确保 DefaultValue
该值在列 DataType
(System.Int32
即列 PriceQuartile
) 有效。
此时,我们已执行将其他列添加到 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 页面,其中显示了每个产品的价格四分位数。 AddingColumns.aspx
打开 文件夹中的页面AdvancedDAL
,并将 GridView 从“工具箱”拖到Designer,并将其 ID
属性设置为 Products
。 从 GridView 的智能标记中,将其绑定到名为 ProductsDataSource
的新 ObjectDataSource。 将 ObjectDataSource 配置为使用 ProductsBLLWithSprocs
类方法 GetProductsWithPriceQuartile
。 由于这是只读网格,因此请将“更新”、“插入”和“删除”选项卡中的下拉列表设置为 (“无”) 。
图 9:将 ObjectDataSource 配置为使用 ProductsBLLWithSprocs
类 (单击以查看全尺寸图像)
图 10:从 GetProductsWithPriceQuartile
方法中检索产品信息 (单击以查看全尺寸图像)
完成“配置数据源”向导后,Visual Studio 会自动将 BoundField 或 CheckBoxField 添加到由 方法返回的每个数据字段的 GridView。 其中一个数据字段是 PriceQuartile
,它是我们在步骤 1 中添加的 ProductsDataTable
列。
编辑 GridView 字段,删除除 、 UnitPrice
和 PriceQuartile
BoundField 之外ProductName
的所有字段。 将 UnitPrice
BoundField 配置为将其值格式化为货币,并分别将 UnitPrice
和 PriceQuartile
BoundFields 右对齐和居中对齐。 最后,将剩余的 BoundFields HeaderText
属性分别更新为 Product、Price 和 Price Ququaile。 此外,检查 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 添加方法以返回除main查询拼写的数据字段以外的其他数据字段时,我们可以向 DataTable 添加相应的列。 但是,仅当 TableAdapter 中存在少量返回不同数据字段的方法,并且这些备用数据字段与main查询之间没有太大差异时,此方法才有效。
可以改为将另一个 TableAdapter 添加到 DataSet,其中包含第一个 TableAdapter 中返回不同数据字段的方法,而不是向 DataTable 添加列。 在本教程中,我们可以将列添加到PriceQuartile
ProductsDataTable
仅由GetProductsWithPriceQuartile
方法) 使用的 (,而是将另一个名为 的 TableAdapter 添加到使用ProductsWithPriceQuartileTableAdapter
Products_SelectWithPriceQuartile
存储过程作为其main查询的数据集。 ASP.NET 需要获取具有价格四分位数的产品信息的页面将使用 ProductsWithPriceQuartileTableAdapter
,而那些没有的页面可以使用 ProductsTableAdapter
。
通过添加新的 TableAdapter,DataTables 将保持不受阻碍,其列精确镜像其 TableAdapter 方法返回的数据字段。 但是,其他 TableAdapter 可能会引入重复的任务和功能。 例如,如果显示 PriceQuartile
列 ASP.NET 页还需要提供插入、更新和删除支持, ProductsWithPriceQuartileTableAdapter
则需要正确配置其 InsertCommand
、 UpdateCommand
和 DeleteCommand
属性。 虽然这些属性镜像 ProductsTableAdapter
,但此配置引入了额外的步骤。 此外,现在有两种方法可以更新、删除产品或将产品添加到数据库 - 通过 ProductsTableAdapter
和 ProductsWithPriceQuartileTableAdapter
类。
本教程的下载内容包括 DataSet 中的NorthwindWithSprocs
一个ProductsWithPriceQuartileTableAdapter
类,该类演示了这种替代方法。
总结
在大多数情况下,TableAdapter 中的所有方法都将返回同一组数据字段,但有时一两个特定方法可能需要返回其他字段。 例如,在 Master/Detail Using a Bulleted List of Master Records with a Details DataList 教程中,我们向 CategoriesTableAdapter
添加了一个方法,该方法除了main查询数据字段外,还返回了一个NumberOfProducts
字段,该字段报告了与每个类别关联的产品数。 在本教程中,我们了解了如何在 中添加ProductsTableAdapter
方法,该方法除了main查询数据字段外,还返回PriceQuartile
了字段。 若要捕获 TableAdapter 方法返回的其他数据字段,需要将相应的列添加到 DataTable。
如果计划手动将列添加到 DataTable,建议 TableAdapter 使用存储过程。 如果 TableAdapter 使用即席 SQL 语句,则每当运行 TableAdapter 配置向导时,所有方法数据字段列表还原main查询返回的数据字段。 此问题不会扩展到存储过程,这就是建议在本教程中使用它们的原因。
编程愉快!
关于作者
Scott Mitchell 是七本 ASP/ASP.NET 书籍的作者, 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可以在 上联系 mitchell@4GuysFromRolla.com他, 也可以通过他的博客联系到他,该博客可在 http://ScottOnWriting.NET中找到。
特别感谢
本教程系列由许多有用的审阅者查看。 本教程的主要审阅者是 Randy Schmidt、Jacky Goor、Bernadette Leigh 和 Hilton Giesenow。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处放置一行 mitchell@4GuysFromRolla.com。