添加其他 DataTable 列 (VB)

作者 :斯科特·米切尔

下载 PDF

使用 TableAdapter 向导创建类型化数据集时,相应的 DataTable 包含主数据库查询返回的列。 但在某些情况下,DataTable 需要包含其他列。 在本教程中,我们了解为什么在需要额外的数据表列时推荐使用存储过程。

介绍

将 TableAdapter 添加到类型化数据集时,相应的 DataTable 架构由 TableAdapter 的主查询确定。 例如,如果主查询返回数据字段 ABC,则 DataTable 将有三个名为 ABC 的对应列。除了主查询之外,TableAdapter 还可以包含其他查询,这些查询可能基于某些参数返回数据的子集。 例如,除了 ProductsTableAdapter 返回有关所有产品的信息的主查询外,它还包含类似 GetProductsByCategoryID(categoryID)GetProductByProductID(productID)返回基于提供的参数的特定产品信息的方法。

如果 DataTable 架构反映 TableAdapter 的主查询,且所有 TableAdapter 方法返回的数据字段与主查询中的数据字段相同或更少,那么这种模型的效果会很好。 如果 TableAdapter 方法需要返回其他数据字段,则应相应地扩展 DataTable 架构。 在使用项目符号列出主记录的 Master/Detail with Details DataList教程中,我们在CategoriesTableAdapter中添加了一种方法,该方法返回主查询中定义的CategoryIDCategoryNameDescription数据字段,以及一个额外的数据字段NumberOfProducts,用于报告每个类别所关联产品的数量。 我们手动向 CategoriesDataTable 添加了一个新列,以便从此新方法中捕获 NumberOfProducts 数据字段的值。

如在“上传文件”教程中所讨论的,使用即席 SQL 语句的 TableAdapters 时必须非常小心,尤其是当其方法的数据字段与主查询不完全匹配时。 如果重新运行 TableAdapter 配置向导,它将更新所有 TableAdapter 方法,以便其数据字段列表与主查询匹配。 因此,具有自定义列列表的任何方法都将还原到主查询的列列表,而不返回预期数据。 使用存储过程时,不会出现此问题。

本教程介绍如何扩展 DataTable 架构以包含其他列。 由于使用临时 SQL 语句时 TableAdapter 的脆弱性,本教程将使用存储过程。 有关配置 TableAdapter 使用存储过程的详细信息,请参阅 为 Typed DataSet 的 TableAdapters 创建新存储过程使用 Typed DataSet 的 TableAdapters 的现有存储过程 教程。

步骤 1:将PriceQuartile列添加到ProductsDataTable

“为类型化数据集 s TableAdapters 创建新存储过程 ”教程中,我们创建了一个名为 NorthwindWithSprocsTyped DataSet 的数据类型。 此数据集当前包含两个 DataTable: ProductsDataTableEmployeesDataTableProductsTableAdapter 有以下三种方法:

  • GetProducts - 主查询,该查询返回 Products 表中的所有记录
  • GetProductsByCategoryID(categoryID) - 返回具有指定 categoryID 的所有产品。
  • GetProductByProductID(productID) - 返回具有指定 productID 的特定产品

主查询和另外两种方法都返回相同的数据字段集,即表中的所有列 Products 。 没有相关子查询或 JOINCategoriesSuppliers 表中拉取相关数据。 因此,Products 表中每个字段都有相应的 ProductsDataTable 列。

在本教程中,让我们向ProductsTableAdapter添加一个名为GetProductsWithPriceQuartile的方法,该方法返回所有的产品。 除了标准产品数据字段之外,GetProductsWithPriceQuartile 还将包含一个 PriceQuartile 数据字段,用于指示产品价格属于哪个四分位数。 例如,价格属于最昂贵的 25%% 的产品将会具有值 1,而那些价格处于最低 25%% 的产品将会具有值 4。 在创建存储过程返回此信息之前,我们首先需要更新ProductsDataTable以包含一个列,该列在使用GetProductsWithPriceQuartile方法时保存PriceQuartile结果。

打开 NorthwindWithSprocs 数据集并右键单击 ProductsDataTable。 从右键菜单中点选“添加”,然后选择“栏目”。

向 ProductsDataTable 添加新列

