共用方式為


使用計算資料行 (C#)

作者:Scott Mitchell

下載 PDF

在建立資料庫表格時,Microsoft SQL Server 可讓您定義一個計算列,其值是根據通常引用相同資料庫記錄中其他值的表達式計算得出的。 這些值在資料庫中是唯讀的,這在使用 TableAdapter 時需要特別考慮。 在本教學課程中,我們學習如何應對計算列帶來的挑戰。

簡介

Microsoft SQL Server 允許計算列,這些列的值是根據表達式計算的,該表達式通常會引用同一表中其他列的值。 例如,時間追蹤資料模型可能有一個以包括 ServicePerformedEmployeeIDRateDuration 等列命名為 ServiceLog 的表。 雖然可以透過網頁或其他程式介面計算每個服務項目的應付金額 (即費率乘以持續時間),但在名為 AmountDue 報告此資訊的 ServiceLog 表中包含一列可能會很方便。 該列可以建立為普通列,但只要 RateDuration 列值發生更改,就需要更新它。 更好的方法是使用表達式 Rate * DurationAmountDue 列設為計算列。 這樣做會導致 SQL Server 在查詢中引用 AmountDue 列值時自動計算該列值。

由於計算列的值是由表達式決定的,因此此類別列是唯讀的,因此不能在 INSERTUPDATE 陳述式中為其指派值。 但是,當計算列是使用即席 SQL 陳述式的 TableAdapter 的主查詢的一部分時,它們會自動包含在自動產生的 INSERTUPDATE 陳述式中。 因此,必須更新 TableAdapter INSERTUPDATE 查詢 InsertCommandUpdateCommand 屬性以刪除對任何計算列的參考。

將計算列與使用即席 SQL 陳述式的 TableAdapter 結合使用的一項挑戰是,每當 TableAdapter 設定精靈完成時,都會自動重新產生 TableAdapter INSERTUPDATE 查詢。 因此,如果重新執行精靈,手動從查詢中刪除的計算列 INSERTUPDATE 查詢將重新出現。 儘管使用預存程序的 TableAdapter 不會受到這種脆弱性的影響,但它們確實有自己的怪癖,我們將在步驟 3 中解決這些怪癖。

在本教學中,我們將向 Northwind 資料庫中的 Suppliers 表格新增一個計算列,然後建立一個對應的 TableAdapter 來使用該表及其計算列。 我們將讓 TableAdapter 使用預存程序而不是臨時 SQL 陳述式,以便在使用 TableAdapter 設定精靈時我們的自訂設定不會遺失。

讓我們開始吧!

第 1 步:將計算列新增至 Suppliers 表中

Northwind 資料庫沒有任何計算列,因此我們需要自己添加一列。 在本教學課程中,我們將一個計算列新增到名為 Suppliers 的表中,FullContactName 列以以下格式傳回聯絡人的姓名、職位和他們工作的公司:ContactName (ContactTitleCompanyName)。 當顯示有關供應商的資訊時,可以在報表中使用此計算列。

首先透過右鍵點擊伺服器資源管理器中的 Suppliers 表並從上下文選單中選擇開啟 Suppliers 表定義來開啟表定義。 這將顯示表的列及其屬性,例如它們的資料類別型、是否允許 NULL 等等。 若要新增計算列,請先在表定義中輸入列名稱。 接下來,將其表達式輸入到「列屬性」視窗中「計算列規範」部分下的「(公式)」文字方塊中 (請參閱圖 1)。 命名計算列 FullContactName 並使用下列表達式:

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

請注意,可以使用 + 運算子在 SQL 中連接字串。 CASE 陳述式可以像傳統程式語言中的條件一樣使用。 在上面的表達式中,CASE 陳述式可以讀作:如果 ContactTitle 不是,則輸出用逗號連接的值 NULLContactTitle 否則不發出任何內容。 有關該 CASE 陳述式的更多信息,請參閱 SQL CASE陳述式

注意

我們可以不在這裡使用 CASE 陳述式,而是使用 ISNULL(ContactTitle, '')。 如果 checkExpression 為非 NULL,則 ISNULL(checkExpression, replacementValue) 傳回 checkExpression,否則傳回 replacementValue。 雖然 ISNULLCASE 在此情況下可以工作,但在更複雜的情況下,CASE 陳述式的彈性無法與 ISNULL

新增此計算列後,您的螢幕應如圖 1 中的螢幕截圖所示。

將名為 FullContactName 的計算列新增至供應商表

圖 1:新增一個名為 FullContactName Suppliers 表格的計算列 (按一下查看全尺寸影像)

命名計算列並輸入其表達式後,透過點擊工具列中的儲存圖示、按 Ctrl+S 或前往檔案選單並選擇儲存 Suppliers,將變更儲存到表中。

保存表應該會重新整理伺服器資源管理器,包括 Suppliers 表的列清單中剛剛新增的列。 此外,輸入到 (公式) 文字方塊中的表達式將自動調整為等效表達式,該表達式會去除不必要的空格,用方括號 ([]) 包圍列名稱,並包含括號以更明確地顯示運算順序:

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

有關 Microsoft SQL Server 中計算列的更多信息,請參閱技術文件。 另請參閱如何:指定計算列,以了解建立計算列的逐步演練。

注意

預設情況下,計算列並不是實體儲存在表中,而是每次在查詢中引用時都會重新計算。 但是,透過勾選 Is Persisted 複選框,您可以指示 SQL Server 將計算列實體儲存在表中。 這樣做允許在計算列上建立索引,這可以提高在 WHERE 子句中使用計算列值的查詢的效能。 有關詳細信息,請參閱在計算列上建立索引

步驟 2:查看計算列的值

在開始資料存取層的工作之前,讓我們花一點時間查看這些 FullContactName 值。 在伺服器資源管理器中,以滑鼠右鍵按一下 Suppliers 資料表名稱並從上下文功能表中選擇「新查詢」。 這將打開一個查詢窗口,提示我們選擇要在查詢中包含哪些表。 新增 Suppliers 表格並點擊關閉。 接下來,檢查供應商表中的 CompanyNameContactNameContactTitleFullContactName 欄位。 最後,點擊工具列中的紅色感嘆號圖示來執行查詢並查看結果。

如圖 2 所示,結果包括 FullContactName,其中使用格式 ldquo;列出 ContactNameContactTitleContactName 列。(ContactTitleCompanyName)。CompanyName

FullContactName 使用格式 ContactName (ContactTitle, CompanyName)

圖 2FullContactName使用格式ContactName (ContactTitleCompanyName) (點擊看大圖)

步驟 3:新增 SuppliersTableAdapter 至資料存取層

為了在我們的應用程式中使用供應商信息,我們需要先在 DAL 中建立一個 TableAdapter 和 DataTable。 理想情況下,這可以使用早期教學課程中介紹的相同簡單步驟來完成。 然而,使用計算列會帶來一些值得討論的問題。

如果您使用的 TableAdapter 使用即席 SQL 陳述式,則只需透過 TableAdapter 設定精靈將計算列包含在 TableAdapter 的主查詢中即可。 但是,這將自動產生包含計算列的 INSERTUPDATE 陳述式。 如果嘗試執行這些方法之一,則會拋出SqlException訊息 The column ColumnName 無法修改,因為它是計算欄位或是 UNION 運算子的結果。 雖然可以透過 TableAdapter 和屬性手動調整 INSERTUPDATE 陳述式,但只要重新執行 TableAdapter InsertCommandUpdateCommand 設定精靈,這些自訂設定就會遺失。

由於使用即席 SQL 陳述式的 TableAdapter 的脆弱性,建議我們在處理計算列時使用預存程序。 如果您使用現有的預存程序,只需按照使用類別型化資料集的 TableAdapter 的現有預存程序教學課程所述設定 TableAdapter 即可。 但是,如果您讓 TableAdapter 精靈為您建立預存程序,那麼最初從主查詢中省略任何計算列就很重要。 如果您在主查詢中包含計算列,TableAdapter 設定精靈會在完成後通知您它無法建立對應的預存程序。 簡而言之,我們需要先使用無計算列的主查詢來設定 TableAdapter,然後手動更新對應的預存程序和 TableAdapter SelectCommand 以包含計算列。 此方法類似於將 TableAdapter 更新為使用JOIN教學課程中使用的方法。

在本教學課程中,我們新增一個新的 TableAdapter 並讓它自動為我們建立預存程序。 因此,我們首先需要從主查詢中省略 FullContactName 計算列。

首先開啟 ~/App_Code/DAL 資料夾中的 NorthwindWithSprocs 資料集。 在設計器中右鍵點擊,然後從上下文選單中選擇新增新的 TableAdapter。 這將啟動 TableAdapter 設定精靈。 指定要從中查詢資料的資料庫 (從 Web.configNORTHWNDConnectionString),然後按一下下一步。 由於我們還沒有建立任何用於查詢或修改 Suppliers 表的預存程序,因此選擇「建立新的預存程序」選項,以便精靈將為我們建立它們,然後按一下「下一步」。

選擇建立新的預存程序選項

圖 3:選擇「建立新預存程序」選項 (點選查看大圖)

後續步驟提示我們輸入主查詢。 輸入以下查詢,查詢傳回每個供應商的 SupplierIDCompanyNameContactNameContactTitle 欄位。 請注意,此查詢故意省略了計算列 (FullContactName);我們將更新相應的預存程序以在步驟 4 中包含此列。

SELECT SupplierID, CompanyName, ContactName, ContactTitle
FROM Suppliers

輸入主查詢並按一下「下一步」後,精靈允許我們命名它將產生的四個預存程序。 將這些預存程序命名為 Suppliers_SelectSuppliers_InsertSuppliers_UpdateSuppliers_Delete,如圖 4 所示。

自訂自動產生的預存程序的名稱

圖 4:自訂自動產生的預存程序的名稱 (按一下查看大圖)

精靈的下一個步驟允許我們命名 TableAdapter 的方法並指定用於存取和更新資料的模式。 選取所有三個複選框,但將 GetData 方法重新命名為 GetSuppliers。 按一下 [完成] 以完成程序。

將 GetData 方法重新命名為 GetSuppliers

圖 5:將 GetData 方法重新命名為 GetSuppliers (按一下查看全尺寸影像)

按一下「完成」後,精靈將建立四個預存程序並將 TableAdapter 和對應的 DataTable 新增至類別型化資料集中。

步驟 4:在 TableAdapter 的主查詢中包含計算列

我們現在需要更新在步驟 3 中建立的 TableAdapter 和 DataTable 以包含 FullContactName 計算列。 這包含兩個步驟:

  1. 更新 Suppliers_Select 預存程序以傳回運算列,以及 FullContactName
  2. 更新資料表以包含相應的 FullContactName 列。

首先導航到伺服器資源管理器並深入到預存程序資料夾。 開啟 Suppliers_Select 預存程序並更新 SELECT 查詢以包含 FullContactName 計算列:

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

透過點選工具列中的「儲存」圖示、按 Ctrl+S 或從「檔案」選單中選擇儲存 Suppliers_Select 選項,儲存對預存程序的變更。

接下來,返回資料集設計器,右鍵點擊 SuppliersTableAdapter,然後從上下文選單中選擇“設定”。 請注意,Suppliers_Select 列現在包含在其資料 FullContactName 列集合中。

執行 TableAdapter 的設定精靈來更新 DataTable 的列

圖 6:執行 TableAdapter 的設定精靈來更新 DataTable 的列 (按一下點選查看大圖)

按一下 [完成] 以完成程序。 這將自動將相應的列新增至 SuppliersDataTable。 TableAdapter 精靈足夠智能,可以偵測到 FullContactName 列是計算列,因此是唯讀的。 因此,它將列的 ReadOnly 屬性設為 true。 若要驗證這一點,請從 SuppliersDataTable 中選擇該列,然後前往「屬性」視窗 (請參閱圖 7)。 請注意,FullContactNameDataTypeMaxLength 屬性也會相應設定。

FullContactName 欄位標記為唯讀

圖 7FullContactName 列被標記為唯讀 (點擊查看全尺寸影像)

步驟 5: 為 TableAdapter 新增 GetSupplierBySupplierID 方法

在本教學課程中,我們將建立一個 ASP.NET 頁面,該頁面在可更新的網格中顯示供應商。 在過去的教學課程中,我們更新了業務邏輯層中的單一記錄,方法是從 DAL 中擷取該特定記錄作為強類別型 DataTable,更新其屬性,然後將更新後的 DataTable 傳送回 DAL 以將變更傳播到資料庫。 為了完成第一步 - 從 DAL 擷取正在更新的記錄 - 我們需要先向 DAL 添加一個 GetSupplierBySupplierID(supplierID) 方法。

右鍵點擊資料集設計中的 SuppliersTableAdapter,然後從上下文選單中選擇新增查詢選項。 正如我們在步驟 3 中所做的那樣,透過選擇「建立新的預存程序」選項,讓精靈為我們產生新的預存程序 (請參閱圖 3 以取得此精靈步驟的螢幕截圖)。 由於此方法將傳回包含多列的記錄,因此指示我們要使用 SQL 查詢 (即傳回行的 SELECT),然後按一下「下一步」。

選擇返回行的 SELECT 選項

圖 8:選擇返回行的 SELECT 選項 (按一下查看大圖)

後續步驟提示我們輸入用於此方法的查詢。 輸入以下內容,這將傳回與主查詢相同的資料欄位,但針對的是特定供應商。

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

下一個畫面要求我們命名將自動產生的預存程序。 命名該預存程序 Suppliers_SelectBySupplierID 並按一下下一步。

將預存程序命名為 Suppliers_SelectBySupplierID

圖 9:命名預存程序 Suppliers_SelectBySupplierID(點選查看大圖)

最後,精靈提示我們輸入用於 TableAdapter 的資料存取模式和方法名稱。 選取兩個複選框,但將 FillByGetDataBy 方法分別重新命名為 FillBySupplierIDGetSupplierBySupplierID

將 TableAdapter 方法命名為 FillBySupplierID 和 GetSupplierBySupplierID

圖 10:命名 TableAdapter 方法 FillBySupplierIDGetSupplierBySupplierID (點選查看全尺寸影像)

按一下 [完成] 以完成程序。

第 6 步:建立業務邏輯層

在建立使用步驟 1 中建立的計算列的 ASP.NET 頁面之前,我們首先需要在 BLL 中新增對應的方法。 我們將在步驟 7 中建立的 ASP.NET 頁面將允許使用者檢視和編輯供應商。 因此,我們需要 BLL 至少提供一種獲取所有供應商的方法和另一種更新特定供應商的方法。

~/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 類別一樣,SuppliersBLLWithSprocs 有一個 protected 傳回 Adapter 類別 SuppliersTableAdapter 實例的屬性以及兩個 public 方法:GetSuppliersUpdateSupplier。 此 GetSuppliers 方法呼叫並 SuppliersDataTable 傳回資料存取層中對應 GetSupplier 方法傳回的值。 UpdateSupplier 方法透過呼叫 DAL GetSupplierBySupplierID(supplierID) 方法擷取有關正在更新的特定供應商的資訊。 然後,它更新 CategoryNameContactNameContactTitle 屬性,並透過呼叫資料存取層的 Update 方法並傳入修改後的 SuppliersRow 物件,將這些變更提交到資料庫。

注意

除了 SupplierIDCompanyName 之外,供應商表中的所有欄位都允許 NULL 值。 因此,如果傳入 contactNamecontactTitle 參數,我們需要分別使用 nullContactName 方法將對應的 ContactTitleNULL 屬性設為資料庫值 SetContactNameNullSetContactTitleNull

步驟 7:使用表示層的計算列

將計算列加入 Suppliers 資料表中並相應更新 DAL 和 BLL 後,我們就可以建立一個與 FullContactName 計算列一起使用的 ASP.NET 頁面。 首先打開 AdvancedDAL 資料夾中的 ComputedColumns.aspx 頁面,然後將 GridView 從工具箱拖曳到設計器上。 將 GridView 的 ID 屬性設為 Suppliers,並從其智慧標記將其綁定到名為 的新 ObjectDataSource SuppliersDataSource。 設定 ObjectDataSource 以使用我們在步驟 6 中新增的 SuppliersBLLWithSprocs 類別,然後按一下「下一步」。

設定 ObjectDataSource 以使用 SuppliersBLLWithSprocs 類別

圖 11:設定 ObjectDataSource 以使用該 SuppliersBLLWithSprocs 類別 (按一下檢視全尺寸影像)

SuppliersBLLWithSprocs 類別中只定義了兩個方法:GetSuppliersUpdateSupplier。 確保分別在 SELECT 和 UPDATE 標籤中指定這兩種方法,然後按一下 Finish 完成 ObjectDataSource 的設定。

完成資料來源設定精靈後,Visual Studio 將為每個傳回的資料欄位新增一個 BoundField。 刪除 BoundField 並將 SupplierIDHeaderTextCompanyName 和 BoundFields 的屬性分別變更為 Company、Contact Name、Title 和 Full Contact Name。ContactNameContactTitleFullContactName 在智慧標記中,選取「啟用編輯」複選框以開啟 GridView 的內建編輯功能。

除了將 BoundFields 新增至 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 (ContactTitleCompanyName)。

每個供應商都列在網格中

圖 12:每個供應商都列在網格中 (點擊查看大圖)

點選特定供應商的「編輯」按鈕會導致回發,並在其編輯介面中呈現該行 (請參閱圖 13)。 前三列在其預設編輯介面中呈現 - 一個 TextBox 控件,Text 屬性設定為資料欄位的值。 但是,FullContactName 列仍保留為文字。 當資料來源設定精靈完成時將 FullContactNameBoundFieldReadOnly 新增至 GridView 時,trueBoundFieldFullContactName SuppliersDataTable ReadOnly 屬性設為 true,因為對應列的屬性設為 。 如步驟 4 所述,FullContactName 屬性設定為 ReadOnly,因為 TableAdapter 偵測到該欄位是計算列 true

FullContactName 欄位不可編輯

圖 13:該列不可編輯 FullContactName (按一下查看大圖)

繼續更新一個或多個可編輯列的值,然後按一下「更新」。 請注意 FullContactName 值如何自動更新以反映變更。

注意

GridView 目前使用 BoundFields 作為可編輯欄位,從而形成預設的編輯介面。 由於 CompanyName 欄位是必填欄位,因此應將其轉換為包含RequiredFieldValidator 的TemplateField。 我將其作為練習留給有興趣的讀者。 有關將 BoundField 轉換為 TemplateField 以及新增驗證控制項的逐步說明,請參閱在編輯和插入介面中新增驗證控制項教學課程。

摘要

定義表的架構時,Microsoft SQL Server 允許包含計算列。 這些列的值是根據表達式計算的,該表達式通常會引用同一記錄中其他列的值。 由於計算列的值是基於表達式,因此它們是唯讀的,不能在 INSERTUPDATE 陳述式中賦值。 當在 TableAdapter 的主查詢中使用計算列並嘗試自動產生對應的 INSERTUPDATEDELETE 陳述式時,這會帶來挑戰。

在本教學課程中,我們討論了規避計算列帶來的挑戰的技術。 特別是,我們在 TableAdapter 中使用預存程序來克服使用即席 SQL 陳述式的 TableAdapter 固有的脆弱性。 當讓 TableAdapter 精靈建立新的預存程序時,重要的是我們讓主查詢最初忽略任何計算列,因為它們的存在會阻止產生資料修改預存程序。 最初設定 TableAdapter 後,可以重新調整 SelectCommand 預存程序以包含任何計算列。

快樂程式!

關於作者

Scott Mitchell 是七本 ASP/ASP.NET 書籍的作者和 4GuysFromRolla.com 的創始人,自 1998 年以來一直致力於 Microsoft Web 技術。 史考特是一名獨立顧問、培訓師和作家。 他的最新著作是 Sams Teach Yourself ASP.NET 2.0 in 24 Hours。 您可以撥打 mitchell@4GuysFromRolla.com 聯絡他。或者透過他的部落格, http://ScottOnWriting.NET部落格可以在以下位置找到。

特別感謝

本教學系列得到了許多有用的審閱者的審閱。 本教學的主要審閱者是 Hilton Geisenow 和 Teresa Murphy。 有興趣查看我即將發表的 MSDN 文章嗎? 如果是這樣,請留言給我 mitchell@4GuysFromRolla.com