使用計算資料行 (C#)
在建立資料庫表格時,Microsoft SQL Server 可讓您定義一個計算列,其值是根據通常引用相同資料庫記錄中其他值的表達式計算得出的。 這些值在資料庫中是唯讀的,這在使用 TableAdapter 時需要特別考慮。 在本教學課程中,我們學習如何應對計算列帶來的挑戰。
簡介
Microsoft SQL Server 允許計算列,這些列的值是根據表達式計算的,該表達式通常會引用同一表中其他列的值。 例如,時間追蹤資料模型可能有一個以包括 ServicePerformed
、EmployeeID
、Rate
和 Duration
等列命名為 ServiceLog
的表。 雖然可以透過網頁或其他程式介面計算每個服務項目的應付金額 (即費率乘以持續時間),但在名為 AmountDue
報告此資訊的 ServiceLog
表中包含一列可能會很方便。 該列可以建立為普通列,但只要 Rate
或 Duration
列值發生更改,就需要更新它。 更好的方法是使用表達式 Rate * Duration
將 AmountDue
列設為計算列。 這樣做會導致 SQL Server 在查詢中引用 AmountDue
列值時自動計算該列值。
由於計算列的值是由表達式決定的,因此此類別列是唯讀的,因此不能在 INSERT
或 UPDATE
陳述式中為其指派值。 但是,當計算列是使用即席 SQL 陳述式的 TableAdapter 的主查詢的一部分時,它們會自動包含在自動產生的 INSERT
和 UPDATE
陳述式中。 因此,必須更新 TableAdapter INSERT
、UPDATE
查詢 InsertCommand
和 UpdateCommand
屬性以刪除對任何計算列的參考。
將計算列與使用即席 SQL 陳述式的 TableAdapter 結合使用的一項挑戰是,每當 TableAdapter 設定精靈完成時,都會自動重新產生 TableAdapter INSERT
和 UPDATE
查詢。 因此,如果重新執行精靈,手動從查詢中刪除的計算列 INSERT
和 UPDATE
查詢將重新出現。 儘管使用預存程序的 TableAdapter 不會受到這種脆弱性的影響,但它們確實有自己的怪癖,我們將在步驟 3 中解決這些怪癖。
在本教學中,我們將向 Northwind 資料庫中的 Suppliers
表格新增一個計算列,然後建立一個對應的 TableAdapter 來使用該表及其計算列。 我們將讓 TableAdapter 使用預存程序而不是臨時 SQL 陳述式,以便在使用 TableAdapter 設定精靈時我們的自訂設定不會遺失。
讓我們開始吧!
第 1 步:將計算列新增至 Suppliers
表中
Northwind 資料庫沒有任何計算列,因此我們需要自己添加一列。 在本教學課程中,我們將一個計算列新增到名為 Suppliers
的表中,FullContactName
列以以下格式傳回聯絡人的姓名、職位和他們工作的公司:ContactName
(ContactTitle
、CompanyName
)。 當顯示有關供應商的資訊時,可以在報表中使用此計算列。
首先透過右鍵點擊伺服器資源管理器中的 Suppliers
表並從上下文選單中選擇開啟 Suppliers
表定義來開啟表定義。 這將顯示表的列及其屬性,例如它們的資料類別型、是否允許 NULL
等等。 若要新增計算列,請先在表定義中輸入列名稱。 接下來,將其表達式輸入到「列屬性」視窗中「計算列規範」部分下的「(公式)」文字方塊中 (請參閱圖 1)。 命名計算列 FullContactName
並使用下列表達式:
ContactName + ' (' + CASE WHEN ContactTitle IS NOT NULL THEN
ContactTitle + ', ' ELSE '' END + CompanyName + ')'
請注意,可以使用 +
運算子在 SQL 中連接字串。 CASE
陳述式可以像傳統程式語言中的條件一樣使用。 在上面的表達式中,CASE
陳述式可以讀作:如果 ContactTitle
不是,則輸出用逗號連接的值 NULL
,ContactTitle
否則不發出任何內容。 有關該 CASE
陳述式的更多信息,請參閱 SQL CASE
陳述式。
注意
我們可以不在這裡使用 CASE
陳述式,而是使用 ISNULL(ContactTitle, '')
。 如果 checkExpression 為非 NULL,則 ISNULL(checkExpression, replacementValue)
傳回 checkExpression,否則傳回 replacementValue。 雖然 ISNULL
或 CASE
在此情況下可以工作,但在更複雜的情況下,CASE
陳述式的彈性無法與 ISNULL
。
新增此計算列後,您的螢幕應如圖 1 中的螢幕截圖所示。
圖 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
表格並點擊關閉。 接下來,檢查供應商表中的 CompanyName
、ContactName
、ContactTitle
和 FullContactName
欄位。 最後,點擊工具列中的紅色感嘆號圖示來執行查詢並查看結果。
如圖 2 所示,結果包括 FullContactName
,其中使用格式 ldquo;列出 ContactName
、ContactTitle
和 ContactName
列。(ContactTitle
,CompanyName
)。CompanyName
圖 2:FullContactName
使用格式ContactName
(ContactTitle
,CompanyName
) (點擊看大圖)
步驟 3:新增 SuppliersTableAdapter
至資料存取層
為了在我們的應用程式中使用供應商信息,我們需要先在 DAL 中建立一個 TableAdapter 和 DataTable。 理想情況下,這可以使用早期教學課程中介紹的相同簡單步驟來完成。 然而,使用計算列會帶來一些值得討論的問題。
如果您使用的 TableAdapter 使用即席 SQL 陳述式,則只需透過 TableAdapter 設定精靈將計算列包含在 TableAdapter 的主查詢中即可。 但是,這將自動產生包含計算列的 INSERT
和 UPDATE
陳述式。 如果嘗試執行這些方法之一,則會拋出SqlException
訊息 The column ColumnName 無法修改,因為它是計算欄位或是 UNION 運算子的結果。 雖然可以透過 TableAdapter 和屬性手動調整 INSERT
和 UPDATE
陳述式,但只要重新執行 TableAdapter InsertCommand
和 UpdateCommand
設定精靈,這些自訂設定就會遺失。
由於使用即席 SQL 陳述式的 TableAdapter 的脆弱性,建議我們在處理計算列時使用預存程序。 如果您使用現有的預存程序,只需按照使用類別型化資料集的 TableAdapter 的現有預存程序教學課程所述設定 TableAdapter 即可。 但是,如果您讓 TableAdapter 精靈為您建立預存程序,那麼最初從主查詢中省略任何計算列就很重要。 如果您在主查詢中包含計算列,TableAdapter 設定精靈會在完成後通知您它無法建立對應的預存程序。 簡而言之,我們需要先使用無計算列的主查詢來設定 TableAdapter,然後手動更新對應的預存程序和 TableAdapter SelectCommand
以包含計算列。 此方法類似於將 TableAdapter 更新為使用JOIN
教學課程中使用的方法。
在本教學課程中,我們新增一個新的 TableAdapter 並讓它自動為我們建立預存程序。 因此,我們首先需要從主查詢中省略 FullContactName
計算列。
首先開啟 ~/App_Code/DAL
資料夾中的 NorthwindWithSprocs
資料集。 在設計器中右鍵點擊,然後從上下文選單中選擇新增新的 TableAdapter。 這將啟動 TableAdapter 設定精靈。 指定要從中查詢資料的資料庫 (從 Web.config
到 NORTHWNDConnectionString
),然後按一下下一步。 由於我們還沒有建立任何用於查詢或修改 Suppliers
表的預存程序,因此選擇「建立新的預存程序」選項,以便精靈將為我們建立它們,然後按一下「下一步」。
圖 3:選擇「建立新預存程序」選項 (點選查看大圖)
後續步驟提示我們輸入主查詢。 輸入以下查詢,查詢傳回每個供應商的 SupplierID
、CompanyName
、ContactName
和 ContactTitle
欄位。 請注意,此查詢故意省略了計算列 (FullContactName
);我們將更新相應的預存程序以在步驟 4 中包含此列。
SELECT SupplierID, CompanyName, ContactName, ContactTitle
FROM Suppliers
輸入主查詢並按一下「下一步」後,精靈允許我們命名它將產生的四個預存程序。 將這些預存程序命名為 Suppliers_Select
、Suppliers_Insert
、Suppliers_Update
和 Suppliers_Delete
,如圖 4 所示。
圖 4:自訂自動產生的預存程序的名稱 (按一下查看大圖)
精靈的下一個步驟允許我們命名 TableAdapter 的方法並指定用於存取和更新資料的模式。 選取所有三個複選框,但將 GetData
方法重新命名為 GetSuppliers
。 按一下 [完成] 以完成程序。
圖 5:將 GetData
方法重新命名為 GetSuppliers
(按一下查看全尺寸影像)
按一下「完成」後,精靈將建立四個預存程序並將 TableAdapter 和對應的 DataTable 新增至類別型化資料集中。
步驟 4:在 TableAdapter 的主查詢中包含計算列
我們現在需要更新在步驟 3 中建立的 TableAdapter 和 DataTable 以包含 FullContactName
計算列。 這包含兩個步驟:
- 更新
Suppliers_Select
預存程序以傳回運算列,以及FullContactName
- 更新資料表以包含相應的
FullContactName
列。
首先導航到伺服器資源管理器並深入到預存程序資料夾。 開啟 Suppliers_Select
預存程序並更新 SELECT
查詢以包含 FullContactName
計算列:
SELECT SupplierID, CompanyName, ContactName, ContactTitle, FullContactName
FROM Suppliers
透過點選工具列中的「儲存」圖示、按 Ctrl+S 或從「檔案」選單中選擇儲存 Suppliers_Select
選項,儲存對預存程序的變更。
接下來,返回資料集設計器,右鍵點擊 SuppliersTableAdapter
,然後從上下文選單中選擇“設定”。 請注意,Suppliers_Select
列現在包含在其資料 FullContactName
列集合中。
圖 6:執行 TableAdapter 的設定精靈來更新 DataTable 的列 (按一下點選查看大圖)
按一下 [完成] 以完成程序。 這將自動將相應的列新增至 SuppliersDataTable
。 TableAdapter 精靈足夠智能,可以偵測到 FullContactName
列是計算列,因此是唯讀的。 因此,它將列的 ReadOnly
屬性設為 true
。 若要驗證這一點,請從 SuppliersDataTable
中選擇該列,然後前往「屬性」視窗 (請參閱圖 7)。 請注意,FullContactName
列 DataType
和 MaxLength
屬性也會相應設定。
圖 7:FullContactName
列被標記為唯讀 (點擊查看全尺寸影像)
步驟 5: 為 TableAdapter 新增 GetSupplierBySupplierID
方法
在本教學課程中,我們將建立一個 ASP.NET 頁面,該頁面在可更新的網格中顯示供應商。 在過去的教學課程中,我們更新了業務邏輯層中的單一記錄,方法是從 DAL 中擷取該特定記錄作為強類別型 DataTable,更新其屬性,然後將更新後的 DataTable 傳送回 DAL 以將變更傳播到資料庫。 為了完成第一步 - 從 DAL 擷取正在更新的記錄 - 我們需要先向 DAL 添加一個 GetSupplierBySupplierID(supplierID)
方法。
右鍵點擊資料集設計中的 SuppliersTableAdapter
,然後從上下文選單中選擇新增查詢選項。 正如我們在步驟 3 中所做的那樣,透過選擇「建立新的預存程序」選項,讓精靈為我們產生新的預存程序 (請參閱圖 3 以取得此精靈步驟的螢幕截圖)。 由於此方法將傳回包含多列的記錄,因此指示我們要使用 SQL 查詢 (即傳回行的 SELECT),然後按一下「下一步」。
圖 8:選擇返回行的 SELECT 選項 (按一下查看大圖)
後續步驟提示我們輸入用於此方法的查詢。 輸入以下內容,這將傳回與主查詢相同的資料欄位,但針對的是特定供應商。
SELECT SupplierID, CompanyName, ContactName, ContactTitle, FullContactName
FROM Suppliers
WHERE SupplierID = @SupplierID
下一個畫面要求我們命名將自動產生的預存程序。 命名該預存程序 Suppliers_SelectBySupplierID
並按一下下一步。
圖 9:命名預存程序 Suppliers_SelectBySupplierID
(點選查看大圖)
最後,精靈提示我們輸入用於 TableAdapter 的資料存取模式和方法名稱。 選取兩個複選框,但將 FillBy
和 GetDataBy
方法分別重新命名為 FillBySupplierID
和 GetSupplierBySupplierID
。
圖 10:命名 TableAdapter 方法 FillBySupplierID
並 GetSupplierBySupplierID
(點選查看全尺寸影像)
按一下 [完成] 以完成程序。
第 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
方法:GetSuppliers
和 UpdateSupplier
。 此 GetSuppliers
方法呼叫並 SuppliersDataTable
傳回資料存取層中對應 GetSupplier
方法傳回的值。 UpdateSupplier
方法透過呼叫 DAL GetSupplierBySupplierID(supplierID)
方法擷取有關正在更新的特定供應商的資訊。 然後,它更新 CategoryName
、ContactName
和 ContactTitle
屬性,並透過呼叫資料存取層的 Update
方法並傳入修改後的 SuppliersRow
物件,將這些變更提交到資料庫。
注意
除了 SupplierID
和 CompanyName
之外,供應商表中的所有欄位都允許 NULL
值。 因此,如果傳入 contactName
或 contactTitle
參數,我們需要分別使用 null
和 ContactName
方法將對應的 ContactTitle
和 NULL
屬性設為資料庫值 SetContactNameNull
SetContactTitleNull
。
步驟 7:使用表示層的計算列
將計算列加入 Suppliers
資料表中並相應更新 DAL 和 BLL 後,我們就可以建立一個與 FullContactName
計算列一起使用的 ASP.NET 頁面。 首先打開 AdvancedDAL
資料夾中的 ComputedColumns.aspx
頁面,然後將 GridView 從工具箱拖曳到設計器上。 將 GridView 的 ID
屬性設為 Suppliers
,並從其智慧標記將其綁定到名為 的新 ObjectDataSource SuppliersDataSource
。 設定 ObjectDataSource 以使用我們在步驟 6 中新增的 SuppliersBLLWithSprocs
類別,然後按一下「下一步」。
圖 11:設定 ObjectDataSource 以使用該 SuppliersBLLWithSprocs
類別 (按一下檢視全尺寸影像)
在 SuppliersBLLWithSprocs
類別中只定義了兩個方法:GetSuppliers
和 UpdateSupplier
。 確保分別在 SELECT 和 UPDATE 標籤中指定這兩種方法,然後按一下 Finish 完成 ObjectDataSource 的設定。
完成資料來源設定精靈後,Visual Studio 將為每個傳回的資料欄位新增一個 BoundField。 刪除 BoundField 並將 SupplierID
、HeaderText
、CompanyName
和 BoundFields 的屬性分別變更為 Company、Contact Name、Title 和 Full Contact Name。ContactName
ContactTitle
FullContactName
在智慧標記中,選取「啟用編輯」複選框以開啟 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
(ContactTitle
,CompanyName
)。
圖 12:每個供應商都列在網格中 (點擊查看大圖)
點選特定供應商的「編輯」按鈕會導致回發,並在其編輯介面中呈現該行 (請參閱圖 13)。 前三列在其預設編輯介面中呈現 - 一個 TextBox 控件,Text
屬性設定為資料欄位的值。 但是,FullContactName
列仍保留為文字。 當資料來源設定精靈完成時將 FullContactName
BoundFieldReadOnly
新增至 GridView 時,true
BoundFieldFullContactName
SuppliersDataTable
ReadOnly
屬性設為 true
,因為對應列的屬性設為 。 如步驟 4 所述,FullContactName
屬性設定為 ReadOnly
,因為 TableAdapter 偵測到該欄位是計算列 true
。
圖 13:該列不可編輯 FullContactName
(按一下查看大圖)
繼續更新一個或多個可編輯列的值,然後按一下「更新」。 請注意 FullContactName
值如何自動更新以反映變更。
注意
GridView 目前使用 BoundFields 作為可編輯欄位,從而形成預設的編輯介面。 由於 CompanyName
欄位是必填欄位,因此應將其轉換為包含RequiredFieldValidator 的TemplateField。 我將其作為練習留給有興趣的讀者。 有關將 BoundField 轉換為 TemplateField 以及新增驗證控制項的逐步說明,請參閱在編輯和插入介面中新增驗證控制項教學課程。
摘要
定義表的架構時,Microsoft SQL Server 允許包含計算列。 這些列的值是根據表達式計算的,該表達式通常會引用同一記錄中其他列的值。 由於計算列的值是基於表達式,因此它們是唯讀的,不能在 INSERT
或 UPDATE
陳述式中賦值。 當在 TableAdapter 的主查詢中使用計算列並嘗試自動產生對應的 INSERT
、UPDATE
和 DELETE
陳述式時,這會帶來挑戰。
在本教學課程中,我們討論了規避計算列帶來的挑戰的技術。 特別是,我們在 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。