• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    在ASP.NET 2.0中操作数据之六十九:处理Computed Columns列

    导言:

      Microsoft SQL Server里有一种computed columns列.这种列的值是通过一个表达式来计算,而表达式引用的是同一张表的其它列的值.打个比方,有一张ServiceLog表,其包含了ServicePerformed, EmployeeID, Rate, Duration等列. 虽然我们可以在一个web页面或其它什么界面里计算每笔服务的费用(也就是 比率 rate乘以时间段duration),不过我们也可以手动向ServiceLog表添加一个 AmountDue列以反映该信息.我们可以将该列创建为一个普通列,只是任何时候当Rate 或 Duration列的值发生改变时需要更新AmountDue列的值.一个比较好的办法是将AmountDue创建成一个computed column 列,其使用的表达式为 Rate * Duration. 这样,当在一个查询里引用该列时SQL Server就可以自动的计算AmountDue列的值.

      由于computed column列的值是由表达式决定的,所以这种列是只读的,并且不能在INSERT 或 UPDATE statements里对其赋值.然而,对使用ad-hoc SQL statements的TableAdapter来说,如果主查询里引用了computed column列,那么自动生成的INSERT 和 UPDATE statements也会自动的引用computed column列.所以,我们必须更新TableAdapter的 INSERT 和 UPDATE 查询,以及InsertCommand 和 UpdateCommand属性,以删除对任何computed column列的引用.

      如果在使用 ad-hoc SQL statements的TableAdapter里使用computed columns的话,我们要面临的挑战之一便是,每当完成TableAdapte设置向导时,TableAdapter的 INSERT 和 UPDATE查询都会自动的生成,又再一次的自动引用computed column列.不过如果TableAdapters使用存储过程的话,就不会出现这个问题.

      在本文,我们将向Northwind数据库的Suppliers表添加一个computed column列,然后相应地创建一个TableAdapter来处理该表以及该computed column列.我们将在TableAdapter里使用存储过程而不是ad-hoc SQL statements.

    第一步:向Suppliers表添加一个Computed Column

      在本文,我们将向Suppliers表添加一个名为FullContactName的computed column列,它以“ContactName (ContactTitle, CompanyName)”的格式返回contact的name, title,以及所在的公司.

      打开服务器资源管理器,在Suppliers表上单击右键,选“Open Table Definition”,这将会显示出表所包含的列以及列的属性,比如数据类型、是否允许为NULL值等等.要添加一个computed column列,只需在表定义里键入表的名称,接下来在Column属性窗口的Computed Column Specification部分的(Formula)文本框里输入表达式(如图1所示)。将该computed column列命名为FullContactName,并使用下面的表达式:

    ContactName + ' (' + CASE WHEN ContactTitle IS NOT NULL THEN
     ContactTitle + ', ' ELSE '' END + CompanyName + ')'

      请注意,在SQL里可以用操作符“+” 来连接字符串。而CASE声明类似于传统编程语言里的条件语句。上面代码里的CASE 声明可以这样来理解:如果ContactTitle 不为NULL,那么输出ContactTitle值,再紧接一个逗号;如果为NULL,则无操作。关于CASE 声明的更多信息请参阅文章《The Power of SQL CASE Statements》(http://www.4guysfromrolla.com/webtech/102704-1.shtml)

      注意:除了CASE声明外,我们还可以使用ISNULL(ContactTitle, '')。语法ISNULL(checkExpression, replacementValue) returns是这样工作的,如果checkExpression 不为NULL,则对其进行返回;如果为NULL则返回replacementValue.虽然本文这2种语法都可以使用,但是在一些稍微复杂点的情况下,使用ISNULL的情况要多一些.添加完computed column列后,你的屏幕看起来应该和图1差不多:


    图1:向Suppliers表添加一个名为FullContactName的Computed Column列

      添加完后点工具栏上的Save图标,或按Ctrl+S键,又或者在File菜单里选“保存Suppliers”.“保存”操作会自动地刷新服务器资源管理器,将刚刚添加的的列展现在Suppliers表里.此外,键入到(Formula)文本框的表达式会自动的进行调整,剔除不必要的空白,将列名用[]括起来,并使用圆括号()来显示操作的先后顺序:

    (((([ContactName]+' (')+case when [ContactTitle] IS NOT NULL
     then [ContactTitle]+', ' else '' end)+[CompanyName])+')')

      关于Microsoft SQL Server里computed columns列的更多信息请参考文章《technical documentation》(http://msdn2.microsoft.com/en-us/library/ms191250.aspx);同时你也可以参考文章《How to: Specify Computed Columns》(http://msdn2.microsoft.com/en-us/library/ms188300.aspx),看如何一步步地创建computed columns列.

      注意:默认情况下,数据库表并没有“实际”(physically)的包含computed columns列,而是每次在一个查询里引用它时重新计算其值.不过,我们可以选择“Is Persisted”选项来让SQL Server实实在在的在数据库表里创建computed columns列.这样的话我们可以为computed column列创建一个索引,当在一个查询的WHERE字句里使用computed column列的值时就可以提高执行效率.更多的信息请参阅文章《Creating Indexes on Computed Columns》(http://msdn2.microsoft.com/en-us/library/ms189292.aspx)

    第二步:查看Computed Column列的值

      在处理数据访问层前,让我们花点时间查看FullContactName列的值.在服务器资源管理器里,在Suppliers表上右键单击,选择“New Query”,这将启动一个查询窗口提示我们在查询里包含哪个表.添加Suppliers表,再点“Close”.接下来从Suppliers表里选择CompanyName, ContactName, ContactTitle,以及FullContactName列.最后,点击工具栏上的红色感叹号图标执行查询,查看结果.如图2所示,结果里包含了FullContactName列,它以ContactName (ContactTitle, CompanyName)”的格式使用了CompanyName, ContactName,ContactTitle这3列.


    图2:FullContactName列的格式为“ContactName (ContactTitle, CompanyName)”

    第三步:在数据访问层添加一个SuppliersTableAdapter

      为了在我们的应用程序里处理supplier信息,我们首先需要在DAL层创建一个TableAdapter 和 DataTable.我们可以用前面的教程探讨的方法来进行创建,稍微不同的是我们将要与computed columns列打交道.

      如果你用ad-hoc SQL statements来构造一个TableAdapter的话,你可以很简单的通过TableAdapter设置向导在TableAdapter的主查询里引用computed column列,这样,在自动生成的INSERT 和 UPDATE statements就会引用computed column列。如果你执行这2个方法的话,将会抛出这样的一个SqlException:“The column ‘ColumnName' cannot be modified because it is either a computed column or is the result of a UNION operator”.虽然我们可以在InsertCommand和UpdateCommand属性里手工改动INSERT 和 UPDATE statement,但是一旦重新运行TableAdapter设置向导后,我们所做的用户定制就会丢失掉.

      由于使用ad-hoc SQL statements的TableAdapters的这种不稳定性,我们倾向于使用存储过程来处理computed columns列.如果你使用的是现有的存储过程的话,你可以参阅第66章《在TableAdapters中使用现有的存储过程》那样来配置TableAdapter.如果你使用TableAdapter设置向导来创建存储过程的话,很重要的一点是最开始,你不要在主查询里引用computed columns列,如果你在主查询里引用了computed columns列的话,你刚完成设置,向导就会提示你不能创建相应的存储过程.简而言之,在设置TableAdapter时,最开始不要在主查询里引用computed column列,接下来再对相应的存储过程和TableAdapter的SelectCommand属性进行更改以引用computed column列.这种方法我们在第67章《在TableAdapters中使用JOINs》里探讨过.

      本文我们将新添加一个TableAdapter并自动创建存储过程.当然我们要在主查询里忽略这个名为FullContactName的computed column列.打开~/App_Code/DAL文件夹里的NorthwindWithSprocs DataSet数据集,在设计器里右键单击,选“add a new TableAdapter”,这将开启TableAdapter设置向导,指定数据库连接信息(也就Web.config文件里的NORTHWNDConnectionString),点Next。选“Create new stored procedures”项,再点Next.


    图3:选择“Create new stored procedures”项

      接下来我们要指定主查询,键入如下的查询,其返回每个supplier的SupplierID, CompanyName, ContactName, ContactTitle列。注意,我们有意忽略了computed column列(即FullContactName列)。不过我们将在第四步更新该存储过程以引用该列:

    SELECT SupplierID, CompanyName, ContactName, ContactTitle
    FROM Suppliers

      输入完主查询后点Next,向导要我们为将要创建的4个存储过程命名,分别命名为Suppliers_Select, Suppliers_Insert, Suppliers_Update,以及 Suppliers_Delete。如图4所示:


    图4:对自动生成的存储过程命名

      接下来要我们为TableAdapter的方法命名并指定用于访问和更新数据的模式.我们全部选中这3项,不过将GetData方法重命名为GetSuppliers.点击Finish完成配置.


    图5:将GetData方法重命名为GetSuppliers

      完成后向导将创建这4个存储过程,并向类型化的DataSet添加ableAdapter以及对应的DataTable.

    第四步:在TableAdapter的主查询里引用Computed Column列

    接下来我们将对第三步创建的TableAdapter 和 DataTable进行更新以引用FullContactName列,这要经过2个步骤:

    1.更新名为Suppliers_Select的存储过程以返回FullContactName列

    2.更新DataTable以包含相应的FullContactName列

      首先在服务器资源管理器里打开存储过程文件夹,打开Suppliers_Select存储过程,更新其SELECT查询以引用FullContactName列:

    SELECT SupplierID, CompanyName, ContactName, ContactTitle, FullContactName
    FROM Suppliers

      保存所做的修改.接下来返回到DataSet Designer,在SuppliersTableAdapter上右键单击,选“Configure”.我们可以注意到Suppliers_Select里的Data Columns集里已经包含了FullContactName列.


    图6:返回到TableAdapter的设置向导更新DataTable的列

      点击Finish完成设置,这将自动地为SuppliersDataTable添加相应的列.TableAdapter发觉FullContactName列是一个computed column列,且是只读的.因此将设置该列的ReadOnly属性为true.我们可以进行验证:在SuppliersDataTable里选择该列,打开其属性窗口(如图7),我们注意到FullContactName列的DataType 和 MaxLength属性都作了相应的设置.


    图7:FullContactName列标记为Read-Only

    第五步:向TableAdapter添加一个GetSupplierBySupplierID方法

      在本文我们将在一个具有更新功能的ASP.NET页面里展示suppliers信息.在前面的文章里,我们从DAL获取指定的记录并将其作为一个强类型的DataTable返回给BLL以做更新,然后将更新后的DataTable再传递给DAL,对数据库做相应的改动.为此,第一步——从DAL返回要更新的记录——我们需要向DAL层添加一个名为GetSupplierBySupplierID(supplierID)的方法.

      在DataSet Design设计器里右键单击SuppliersTableAdapter,选“Add Query” ,再选“Create new stored procedure”(可参考3图)。再选“SELECT which returns rows”再点Next.


    图8:选“SELECT which returns rows”项

      接下来为该方法指定查询,键入如下的代码,它将检索某个具体的supplier返回的列与主查询一样.

    SELECT SupplierID, CompanyName, ContactName, ContactTitle, FullContactName
    FROM Suppliers
    WHERE SupplierID = @SupplierID

      接下来我们将该存储过程命名为Suppliers_SelectBySupplierID,点Next.


    图9:将存储过程命名为Suppliers_SelectBySupplierID

      在接下来的界面,全部选中图里的2项,并将FillBy 和 GetDataBy方法分别命名为FillBySupplierID 和 GetSupplierBySupplierID.


    图10:将TableAdapter的方法命名为FillBySupplierID 和 GetSupplierBySupplierID

      点Finish完成向导

    第六步:创建业务逻辑层Business Logic Layer

      在创建ASP.NET页面前,我们首先要在BLL添加相应的方法.我们将在第7步创建页面,其允许我们查看并编辑suppliers.因此我们在BLL至少要包含2个方法,一个获取所有的suppliers,一个用于更新某个具体的supplier.

    在~/App_Code/BLL文件夹里创建一个名为SuppliersBLLWithSprocs的新类,添加代码如下:

    using System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    using NorthwindWithSprocsTableAdapters;
    
    [System.ComponentModel.DataObject]
    public class SuppliersBLLWithSprocs
    {
     private SuppliersTableAdapter _suppliersAdapter = null;
     protected SuppliersTableAdapter Adapter
     {
     get
     {
      if (_suppliersAdapter == null)
      _suppliersAdapter = new SuppliersTableAdapter();
    
      return _suppliersAdapter;
     }
     }
    
     [System.ComponentModel.DataObjectMethodAttribute
     (System.ComponentModel.DataObjectMethodType.Select, true)]
     public NorthwindWithSprocs.SuppliersDataTable GetSuppliers()
     {
     return Adapter.GetSuppliers();
     }
    
     [System.ComponentModel.DataObjectMethodAttribute
     (System.ComponentModel.DataObjectMethodType.Update, true)]
     public bool UpdateSupplier(string companyName, string contactName,
     string contactTitle, int supplierID)
     {
     NorthwindWithSprocs.SuppliersDataTable suppliers =
      Adapter.GetSupplierBySupplierID(supplierID);
     if (suppliers.Count == 0)
      // no matching record found, return false
      return false;
    
     NorthwindWithSprocs.SuppliersRow supplier = suppliers[0];
    
     supplier.CompanyName = companyName;
     if (contactName == null)
      supplier.SetContactNameNull();
     else
      supplier.ContactName = contactName;
     if (contactTitle == null)
      supplier.SetContactTitleNull();
     else
      supplier.ContactTitle = contactTitle;
    
     // Update the product record
     int rowsAffected = Adapter.Update(supplier);
    
     // Return true if precisely one row was updated, otherwise false
     return rowsAffected == 1;
     }
    }
    
    

      和其它的BLL class类一样,SuppliersBLLWithSprocs有一个protected Adapter属性,2个public方法:GetSuppliers 和 UpdateSupplier.其中,GetSuppliers方法调用Data Access Layer层对应的GetSupplier方法,该方法将SuppliersDataTable返回给BLL层;而UpdateSupplier方法通过调用DAL层的GetSupplierBySupplierID(supplierID)方法来获取某人具体supplier的信息,然后更新其CategoryName, ContactName,ContactTitle属性,再将修改后的SuppliersRow对象传递给Data Access Layer层的 Update方法,以对数据库做相应的更新.

      注意:除了SupplierID 和 CompanyName外,Suppliers表的所有列都允许为NULL值,所以如果传递的contactName 或 contactTitle参数为null的话,我们将分别调用SetContactNameNull 和 SetContactTitleNull方法来将ContactName 和 ContactTitle 属性设置为NULL.

    第七步: 在表现层处理Computed Column列

      做完了所有的必要工作后, 我们将创建一个ASP.NET页面来处理FullContactName列, 打开AdvancedDAL文件夹里的ComputedColumns.aspx 页面,拖一个GridView控件到页面,设其ID为Suppliers,在其智能标签里绑定到一个名为SuppliersDataSource的ObjectDataSource控件,设置其调用SuppliersBLLWithSprocs类,点Next.


    图11:设置ObjectDataSource调用SuppliersBLLWithSprocs Class类

      在SuppliersBLLWithSprocs类里只有2个方法GetSuppliers 和 UpdateSupplier.确保在SELECT 和 UPDATE标签里分别选中这2个方法,点Finish完成设置.完成设置后,Visual Studio将添加相应的BoundField,移除SupplierID列,并将CompanyName, ContactName, ContactTitle,和FullContactName列的HeaderText属性分别设置为“Company”, “Contact Name”, “Title”,“Full Contact Name”,再启用GridView的编辑功能.

      Visual Studio将ObjectDataSource控件的OldValuesParameterFormatString属性设置为“original_{0}”. 我们要将其改为默认值“{0}”.如此这般,GridView 和 ObjectDataSource控件的声明代码看起来和下面的差不多:

    asp:GridView ID="Suppliers" runat="server" AutoGenerateColumns="False"
     DataKeyNames="SupplierID" DataSourceID="SuppliersDataSource">
     Columns>
     asp:CommandField ShowEditButton="True" />
     asp:BoundField DataField="CompanyName"
      HeaderText="Company"
      SortExpression="CompanyName" />
     asp:BoundField DataField="ContactName"
      HeaderText="Contact Name"
      SortExpression="ContactName" />
     asp:BoundField DataField="ContactTitle"
      HeaderText="Title"
      SortExpression="ContactTitle" />
     asp:BoundField DataField="FullContactName"
      HeaderText="Full Contact Name"
      SortExpression="FullContactName"
      ReadOnly="True" />
     /Columns>
    /asp:GridView>
    
    asp:ObjectDataSource ID="SuppliersDataSource" runat="server"
     SelectMethod="GetSuppliers" TypeName="SuppliersBLLWithSprocs"
     UpdateMethod="UpdateSupplier">
     UpdateParameters>
     asp:Parameter Name="companyName" Type="String" />
     asp:Parameter Name="contactName" Type="String" />
     asp:Parameter Name="contactTitle" Type="String" />
     asp:Parameter Name="supplierID" Type="Int32" />
     /UpdateParameters>
    /asp:ObjectDataSource>
    
    

      接下来我们在浏览器里登录该页面,如图12所示。每行都有一个FullContactName列,格式为“ContactName (ContactTitle, CompanyName)”.


    图12:每行展示一个Supplier

      点击某行的Edit按钮将导致页面回传,且该行显示为一个编辑界面(如图13),头3行呈现为默认的编辑界面——一个TextBox控件,且其Text属性为该数据域(data field)的值.不过FullContactName列仍然呈现为一个文本框.在Data Source设置向导完成并向GridView控件添加完相应的BoundFields时,FullContactName BoundField的ReadOnly属性为true。我们在第四步注意到,FullContactName列的ReadOnly属性为true,因为TableAdapter意识到该列为一个computed column列.


    图13:FullContactName列为只读

      我们改动这3个列中至少一个列的值,点Update按钮.我们发现FullContactName列的值跟着发生改变.

      注意:由于GridView当前用的是BoundFields,导致编辑时用的是默认的界面.又由于CompanyName列是必需的,我们应将其转化成一个TemplateField以包含一个RequiredFieldValidator控件.我将此作为一个练习留给读者,你可以参考第19章《给编辑和新增界面增加验证控件》,看如何一步步的将BoundField转换成 TemplateField,再添加一个确认控件.

    结语:

      当创建一个表时,Microsoft SQL Server允许我们创建一个computed columns列.这些computed columns列引用该条记录的其它列,再通过一个表达式对其赋值.由于其值来源于一个表达式,因此这种列是只读的,且不能通过INSERT 或 UPDATE statement对其赋值.正是如此,当在一个TableAdapter的主查询里引用computed column的话,要想自动的生成对应的INSERT, UPDATE,和DELETE statements有点麻烦.

      在本文,我们探讨了使用computed columns列面临的挑战.具体来说,由于使用ad-hoc SQL statements的TableAdapters自身固有的不稳定性,我们探讨了使用存储过程的情况.当使用TableAdapter向导创建一个新的存储过程的时候,很重要的一点是,最开始不要在主查询里引用任何的computed columns,不然就不能自动的生成对应的存储过程.完成向导后,我们要手动修改SelectCommand属性以引用computed columns列.

      祝编程快乐!

    作者简介

      本系列教程作者 Scott Mitchell,著有六本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用 微软Web技术。大家可以点击查看全部教程《[翻译]Scott Mitchell 的ASP.NET 2.0数据教程》,希望对大家的学习ASP.NET有所帮助。

    您可能感兴趣的文章:
    • DevExpress GridControl实现根据RowIndex和VisibleColumnsIndex来获取单元格值
    • 如何让easyui gridview 宽度自适应窗口改变及fitColumns应用
    • ERROR 1222 (21000): The used SELECT statements have a different number of columns
    • 深入浅析MySQL COLUMNS分区
    上一篇:在ASP.NET 2.0中操作数据之六十八:为DataTable添加额外的列
    下一篇:在ASP.NET 2.0中操作数据之七十:配置数据库连接和命令等级设置
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    在ASP.NET 2.0中操作数据之六十九:处理Computed Columns列 在,ASP.NET,2.0,中,操作,数据,