图 1:向 ProductsDataTable 添加新列(单击以查看全尺寸图像

这将向名为 Column1 类型的 System.StringDataTable 添加新列。 我们需要将此列的名称更新为 PriceQuartile 及其类型 System.Int32 ,因为它将用于保存介于 1 到 4 之间的数字。 选择 ProductsDataTable 中的新添加列,然后在“属性”窗口中将 Name 属性设置为 PriceQuartile,并将 DataType 属性设置为 System.Int32

设置新列的名称和 DataType 属性

图 2:设置新列的NameDataType 属性(单击以查看全尺寸图像

如图 2 所示,可以设置额外的属性,例如,列中的值是否必须唯一、列是否为自动递增列、数据库NULL值是否被允许,以及其他类似的特性。 将这些值设置为默认值。

步骤 2:创建GetProductsWithPriceQuartile方法

现在已经将 ProductsDataTable 更新为包含 PriceQuartile 列,因此我们可以创建 GetProductsWithPriceQuartile 方法了。 首先右键单击 TableAdapter,然后从上下文菜单中选择“添加查询”。 此时会显示 TableAdapter 查询配置向导,该向导首先提示我们是使用即席 SQL 语句还是新的或现有的存储过程。 由于我们还没有返回价格四分位数数据的存储过程,因此允许 TableAdapter 为我们创建此存储过程。 选择“创建新存储过程”选项,然后单击“下一步”。

指示 TableAdapter 向导为我们创建一个存储过程

图 3:指示 TableAdapter 向导为我们创建存储过程(单击以查看全尺寸图像

在随后的屏幕中,如图 4 所示,向导会询问要添加的查询类型。 GetProductsWithPriceQuartile由于该方法将返回表中的所有列和记录Products,请选择返回行选项的 SELECT,然后单击“下一步”。

查询将是返回多行的 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 命名,然后单击“下一步”。

将存储过程命名为Products_SelectWithPriceQuartile

图 5:命名存储过程 Products_SelectWithPriceQuartile单击以查看全尺寸图像

最后,系统会提示我们命名 TableAdapter 方法。 请保持勾选“填充数据表”和“返回数据表”复选框,并将方法分别命名为FillWithPriceQuartileGetProductsWithPriceQuartile

将 TableAdapter 的方法命名为并单击“完成”

图 6:命名 TableAdapter 方法并单击“完成”(单击以查看全尺寸图像

在指定 SELECT 查询并命名存储过程和 TableAdapter 方法后,单击“完成”以完成向导。 此时,可能会从向导中收到一到两个警告,指出 SQL 构造或语句 OVER 不被支持。 可以忽略这些警告。

完成向导后,TableAdapter 应包含 FillWithPriceQuartileGetProductsWithPriceQuartile 方法,数据库应包含名为 Products_SelectWithPriceQuartile 的存储过程。 花点时间验证 TableAdapter 确实包含此新方法,并且存储过程已正确添加到数据库。 检查数据库时,如果看不到存储过程,请尝试右键单击“存储过程”文件夹并选择“刷新”。

验证是否已将新方法添加到 TableAdapter

图 7:验证是否已将新方法添加到 TableAdapter

确保数据库包含 Products_SelectWithPriceQuartile 存储过程

图 8:确保数据库包含 Products_SelectWithPriceQuartile 存储过程(单击以查看全尺寸图像

注释

使用存储过程而不是即席 SQL 语句的好处之一是重新运行 TableAdapter 配置向导不会修改存储过程列列表。 右键单击 TableAdapter,从上下文菜单中选择“配置”选项以启动向导,然后单击“完成”以完成该向导,来验证这一点。 接下来,转到数据库并查看 Products_SelectWithPriceQuartile 存储过程。 请注意,其列列表尚未修改。 如果我们使用的是即席 SQL 语句,则重新运行 TableAdapter 配置向导会还原此查询的列列表以匹配主查询列列表,从而从该方法使用的 GetProductsWithPriceQuartile 查询中删除 NTILE 语句。

调用数据访问层的方法 GetProductsWithPriceQuartile 时,TableAdapter 将执行 Products_SelectWithPriceQuartile 存储过程,并且对于每个返回的记录向 ProductsDataTable 添加一行。 存储过程返回的数据字段映射到 ProductsDataTable s 列。 由于存在从存储过程返回的数据字段,因此其值将分配给ProductsDataTable s PriceQuartile列。

对于查询不返回 PriceQuartile 数据字段的 TableAdapter 方法, PriceQuartile 列值是其 DefaultValue 属性指定的值。 如图 2 所示,此值设置为 DBNull默认值。 如果想要其他默认值,只需相应地设置属性 DefaultValue 。 只需确保对于给定的DefaultValue列,DataType值在PriceQuartile列中是有效的(即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 Function GetProductsWithPriceQuartile() As NorthwindWithSprocs.ProductsDataTable
    Return Adapter.GetProductsWithPriceQuartile()
End Function

与其他数据检索方法一 ProductsBLLWithSprocs样,该方法 GetProductsWithPriceQuartile 只需调用 DAL 的相应 GetProductsWithPriceQuartile 方法并返回其结果。

步骤 4:在 ASP.NET 网页中显示价格四分位数信息

完成 BLL 添加后,我们便可以创建一个 ASP.NET 页面,其中显示了每个产品的价格四分位数。 打开AddingColumns.aspx页面在AdvancedDAL文件夹中,并将 GridView 从工具箱拖到设计器上,将其ID属性设置为 Products。 从 GridView 智能标记中,将其绑定到名为 ProductsDataSource 的新 ObjectDataSource。 将 ObjectDataSource 配置为使用 ProductsBLLWithSprocs 类 s GetProductsWithPriceQuartile 方法。 由于这是只读网格,因此请将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为 (None) 。

将 ObjectDataSource 配置为使用 ProductsBLLWithSprocs 类

图 9:将 ObjectDataSource 配置为使用 ProductsBLLWithSprocs 类(单击以查看全尺寸图像

从 GetProductsWithPriceQuartile 方法检索产品信息

图 10:从 GetProductsWithPriceQuartile 方法检索产品信息(单击以查看全尺寸图像

完成“配置数据源”向导后,Visual Studio 会自动将每个由方法返回的数据字段添加为 GridView 中的 BoundField 或 CheckBoxField。 这些数据字段之一是PriceQuartile,就是我们在步骤 1 中添加到 ProductsDataTable 的列。

编辑 GridView 的字段,只保留ProductNameUnitPricePriceQuartile的 BoundFields,删除其他所有字段。 将 UnitPrice BoundField 配置为将其值格式化为货币,并将UnitPricePriceQuartile BoundFields分别配置为右对齐和居中对齐。 最后,将剩余的 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 中返回不同数据字段的方法数量很少,并且这些备用数据字段与主要查询的数据字段差别不大的话,那么这种方法效果很好。

可以选择将另外一个 TableAdapter 添加到包含第一个 TableAdapter 返回不同数据字段的方法的 DataSet,而不是向 DataTable 添加列。 对于本教程,我们不应将PriceQuartile列添加到ProductsDataTable(因为它仅在GetProductsWithPriceQuartile方法中使用),而是可以将一个额外的 TableAdapter 添加到名为ProductsWithPriceQuartileTableAdapter的数据集,并使用Products_SelectWithPriceQuartile存储过程作为其主查询。 ASP.NET需要获得具有价格四分位数的产品信息的页面将使用ProductsWithPriceQuartileTableAdapter,而不需要价格信息的页面可以继续使用ProductsTableAdapter

通过添加新的 TableAdapter,DataTable 保持不被影响,其列精确反映了其 TableAdapter 方法返回的数据字段。 但是,其他 TableAdapters 可能会引入重复的任务和功能。 例如,如果显示PriceQuartile列的那些 ASP.NET 页也需要提供插入、更新和删除支持,ProductsWithPriceQuartileTableAdapter则需要正确配置其InsertCommandUpdateCommand属性和DeleteCommand属性。 虽然这些属性会镜像 ProductsTableAdapter s,但此配置引入了额外的步骤。 此外,现在有两种方法可以通过ProductsTableAdapterProductsWithPriceQuartileTableAdapter类更新、删除或向数据库添加产品。

本教程的下载内容包括一个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自学ASP.NET 2.0的24小时教程》。 可以通过 mitchell@4GuysFromRolla.com 联系他。

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是兰迪·施密特、杰基·古尔、伯纳黛特·利和希尔顿·吉森诺。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请联系我。mitchell@4GuysFromRolla.com