設計數據層組件並在層間傳遞數據

目錄

簡介

在設計分佈式應用程序時需要確定如何訪問和表示與該應用程序相關聯的業務數據。本文提供一些指導原則以幫助您選擇公開數據、保持數據和在應用程序的層間傳遞數據的最佳方式。

圖 1 所示爲分佈式應用程序中的常見層。本文區分業務數據與使用這些數據的業務過程,並且僅在需要明確說明時討論業務過程層。同樣,本文僅在直接涉及數據表示方式(例如 Microsoft® ASP.NET Web 頁面公開業務數據的方式)時討論表示層。圖 1 中使用了兩個新術語:數據訪問邏輯組件和業務實體組件。本文後面將解釋這些術語。

圖 1:分佈式應用程序中數據的訪問與表示

多數應用程序將數據存儲在關係數據庫中。除此之外還有其他數據存儲方式,但本文重點討論 .NET 應用程序與關係數據庫交互的方式,而並不專門討論它如何與平面文件、非關係數據庫等其他數據存儲中的數據進行交互。

本文明確區分保持邏輯與數據本身。將保持邏輯與數據區分開來的原因如下:

  • 獨立的數據保持組件可以將應用程序與數據源名稱、連接信息、字段名等數據庫相關內容隔離開。
  • 現在的許多應用程序都採用 XML Web services、Microsoft 消息隊列(亦稱 MSMQ)等鬆散耦合的、基於消息的技術。這些應用程序通常通過傳遞業務文檔而不是傳遞對象進行通信。
    注意:有關 XML Web services 的介紹,請參閱 MSDN® Magazine 2002 年 3 月號中的文章 .NET Web Services: Web Methods Make it Easy to Publish Your App's Interface over the Internet。有關消息隊列的詳細信息,請參閱“Message Queuing Overview”。

爲區分保持邏輯與數據本身,本文提出了兩種不同的組件類型。

  • 數據訪問邏輯組件。數據訪問邏輯組件從數據庫中檢索數據並把實體數據保存回數據庫中。數據訪問邏輯組件還包含實現數據相關操作所需的所有業務邏輯。
  • 業務實體組件。數據用來表示產品、訂單等現實世界中的業務實體。在應用程序中表示這種業務實體的方法非常多,例如 XML、DataSet、面向對象的自定義類等,這取決於應用程序的物理和邏輯設計限制。本文後面將詳細討論各種設計方案。

數據訪問邏輯組件

數據訪問邏輯組件代表調用程序提供對數據庫執行以下任務的方法:

  • 在數據庫中創建記錄
  • 讀取數據庫中的記錄並把業務實體數據返回給調用程序
  • 使用調用程序提供的修改後的業務實體數據更新數據庫中的記錄
  • 刪除數據庫中的記錄

執行上述任務的方法通常稱爲“CRUD”方法,這是由各項任務的首字母組成的一個縮寫詞。

數據訪問邏輯組件還提供對數據庫實現業務邏輯的方法。例如,數據訪問邏輯組件可能包含一個查找目錄中本月銷售額最高的產品的方法。

通常,數據訪問邏輯組件訪問一個單一數據庫,並封裝了針對該數據庫中一個表或一組相關表的數據相關操作。例如,可以定義一個數據訪問邏輯組件來處理數據庫中的 Customer 表和 Address 表,同時定義另一個數據訪問邏輯組件來處理 Orders 表和 OrderDetails 表。本文後面將討論將數據訪問邏輯組件映射到數據庫表的設計決策。

表示業務實體

每個數據訪問邏輯組件都處理一種特定類型的業務實體。例如,Customer 數據訪問邏輯組件處理 Customer 業務實體。表示業務實體的方式很多,這取決於諸如以下因素:

  • 是否需要把業務實體數據與 Microsoft Windows® 窗體或 ASP.NET 頁面中的控件綁定在一起?
  • 是否需要對業務實體數據執行排序或搜索操作?
  • 應用程序是每次處理一個業務實體,還是通常處理一組業務實體?
  • 是本地部署還是遠程部署應用程序?
  • XML Web services 是否使用該業務實體?
  • 性能、可縮放性、可維護性、編程方便性等非功能性要求的重要程度如何?

本文將概述以下實現選項的優缺點:

  • XML。使用 XML 字符串或 XML 文檔對象模型 (DOM) 對象來表示業務實體數據。XML 是一種開放而靈活的數據表示格式,可用於集成各種類型的應用程序。
  • DataSet。DataSet 是緩存在內存中的表,它是從關係數據庫或 XML 文檔中獲得的。數據訪問邏輯組件可以使用 DataSet 來表示從數據庫中檢索到的業務實體數據,您可以在應用程序中使用該 DataSet。有關 DataSet 的介紹,請參閱 .NET Data Access Architecture Guide 中的“Introducing ADO.NET”。
  • 有類型的 DataSet。有類型的 DataSet 是從 ADO.NET DataSet 類繼承而來的類,它爲訪問表和 DataSet 中的列提供了具有嚴格類型的方法、事件和屬性。
  • 業務實體組件。這是一種自定義類,用於表示各種業務實體類型。您可以定義保存業務實體數據的字段,並定義將此數據向客戶端應用程序公開的屬性,然後使用在該類中定義的字段來定義方法以封裝簡單的業務邏輯。此選項並不通過 CRUD 方法實現與基礎數據訪問邏輯組件的數據傳遞,而是通過客戶端應用程序直接與數據訪問邏輯組件進行通信以執行 CRUD 操作。
  • 帶有 CRUD 行爲的業務實體組件。按上述方法定義一個自定義實體類,並實現調用與此業務實體相關聯的基礎數據訪問邏輯組件的 CRUD 方法。
    注意:如果希望以一種更加面向對象的方式使用數據,可以使用另一種替代方法,即定義一個基於公共語言運行庫的反射功能的對象保持層。您可以創建一個使用反射功能來讀取對象屬性的架構,並使用映射文件來描述對象與表之間的映射。然而,要有效地實現上述方法,需要大量的基礎結構代碼投入。對於 ISV 和解決方案提供商來說,這種投入或許可以接受,但對於大多數組織則不可行。有關這方面的討論超出了本文的範圍,這裏不再論述。

技術因素

圖 2 所示爲影響數據訪問邏輯組件和業務實體實現策略的一些技術因素。本文將分別討論這些技術因素並提供相關建議。

圖 2:影響數據訪問邏輯組件和業務實體設計的技術因素

將關係數據映射到業務實體

數據庫通常包含許多表,這些表之間的關係通過主鍵和外鍵來實現。當定義業務實體以在 .NET 應用程序中表示這些數據時,必須確定如何把這些表映射到業務實體。

請考慮圖 3 所示的假想零售商數據庫。

圖 3:假想的關係數據庫中的表關係

下表總結了示例數據庫中的關係類型。

關係類型示例說明
一對多Customer:Address



Customer:Order
一個客戶可以有多個地址,例如送貨地址、帳單接收地址、聯繫地址等。

一個客戶可以有多個訂單。
多對多Order:Product一個訂單可以包含許多產品,每種產品由 OrderDetails 表中的單獨一行表示。同樣,一種產品也可以出現在許多訂單中。

當定義業務實體以在數據庫中建立信息模型時,應考慮要如何在您的應用程序中使用這些信息。應當標識封裝您的應用程序的功能的核心業務實體,而不是爲每個表定義單獨的業務實體。

該假想零售商的應用程序中的典型操作如下:

  • 獲取(或更新)客戶的有關信息(包括地址)
  • 獲取客戶的訂單列表
  • 獲取特定訂單的訂購項目列表
  • 創建新訂單
  • 獲取(或更新)一個或一組產品的有關信息

爲滿足這些應用程序要求,該應用程序要處理三個邏輯業務實體:Customer、Order 和 Product。對於每個業務實體,都將定義一個單獨的數據訪問邏輯組件,如下所示:

  • Customer 數據訪問邏輯組件。此類將爲檢索和修改 Customer 表和 Address 表中的數據提供服務。
  • Order 數據訪問邏輯組件。此類將爲檢索和修改 Order 表和 OrderDetails 表中的數據提供服務。
  • Product 數據訪問邏輯組件。此類將爲檢索和修改 Product 表中的數據提供服務。

圖 4 所示爲這些數據訪問邏輯組件與它們所表示的數據庫中的表之間的關係。

圖 4:定義向 .NET 應用程序公開關係數據的數據訪問邏輯組件

有關如何實現數據訪問邏輯組件的說明,請參閱本文後面的實現數據訪問邏輯組件

將關係數據映射到業務實體的建議

要將關係數據映射到業務實體,請考慮以下建議:

  • 花些時間來分析您的應用程序的邏輯業務實體併爲之建立模型,不要爲每個表定義一個單獨的業務實體。建立應用程序的工作方式模型的方法之一是使用統一建模語言 (UML)。UML 是一種形式設計註釋,用於在面向對象的應用程序中建立對象模型,並獲取有關對象如何表示自動過程、人機交互以及關聯的信息。有關詳細信息,請參閱 Modeling Your Application and Data
  • 不要定義單獨的業務實體來表示數據庫中的多對多表,可以通過在數據訪問邏輯組件中實現的方法來公開這些關係。例如,前面示例中的 OrderDetails 表沒有映射到單獨的業務實體,而是通過在 Order 數據訪問邏輯組件中封裝 OrderDetails 表來實現 Order 與 Product 表之間的多對多關係。
  • 如果具有返回特定業務實體類型的方法,請把這些方法放在該類型對應的數據訪問邏輯組件中。例如,當檢索一個客戶的全部訂單時,返回值爲 Order 類型,因此應在 Order 數據訪問邏輯組件中實現該功能。反之,當檢索訂購某特定產品的全部客戶時,應在 Customer 數據訪問邏輯組件中實現該功能。
  • 數據訪問邏輯組件通常訪問來自單一數據源的數據。當需要聚合多個數據源的數據時,建議分別爲訪問每個數據源定義一個數據訪問邏輯組件,這些組件可以由一個能夠執行聚合任務的更高級業務過程組件來調用。建議採用這種方法的原因有二:
    • 事務管理集中在業務過程組件中,不需要由數據訪問邏輯組件顯式控制。如果通過一個數據訪問邏輯組件訪問多個數據源,則需要把該數據訪問邏輯組件作爲事務處理的根,這會給僅讀取數據的功能帶來額外的系統開銷。
    • 通常,並不是應用程序的所有區域都需要聚合,並且通過分離對數據的訪問,您可以單獨使用該類型,也可以在必要時將其用作聚合的一部分。

實現數據訪問邏輯組件

數據訪問邏輯組件是一個無狀態類,也就是說,所交換的所有消息都可以獨立解釋。調用之間不存在狀態。數據訪問邏輯組件爲訪問單一數據庫(某些情況下可以是多個數據庫,例如水平數據庫分區)中的一個或多個相關表提供方法。通常,數據訪問邏輯組件中的這些方法將調用存儲過程以執行相應操作。

數據訪問邏輯組件的主要目標之一是從調用應用程序中隱藏數據庫的調用及格式特性。數據訪問邏輯組件爲這些應用程序提供封裝的數據訪問服務。具體地說,數據訪問邏輯組件處理以下實現細節:

  • 管理和封裝鎖定模式
  • 正確處理安全性和授權問題
  • 正確處理事務處理問題
  • 執行數據分頁
  • 必要時執行數據相關路由
  • 爲非事務性數據的查詢實現緩存策略(如果適用)
  • 執行數據流處理和數據序列化

本節後面將詳細討論其中的某些問題。

數據訪問邏輯組件的應用方案

圖 5 所示爲從各種應用程序類型(包括 Windows 窗體應用程序、ASP.NET 應用程序、XML Web services 和業務過程)中調用數據訪問邏輯組件的方式。根據應用程序的部署方式,這些調用可以是本地的,也可以是遠程的。

單擊此處查看大圖像

圖 5:數據訪問邏輯組件的應用方案(單擊縮略圖以查看大圖像)

實現數據訪問邏輯組件類

數據訪問邏輯組件使用 ADO.NET 執行 SQL 語句或調用存儲過程。有關數據訪問邏輯組件類的示例,請參閱附錄中的如何定義數據訪問邏輯組件類

如果您的應用程序包含多個數據訪問邏輯組件,可以使用數據訪問助手組件來簡化數據訪問邏輯組件類的實現。該組件可以幫助管理數據庫連接、執行 SQL 命令以及緩存參數。數據訪問邏輯組件仍然封裝訪問特定業務數據所需的邏輯,而數據訪問助手組件則專注於數據訪問 API 的開發和數據連接配置,從而幫助減少代碼的重複。Microsoft 提供了 Data Access Application Block for .NET,當使用 Microsoft SQL Server™ 數據庫時,可在您的應用程序中將其用作一個通用的數據訪問助手組件。圖 6 所示爲使用數據訪問助手組件幫助實現數據訪問邏輯組件的方法。

圖 6: 使用數據訪問助手組件實現數據訪問邏輯組件

當存在所有數據訪問邏輯組件公用的實用程序功能時,可以定義一個基本類以從中繼承和擴展數據訪問邏輯組件。

將數據訪問邏輯組件類設計爲可以爲不同類型的客戶端提供一致的接口。如果將數據訪問邏輯組件設計爲與當前及潛在的業務過程層的實現要求相兼容,可以減少必須實現的附加接口、接觸面或映射層的數目。

要支持廣泛的業務過程和應用程序,請考慮以下技術以便將數據傳入和傳出數據訪問邏輯組件方法:

  • 將業務實體數據傳遞給數據訪問邏輯組件中的方法。您可以用多種不同的格式傳遞數據:作爲一系列標量值、作爲 XML 字符串、作爲 DataSet 或作爲自定義業務實體組件。
  • 從數據訪問邏輯組件中的方法返回業務實體數據。您可以用多種不同的格式返回數據:作爲輸出參數標量值、作爲 XML 字符串、作爲 DataSet、作爲自定義業務實體組件或作爲數據讀取器。

以下各節將說明用於將業務實體數據傳入和傳出數據訪問邏輯組件的各種方式以及每種方式的優缺點。這些信息有助於您根據自己特定的應用程序方案做出相應選擇。

將標量值作爲輸入和輸出傳遞

這種方法的優點如下:

  • 抽象。調用程序只需要知道定義業務實體的數據,而不需要知道業務實體的具體類型或具體結構。
  • 序列化。標量值本身支持序列化。
  • 內存使用效率高。標量值只傳遞實際需要的數據。
  • 性能。當處理實例數據時,標量值具有比本文所述的其他方法更高的性能。

這種方法的缺點如下:

  • 緊密耦合與維護。架構的更改可能需要修改方法簽名,這會影響調用代碼。
  • 實體集合。要向數據訪問邏輯組件保存或更新多個實體,必須進行多次單獨的方法調用。這在分佈式環境中會給性能帶來很大影響。
  • 支持開放式併發。要支持開放式併發,必須在數據庫中定義時間戳列並將其作爲數據的一部分。

將 XML 字符串作爲輸入和輸出傳遞

這種方法的優點如下:

  • 鬆散耦合。調用程序只需要知道定義業務實體的數據和爲業務實體提供元數據的架構。
  • 集成。採用 XML 可以支持以各種方式(例如,.NET 應用程序、BizTalk Orchestration 規則和第三方業務規則引擎)實現的調用程序。
  • 業務實體集合。一個 XML 字符串可以包含多個業務實體的數據。
  • 序列化。字符串本身支持序列化。

這種方法的缺點如下:

  • 需要重新分析 XML 字符串。必須在接收端重新分析 XML 字符串。很大的 XML 字符串會影響性能。
  • 內存使用效率低。XML 字符串比較繁瑣,因而在需要傳遞大量數據時會降低內存使用效率。
  • 支持開放式併發。要支持開放式併發,必須在數據庫中定義時間戳列並將其作爲 XML 數據的一部分。

將 DataSet 作爲輸入和輸出傳遞

這種方法的優點如下:

  • 固有功能。DataSet 提供了內置功能,可以處理開放式併發(以及數據適配器)並支持複雜的數據結構。此外,有類型的 DataSet 還提供了數據驗證支持。
  • 業務實體集合。DataSet 是爲處理複雜的關係集合而設計的,因此不需要再編寫自定義代碼來實現這一功能。
  • 維護。更改架構不會影響方法簽名。然而,如果使用的有類型的 DataSet 和程序集具有嚴格名稱,則必須按照新版本重新編譯數據訪問邏輯組件類,或在全局程序集緩存中使用發佈者策略,或在配置文件中定義一個 <bindingRedirect> 元素。有關運行時如何定位程序集的信息,請參閱 How the Runtime Locates Assemblies
  • 序列化。DataSet 本身支持 XML 序列化,並且可以跨層序列化。

這種方法的缺點如下:

  • 性能。實例化和封送處理 DataSet 會增加運行時負擔。
  • 表示單個業務實體。DataSet 是爲處理一組數據而設計的。如果您的應用程序主要處理實例數據,則標量值或自定義實體是更好的方法,後者不會影響性能。

將自定義業務實體組件作爲輸入和輸出傳遞

這種方法的優點如下:

  • 維護。更改架構不會影響數據訪問邏輯組件方法簽名。然而,如果業務實體組件包含在嚴格命名的程序集中,就會出現與有類型的 DataSet 同樣的問題。
  • 業務實體集合。可以將自定義業務實體組件的數組和集合傳入和傳出方法。

這種方法的缺點如下:

  • 支持開放式併發。要方便地支持開放式併發,必須在數據庫中定義時間戳列並將其作爲實例數據的一部分。
  • 集成限制。當使用自定義業務實體組件作爲數據訪問邏輯組件的輸入時,調用程序必須知道業務實體的類型,而這會限制不使用 .NET 的調用程序的集成。然而,如果調用程序使用自定義業務實體組件作爲數據訪問邏輯組件的輸出,則上述問題並不會限制集成。例如,Web 方法可以返回從數據訪問邏輯組件返回的自定義業務實體組件,並使用 XML 序列化自動將該業務實體組件序列化爲 XML。

將數據讀取器作爲輸出返回

這種方法的優點如下:

  • 性能。當需要快速呈現數據時,這種方法具有性能優勢,並且可以使用表示層代碼部署數據訪問邏輯組件。

這種方法的缺點如下:

  • 遠程。建議不要在遠程方案中使用數據讀取器,因爲它可能會使客戶端應用程序與數據庫保持長時間的連接。

配合使用數據訪問邏輯組件與存儲過程

可以使用存儲過程執行數據訪問邏輯組件支持的許多數據訪問任務。

優點

  • 存儲過程通常可以改善性能,因爲數據庫能夠優化存儲過程使用的數據訪問計劃併爲以後的重新使用緩存該計劃。
  • 可以在數據庫內分別設置各個存儲過程的安全保護。管理員可以授予客戶端執行某個存儲過程的權限,而不授予任何基礎表訪問權限。
  • 存儲過程可以簡化維護,因爲修改存儲過程通常比修改所部署的組件中的硬編碼 SQL 語句要容易。然而,隨着在存儲過程中實現的業務邏輯的增多,上述優勢會逐漸減弱。
  • 存儲過程增大了從基礎數據庫架構進行抽象的程度。存儲過程的客戶端與存儲過程的實現細節和基礎架構是彼此分離的。
  • 存儲過程會降低網絡流量。應用程序可以按批執行 SQL 語句而不必發出多個 SQL 請求。

儘管存儲過程具有上述優點,但仍有某些情況不適合使用存儲過程。

缺點

  • 如果邏輯全部在存儲過程中實現,那麼涉及廣泛業務邏輯和處理的應用程序可能會給服務器帶來過重負荷。這類處理包括數據傳輸、數據遍歷、數據轉換和大計算量操作。應把這類處理移到業務過程或數據訪問邏輯組件中,與數據庫服務器相比,它們具有更好的可縮放性。
  • 不要把所有業務邏輯都放在存儲過程中。如果必須在 T - SQL 中修改業務邏輯,應用程序的維護和靈活性將成爲問題。例如,支持多個 RDBMS 的 ISV 應用程序不應當分別爲每個系統維護存儲過程。
  • 通常,存儲過程的編寫與維護是一項專門技能,並非所有開發人員都能夠掌握。這會造成項目開發計劃的瓶頸。

配合使用數據訪問邏輯組件與存儲過程的建議

配合使用數據訪問邏輯組件與存儲過程時,請考慮以下建議:

  • 公開存儲過程。數據訪問邏輯組件應當是向存儲過程名稱、參數、表、字段等數據庫架構信息公開的僅有組件。業務實體實現應不需要知道或依賴於數據庫架構。
  • 使存儲過程與數據訪問邏輯組件相關聯。每個存儲過程只應被一個數據訪問邏輯組件調用,並應與調用它的數據訪問邏輯組件相關聯。例如,假設一個客戶向一個零售商訂貨。您可以編寫一個名爲 OrderInsert 的存儲過程,用於在數據庫中創建訂單。在您的應用程序中,必須確定是從 Customer 數據訪問邏輯組件還是從 Order 數據訪問邏輯組件調用該存儲過程。Order 數據訪問邏輯組件處理所有與訂單相關的任務,而 Customer 數據訪問邏輯組件處理客戶姓名、地址等客戶信息,因此最好使用前者。
  • 命名存儲過程。爲要使用的數據訪問邏輯組件定義存儲過程時,所選擇的存儲過程名稱應當強調與之相關的數據訪問邏輯組件。這種命名方法有助於識別哪個組件調用哪個存儲過程,併爲在 SQL 企業管理器中邏輯分組存儲過程提供了一種方法。例如,可以事先編寫名爲 CustomerInsert、CustomerUpdate、CustomerGetByCustomerID、CustomerDelete 的存儲過程供 Customer 數據訪問邏輯組件使用,然後提供 CustomerGetAllInRegion 等更具體的存儲過程以支持您的應用程序的業務功能。
    注意:不要在存儲過程名稱前面使用前綴 sp_,這會降低性能。當調用一個以 sp_ 開頭的存儲過程時,SQL Server 始終會先檢查 master 數據庫,即使該存儲過程已由數據庫名稱進行限定。
  • 解決安全性問題。如果接受用戶輸入以動態執行查詢,請不要通過沒有使用參數的連接值來創建字符串。如果使用 sp_execute 執行結果字符串,或者不使用 sp_executesql 參數支持,則還應避免在存儲過程中使用字符串連接。

管理鎖定和併發

某些應用程序在更新數據庫數據時採用“後進有效”(Last in Wins) 法。使用“後進有效”法更新數據庫時不會將更新與原始記錄相比較,因此可能會覆蓋掉自上次刷新記錄以來其他用戶所做的所有更改。然而,有時應用程序卻需要在執行更新之前確定數據自最初讀取以來是否被更改。

數據訪問邏輯組件可以實現管理鎖定和併發的代碼。管理鎖定和併發的方法有兩種:

  • 保守式併發。爲進行更新而讀取某行數據的用戶可以在數據源中對該行設置一個鎖定。在該用戶解除鎖定之前,其他任何用戶都不能更改該行。
  • 開放式併發。用戶在讀取某行數據時不鎖定該行。其他用戶可以在同一時間自由訪問該行。當用戶要更新某行數據時,應用程序必須確定自該行被讀取以來其他用戶是否進行過更改。嘗試更新已經過更改的記錄會導致併發衝突。

使用保守式併發

保守式併發主要用於數據爭用量大以及通過鎖定來保護數據的成本低於發生併發衝突時回滾事務的成本的環境。如果鎖定時間很短(例如在編程處理的記錄中),則實現保守式併發效果最好。

保守式併發要求與數據庫建立持久連接,並且因爲記錄可能被鎖定較長時間,因此當用戶與數據進行交互時,不能提供可縮放的性能。

使用開放式併發

開放式併發適用於數據爭用量低或要求只讀訪問數據的環境。開放式併發可以減少所需鎖定的數量,從而降低數據庫服務器的負荷,提高數據庫的性能。

開放式併發在 .NET 中被廣泛使用以滿足移動和脫機應用程序的需要。在這種情況下,長時間鎖定數據是不可行的。此外,保持記錄鎖定還要求與數據庫服務器的持久連接,這在脫機應用程序中是不可能的。

測試開放式併發衝突

測試開放式併發衝突的方法有多種:

  • 使用分佈式時間戳。分佈式時間戳適用於不要求協調的環境。在數據庫的每個表中添加一個時間戳列或版本列。時間戳列與對錶內容的查詢一起返回。當試圖更新時,數據庫中的時間戳值將與被修改行中的原始時間戳值進行比較。如果這兩個值匹配,則執行更新,同時時間戳列被更新爲當前時間以反映更新。如果這兩個值不匹配,則發生開放式併發衝突。
  • 保留原始數據值的副本。在查詢數據庫的數據時保留原始數據值的一個副本。在更新數據庫時,檢查數據庫的當前值是否與原始值匹配。
  • 原始值保存在 DataSet 中,當更新數據庫時,數據適配器可以使用該原始值執行開放式併發檢查。
  • 使用集中的時間戳。在數據庫中定義一個集中的時間戳表,用於記錄對任何表中的任何行的更新。例如,時間戳表可以顯示以下信息:“2002 年 3 月 26 日下午 2:56 約翰更新了表 XYZ 中的行 1234”。

    集中的時間戳適用於簽出方案以及某些脫機客戶端方案,其中可能需要明確的鎖定所有者和替代管理。此外,集中的時間戳還可以根據需要提供審覈。

手動實現開放式併發

請考慮以下 SQL 查詢:

SELECT Column1, Column2, Column3 FROM Table1

要在更新 Table1 的行時測試開放式併發衝突,可以發出以下 UPDATE 語句:

UPDATE Table1 Set Column1 = @NewValueColumn1,
              Set Column2 = @NewValueColumn2,
              Set Column3 = @NewValueColumn3
WHERE Column1 = @OldValueColumn1 AND
      Column2 = @OldValueColumn2 AND
      Column3 = @OldValueColumn3

如果原始值與數據庫中的值匹配,則執行更新。如果某個值被修改,WHERE 子句將無法找到相應匹配,從而更新將不會修改該行。您可以對此技術稍加變化,即只對特定列應用 WHERE 子句,使得如果自上次查詢以來特定字段被更新,則不覆蓋數據。

注意:請始終返回一個唯一標識查詢中的一行的值,例如一個主關鍵字,以用於 UPDATE 語句的 WHERE 子句。這樣可以確保 UPDATE 語句更新正確的行。

如果數據源中的列允許空值,則可能需要擴展 WHERE 子句,以便檢查本地表與數據源中匹配的空引用。例如,以下 UPDATE 語句將驗證本地行中的空引用(或值)是否仍然與數據源中的空引用(或值)相匹配。

UPDATE Table1 Set Column1 = @NewColumn1Value
WHERE (@OldColumn1Value IS NULL AND Column1 IS NULL) OR Column1 =
@OldColumn1Value

使用數據適配器和 DataSet 實現開放式併發

可以配合使用 DataAdapter.RowUpdated 事件與前面所述技術以通知您的應用程序發生了開放式併發衝突。每當試圖更新 DataSet 中的修改過的行時,都將引發 RowUpdated 事件。可以使用 RowUpdated 事件添加特殊處理代碼,包括髮生異常時的處理、添加自定義錯誤信息以及添加重試邏輯。

RowUpdated 事件處理程序接收一個 RowUpdatedEventArgs 對象,該對象具有 RecordsAffected 屬性,可以顯示針對表中的一個修改過的行的更新命令會影響多少行。如果把更新命令設置爲測試開放式併發,則當發生開放式併發衝突時,RecordsAffected 屬性將爲 0。設置 RowUpdatedEventArgs.Status 屬性以表明要採取的操作;例如,可以把該屬性設置爲 UpdateStatus.SkipCurrentRow 以跳過對當前行的更新,但是繼續更新該更新命令中的其他行。有關 RowUpdated 事件的詳細信息,請參閱 Working with DataAdapter Events

使用數據適配器測試併發錯誤的另一種方法是在調用 Update 方法之前把 DataAdapter.ContinueUpdateOnError 屬性設置爲 true。完成更新後,調用 DataTable 對象的 GetErrors 方法以確定哪些行發生了錯誤。然後,使用這些行的 RowError 屬性找到特定的詳細錯誤信息。有關如何處理行錯誤的詳細信息,請參閱 Adding and Reading Row Error Information

以下代碼示例顯示了 Customer 數據訪問邏輯組件如何檢查併發衝突。該示例假設客戶端檢索到了一個 DataSet 並修改了數據,然後把該 DataSet 傳遞給了數據訪問邏輯組件中的 UpdateCustomer 方法。UpdateCustomer 方法將通過調用以下存儲過程來更新相應的客戶記錄;僅當客戶 ID 與公司名稱未被修改時存儲過程才能更新該客戶記錄:

CREATE PROCEDURE CustomerUpdate
{
  @CompanyName varchar(30),
  @oldCustomerID varchar(10),
  @oldCompanyName varchar(30)
}
AS
  UPDATE Customers Set CompanyName = @CompanyName
  WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName
GO

在 UpdateCustomer 方法中,以下代碼示例將一個數據適配器的 UpdateCommand 屬性設置爲測試開放式併發,然後使用 RowUpdated 事件測試開放式併發衝突。如果遇到開放式併發衝突,應用程序將通過設置要更新的行的 RowError 來表明開放式併發衝突。注意,傳遞給 UPDATE 命令中的 WHERE 子句的參數值被映射到 DataSet 中各相應列的原始值。

// CustomerDALC 類中的 UpdateCustomer 方法
public void UpdateCustomer(DataSet dsCustomer)
{
// 連接到 Northwind 數據庫
  SqlConnection cnNorthwind = new SqlConnection(
    "Data source=localhost;Integrated security=SSPI;Initial
Catalog=northwind");

// 創建一個數據適配器以訪問 Northwind 中的 Customers 表
  SqlDataAdapter da = new SqlDataAdapter();

// 設置數據適配器的 UPDATE 命令,調用存儲過程“UpdateCustomer”
  da.UpdateCommand = new SqlCommand("CustomerUpdate", cnNorthwind);
  da.UpdateCommand.CommandType = CommandType.StoredProcedure;

  // 向數據適配器的 UPDATE 命令添加兩個參數,
  // 爲 WHERE 子句指定信息(用於檢查開放式併發衝突)
  da.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30,
"CompanyName");

  // 將 CustomerID 的原始值指定爲第一個 WHERE 子句參數
  SqlParameter myParm = da.UpdateCommand.Parameters.Add(
                            "@oldCustomerID", SqlDbType.NChar, 5,
"CustomerID");
  myParm.SourceVersion = DataRowVersion.Original;

  // 將 CustomerName 的原始值指定爲第二個 WHERE 子句參數
  myParm = da.UpdateCommand.Parameters.Add(
                            "@oldCompanyName", SqlDbType.NVarChar, 30,
"CompanyName");
  myParm.SourceVersion = DataRowVersion.Original;

  // 爲 RowUpdated 事件添加一個處理程序
  da.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);

  // 更新數據庫
  da.Update(ds, "Customers");

  foreach (DataRow myRow in ds.Tables["Customers"].Rows)
  {
    if (myRow.HasErrors)
      Console.WriteLine(myRow[0] + " " + myRow.RowError);
  }
}

// 處理 RowUpdated 事件的方法。 如果登記該事件但不處理它,
// 則引發一個 SQL 異常。
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs
args)
{
  if (args.RecordsAffected == 0)
  {
    args.Row.RowError = "遇到開放式併發衝突";
    args.Status = UpdateStatus.SkipCurrentRow;
  }
}

當在一個 SQL Server 存儲過程中執行多個 SQL 語句時,出於性能原因,可以使用 SET NOCOUNT ON 選項。此選項將禁止 SQL Server 在每次執行完一條語句時都向客戶端返回一條消息,從而可以降低網絡流量。然而,這樣將不能像前面的代碼示例那樣檢查 RecordsAffected 屬性。RecordsAffected 屬性將始終爲 1。另一種方法是在存儲過程中返回 @@ROWCOUNT 函數(或將它指定爲一個輸出參數);@@ROWCOUNT 包含了存儲過程中上一條語句完成時的記錄數目,並且即使使用了 SET NOCOUNT ON,該函數也會被更新。因此,如果存儲過程中執行的上一條 SQL 語句是實際的 UPDATE 語句,並且已經指定 @@ROWCOUNT 作爲返回值,則可以對應用程序代碼進行如下修改:

// 向數據適配器的 UPDATE 命令添加另一個參數來接收返回值。
// 可以任意命名該參數。
myParm = da.UpdateCommand.Parameters.Add("@RowCount", SqlDbType.Int);
myParm.Direction = ParameterDirection.ReturnValue;

// 將 OnRowUpdated 方法修改爲檢查該參數的值
// 而不是 RecordsAffected 屬性。
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs
args)
{
  if (args.Command.Parameters["@RowCount"].Value == 0)
  {
    args.Row.RowError = "遇到開放式併發衝突";
    args.Status = UpdateStatus.SkipCurrentRow;
  }
}

COM 互操作性

如果希望數據訪問邏輯組件類能夠被 COM 客戶端調用,則建議按前面所述的原則定義數據存取邏輯組件,並提供一個包裝組件。然而,如果希望 COM 客戶端能夠訪問數據訪問邏輯組件,請考慮以下建議:

  • 將該類及其成員定義爲公共。
  • 避免使用靜態成員。
  • 在託管代碼中定義事件-源接口。
  • 提供一個不使用參數的構造函數。
  • 不要使用重載的方法,而使用多個名稱不同的方法。
  • 使用接口公開常用操作。
  • 使用屬性爲類和成員提供附加 COM 信息。
  • 在 .NET 代碼引發的所有異常中包含 HRESULT 值。
  • 在方法簽名中使用自動兼容的數據類型。

有關 COM 互操作性的詳細信息,請參閱 Microsoft .NET/COM Migration and Interoperability 指南。

實現業務實體

業務實體具有以下特點:

  • 業務實體提供對業務數據及相關功能(在某些設計中)的狀態編程訪問。
  • 業務實體可以使用具有複雜架構的數據來構建。這種數據通常來自數據庫中的多個相關表。
  • 業務實體數據可以作爲業務過程的部分 I/O 參數傳遞。
  • 業務實體可以是可序列化的,以保持它們的當前狀態。例如,應用程序可能需要在本地磁盤、桌面數據庫(如果應用程序脫機工作)或消息隊列消息中存儲實體數據。
  • 業務實體不直接訪問數據庫。全部數據庫訪問都是由相關聯的數據訪問邏輯組件提供的。
  • 業務實體不啓動任何類型的事務處理。事務處理由使用業務實體的應用程序或業務過程來啓動。

如本文前面所述,在您的應用程序中表示業務實體的方法有很多(從以數據爲中心的模型到更加面向對象的表示法):

  • XML
  • 通用 DataSet
  • 有類型的 DataSet
  • 自定義業務實體組件
  • 帶有 CRUD 行爲的自定義業務實體組件

以下各節將介紹如何使用這些格式來表示業務實體。爲幫助您確定特定環境中最適宜的業務實體表示,以下各節將介紹如何爲各業務實體格式執行以下任務:

  • 組織業務實體集合
  • 將業務實體數據綁定到用戶界面控件
  • 序列化業務實體數據
  • 在層間傳遞業務實體數據

以下各節還針對非功能性要求(包括性能、效率、可縮放性和可擴展性)考慮了每種業務實體表示的適用性。

將業務實體表示爲 XML

以下示例顯示瞭如何將一個簡單的業務實體表示爲 XML。該業務實體包含一個產品。

<?xml version="1.0"?>
<Product xmlns="urn:aUniqueNamespace">
  <ProductID>1</ProductID>
  <ProductName>Chai</ProductName>
  <QuantityPerUnit>10 boxes x 20 bags</QuantityPerUnit>
  <UnitPrice>18.00</UnitPrice>
  <UnitsInStock>39</UnitsInStock>
  <UnitsOnOrder>0</UnitsOnOrder>
  <ReorderLevel>10</ReorderLevel>
</Product>

有關詳細信息,請參閱附錄中的如何使用 XML 表示數據的集合和層次結構

當使用 XML 表示業務實體數據時,請考慮以下原則:

  • 確定 XML 文檔是包含單個業務實體還是包含一個業務實體集合。前面的示例表示的是單個 Product 業務實體。
  • 使用一個命名空間唯一標識該 XML 文檔,以避免與其他 XML 文檔的內容發生命名衝突。前面的示例使用名爲 urn:aUniqueNamespace 的默認命名空間。
  • 爲元素和屬性選擇合適的名稱。前面的示例使用 Product 表的列名稱,但並不要求一定這樣。可以選擇對您的應用程序有意義的名稱。
  • 使用以下方法之一以 XML 格式檢索您的業務實體:
    • 如果您使用的是 SQL Server 2000,則可以在查詢或存儲過程中使用 FOR XML 子句。在性能測試中,使用 FOR XML 只比返回 DataSet 稍微快一點。
    • 檢索 DataSet 並將其轉換爲 XML 流或以 XML 流的格式寫出。這種方法會帶來創建 DataSet 的系統開銷和額外的轉換開銷(如果執行轉換)。
    • 使用輸出參數或數據讀取器構建一個 XML 文檔。數據讀取器是從數據庫檢索多個行的最快方法,但與構建 XML 相關聯的過程可能會減弱這種性能優勢。

有關詳細信息或性能方面的考慮,請參閱 Performance Comparison:Data Access Techniques

將業務實體表示爲 XML 的優點如下:

  • 標準支持。XML 是 World Wide Web Consortium (W3C) 的標準數據表示格式。有關此標準的詳細信息,請參閱 http://www.w3.org/xml
  • 靈活性。XML 能夠表示信息的層次結構和集合。有關詳細信息,請參閱附錄中的如何使用 XML 表示數據的集合和層次結構
  • 互操作性。在所有平臺上,XML 都是與外部各方及貿易伙伴交換信息的理想選擇。如果 XML 數據將由 ASP.NET 應用程序或 Windows 窗體應用程序使用,則還可以把這些 XML 數據裝載到一個 DataSet 中,以利用 DataSet 提供的數據綁定支持。

將業務實體表示爲 XML 的缺點如下:

  • 類型保真。XML 不支持類型保真。然而,對於簡單的數據分類可以使用 XSD 架構。
  • 驗證 XML。要驗證 XML,可以手動分析代碼,或者使用 XSD 架構。但這兩種方法都比較慢。有關如何使用 XSD 架構驗證 XML 的示例,請參閱如何使用 XSD 架構驗證 XML
  • 顯示 XML。您不能將 XML 數據自動顯示在用戶界面上。可以編寫一個 XSLT 樣式表將數據轉換爲 DataSet;但樣式表的編寫比較麻煩。另一種方法是通過樣式表將 XML 轉換爲 HTML 等可顯示格式。有關詳細信息,請參閱附錄中的如何在 .NET 應用程序中編程應用樣式表
  • 分析 XML。要分析 XML,可以使用文檔對象模型 (DOM) 或 Microsoft .NET Framework 類庫提供的 XmlReader 類。XmlReader 提供對 XML 數據的快速只讀的、僅向前的訪問,而 DOM 可以提供隨機讀/寫訪問,因此更靈活。然而,使用 DOM 分析 XML 文檔的速度較慢;您必須創建一個 XmlDocument 實例(或另一個 XML 分析器類)並把整個 XML 文件裝載到內存中。
  • 排序 XML。您不能自動排序 XML 數據,而應使用以下技術之一:
    • 按預先排好的順序提供數據。這種方法不支持在調用應用程序中動態重新排序數據。
    • 應用 XSLT 樣式表動態排序數據。如果需要,可以使用 DOM 在運行時改變 XSLT 樣式表中的排序條件。
    • 將 XML 數據轉換爲 DataSet,並使用 DataView 對象排序和搜索數據元素。
  • 使用專用字段。您不能選擇隱藏信息。

將業務實體表示爲通用 DataSet

通用 DataSet 是 DataSet 類的實例,它是在 ADO.NET 的 System.Data 命名空間中定義的。DataSet 對象包含一個或多個 DataTable 對象,用以表示數據訪問邏輯組件從數據庫檢索到的信息。

圖 7 所示爲用於 Product 業務實體的通用 DataSet 對象。該 DataSet 對象具有一個 DataTable,用於保存產品信息。該 DataTable 具有一個 UniqueConstraint 對象,用於將 ProductID 列標記爲主鍵。DataTable 和 UniqueConstraint 對象是在數據訪問邏輯組件中創建該 DataSet 時創建的。

圖 7:用於 Product 業務實體的通用 DataSet

圖 8 所示爲用於 Order 業務實體的通用 DataSet 對象。此 DataSet 對象具有兩個 DataTable 對象,分別保存訂單信息和訂單詳細信息。每個 DataTable 具有一個對應的 UniqueConstraint 對象,用於標識表中的主鍵。此外,該 DataSet 還有一個 Relation 對象,用於將訂單詳細信息與訂單相關聯。

圖 8:用於 Order 業務實體的通用 DataSet

以下代碼顯示瞭如何從數據訪問邏輯組件檢索通用 DataSet ,然後將該 DataSet 綁定到 DataGrid 控件,再將該 DataSet 傳遞到數據訪問邏輯組件以保存對數據所做的更改:

// 創建 ProductDALC 對象
ProductDALC dalcProduct = new ProductDALC();

// 對 ProductDALC 調用一個方法以獲取一個包含全部產品信息的 DataSet
DataSet dsProducts = dalcProduct.GetProducts();

// 在客戶端中使用 DataSet。 例如,把該 DataSet 綁定到用戶界面控件
dataGrid1.DataSource = dsProducts.Tables[0].DefaultView;
dataGrid1.DataBind();

// 然後,把更新後的 DataSet 傳遞給 ProductDALC,將更改
// 保存到數據庫
dalcProduct.UpdateProducts(dsProducts);

您還可以在運行時查詢和修改 DataSet 中的表、約束及關係。有關詳細信息,請參閱 Creating and Using DataSets

將業務實體表示爲通用 DataSet 的優點如下:

  • 靈活性。DataSet 可以包含數據的集合,能夠表示複雜的數據關係。
  • 序列化。在層間傳遞時,DataSet 本身支持序列化。
  • 數據綁定。可以把 DataSet 綁定到 ASP.NET 應用程序和 Windows 窗體應用程序的任意用戶界面控件。
  • 排序與過濾。可以使用 DataView 對象排序和過濾 DataSet。應用程序可以爲同一個 DataSet 創建多個 DataView 對象,以便用不同方式查看數據。
  • 與 XML 的互換性。可以用 XML 格式讀寫 DataSet。這種方法在遠程和脫機應用程序中很有用,它可以用 XML 格式接收 DataSet,然後在本地重新創建該 DataSet 對象。應用程序在與數據庫斷開連接後,還可以將 DataSet 保持爲 XML 格式。
  • 元數據的可用性。可以用 XSD 架構的形式爲 DataSet 提供完整的元數據。還可以使用 DataSet、DataTable、DataColumn、Constraint 和 Relation 類中的方法以編程方式爲 DataSet 獲取元數據。
  • 開放式併發。在更新數據時,可以配合使用數據適配器與 DataSet 以方便地執行開放式併發檢查。
  • 可擴展性。如果修改了數據庫架構,則適當情況下數據訪問邏輯組件中的方法可以創建包含修改後的 DataTable 和 DataRelation 對象的 DataSet。數據訪問邏輯組件方法簽名並不改變。可以將調用應用程序修改爲使用該 DataSet 中的這些新元素。

將業務實體表示爲通用 DataSet 的缺點如下:

  • 客戶端代碼必須通過 DataSet 中的集合訪問數據。要訪問 DataSet 中的表,客戶端代碼必須使用整數索引生成器或字符串索引生成器來索引 DataTable 集合。要訪問特定列,必須使用列編號或列名稱索引 DataColumn 集合。以下示例顯示瞭如何訪問 Products 表中第一行的 ProductName 列:
    // 獲取所調用的名爲 dsProducts 的 DataSet 的第一行的 
    // 產品名稱。 注意,該集合是基於零的。
    String str = (String)dsProducts.Tables["Products"].Rows[0]["ProductName"];
    ...
    
    注意:這裏沒有這些索引生成器的編譯時檢查。如果指定一個無效的表名稱、列名稱或列類型,會在運行時捕獲該錯誤。使用通用 DataSet 時不支持 IntelliSense。
  • 實例化和封送處理的成本很高。DataSet 需要創建多個子對象(DataTable、DataRow 和 DataColumn),這意味着在實例化和封送處理時,DataSet 會比 XML 字符串或自定義實體組件花費更長的時間。隨着數據量的增大,創建 DataSet 內部結構的系統開銷將明顯少於將數據填充到 DataSet 中所需的開銷,因此 DataSet 的相對性能會隨之提高。
  • 專用字段。您不能選擇隱藏信息。

將業務實體表示爲有類型的 DataSet

有類型的 DataSet 是包含具有嚴格類型的方法、屬性和類型定義以公開 DataSet 中的數據和元數據的類。有關如何創建有類型的 DataSet 的示例,請參閱附錄中的如何創建有類型的 DataSet

下面列出了有類型的 DataSet 與通用 DataSet 相比的優缺點。注意,有類型的 DataSet 的實例化和封送處理性能與通用 DataSet 基本相同。

將業務實體表示爲有類型的 DataSet 的優點如下:

  • 代碼易讀。要訪問有類型的 DataSet 中的表和列,可以使用有類型的方法和屬性,如以下代碼所示:
    ...
    // 獲取所調用的名爲 dsProducts 的有類型的 DataSet 的第一行的
    // 產品名稱。 注意,該集合是基於零的。
    String str = dsProducts.Products[0].ProductName;
    ...
    

    在本示例中,dsProducts 是有類型的 DataSet 的一個實例。該 DataSet 有一個 DataTable,它由一個命名爲 Products 的屬性公開。該 DataTable 中的列由 ProductName 等屬性公開,後者返回列的相應數據類型(而不僅僅返回對象)。

    有類型的方法和屬性的提供使得使用有類型的 DataSet 比使用通用 DataSet 更方便。使用有類型的 DataSet 時,IntelliSense 將可用。

  • 編譯時類型檢查。無效的表名稱和列名稱將在編譯時而不是在運行時檢測。

將業務實體表示爲有類型的 DataSet 的缺點如下:

  • 部署。必須將包含有類型的 DataSet 類的程序集部署到使用業務實體的所有層。
  • 支持企業服務 (COM+) 調用程序。如果有類型的 DataSet 將由 COM+ 客戶端使用,則必須爲包含該有類型的 DataSet 類的程序集提供一個嚴格名稱,並且必須在客戶端計算機上註冊。通常,該程序集安裝在全局程序集緩存中。這些也是自定義實體類所要求的步驟,如本文後面所述。
  • 可擴展性問題。如果修改了數據庫架構,則可能需要重新生成有類型的 DataSet 類以支持新架構。重新生成過程將不會保留在有類型的 DataSet 類中實現的任何自定義代碼。必須將包含有類型的 DataSet 類的程序集重新部署到所有客戶端應用程序中。
  • 實例化。您不能使用 new 運算符來實例化類型。
  • 繼承。有類型的 DataSet 必須從 DataSet 類繼承,這會禁止使用任何其他基本類。

定義自定義業務實體組件

表示業務實體的自定義類通常包含以下成員:

  • 用於在本地緩存業務實體的數據的專用字段。這些字段在數據訪問邏輯組件從數據庫檢索數據時保存數據庫數據的一個快照。
  • 用於訪問實體的狀態和訪問實體內數據的子集及層次結構的公共屬性。這些屬性的名稱可以與數據庫的列名稱相同,但這並不是一個絕對要求。可以根據您的應用程序的需要選擇屬性名,而不必使用數據庫中的名稱。
  • 用以使用實體組件中的數據執行本地化處理的方法和屬性。
  • 用以通知實體組件內部狀態變化的事件。

圖 9 所示爲使用自定義實體類的方法。注意,實體類並不知道數據訪問邏輯組件或基礎數據庫;所有數據庫訪問都由數據訪問邏輯組件執行,以集中數據訪問策略和業務邏輯。此外,在層間傳遞業務實體數據的方式與表示業務實體的格式也沒有直接關係;例如,可以在本地將業務實體表示爲對象,而用另一種方法(如標量值或 XML)將業務實體數據傳遞到其他層。

單擊此處查看大圖像

圖 9:自定義業務實體組件的作用(單擊縮略圖以查看大圖像)

定義自定義業務實體組件的建議

在實現自定義實體組件時,請考慮以下建議:

  • 選擇使用結構還是使用類。對於不包含分層數據或集合的簡單業務實體,可以考慮定義一個結構來表示業務實體。對於複雜的業務實體或要求繼承的業務實體,可將實體定義爲類。有關結構和類這兩種類型的比較,請參閱 Structures and Classes
  • 表示業務實體的狀態。對於數字、字符串等簡單值,可以使用等價的 .NET 數據類型來定義字段。有關說明如何定義自定義實體的代碼示例,請參閱附錄中的如何定義業務實體組件
  • 表示自定義業務實體組件中的子集合和層次結構。表示自定義實體中的數據的子集合和層次結構的方法有兩種:
    • .NET 集合(例如 ArrayList)。.NET 集合類爲大小可調的集合提供了一個方便的編程模型,還爲將數據綁定到用戶界面控件提供了內置的支持。
    • DataSet。DataSet 適合於存儲來自關係數據庫或 XML 文件的數據的集合和層次結構。此外,如果需要過濾、排序或綁定子集合,也應首選 DataSet。

      有關說明如何表示自定義實體中數據的集合和層次結構的代碼示例,請參閱附錄中的如何表示自定義實體中數據的集合和層次結構

  • 支持用戶界面客戶端的數據綁定。如果自定義實體將要由用戶界面使用並且希望利用自動數據綁定,可能需要在自定義實體中實現數據綁定。請考慮以下方案:
    • Windows 窗體中的數據綁定。您可以將實體實例的數據綁定到控件而不必在自定義實體中實現數據綁定接口。也可以綁定實體的數組或 .NET 集合。
    • Web 窗體中的數據綁定。如果不實現 IBindingList 接口,則不能將實體實例的數據綁定到 Web 窗體中的控件。然而,如果只想綁定集合,則可以使用數組或 .NET 集合而不必在自定義實體中實現 IBindingList 接口。

      有關說明如何將自定義實體綁定到用戶界面控件的代碼示例,請參閱附錄中的如何將業務實體組件綁定到用戶界面控件

  • 公開內部數據更改的事件。公開事件可以獲得豐富的客戶端用戶界面設計,因爲它使得無論數據顯示在哪裏都可以對其進行刷新。事件應當只針對內部狀態,而不是針對服務器上的數據更改。有關說明如何公開自定義實體類中的事件的代碼示例,請參閱附錄中的如何公開業務實體組件中的事件
  • 使業務實體可序列化。使業務實體可序列化可以將業務實體的狀態保持在中間狀態而不進行數據庫交互。這樣可以方便脫機應用程序的開發和複雜用戶界面過程的設計,即在完成前不會影響業務數據。序列化有兩種類型:
    • 使用 XmlSerializer 類進行 XML 序列化。如果只需要把公共字段和公共讀/寫屬性序列化爲 XML,則可以使用 XML 序列化。注意,如果從 Web 服務返回業務實體數據,對象將通過 XML 序列化自動序列化爲 XML。

      您可以對業務實體執行 XML 序列化而無需在實體中實現任何附加代碼。然而,只有對象中的公共字段和公共讀/寫屬性被序列化爲 XML。專用字段、索引生成器、專用屬性、只讀屬性及對象圖不會被序列化。您可以使用自定義實體中的屬性控制結果 XML。有關將自定義實體組件序列化爲 XML 格式的詳細信息,請參閱附錄中的如何將業務實體組件序列化爲 XML 格式

    • 使用 BinaryFormatter 或 SoapFormatter 類進行格式序列化。如果需要序列化對象的所有公共字段、專用字段及對象圖,或者需要與遠程服務器之間傳遞實體組件,則可以使用格式序列化。

      格式類將序列化對象的所有公共和專用字段及屬性。BinaryFormatter 將對象序列化爲二進制格式,SoapFormatter 將對象序列化爲 SOAP 格式。使用 BinaryFormatter 的序列化比使用 SoapFormatter 的序列化速度要快。要使用任何一個格式類,都必須將實體類標記爲 [Serializable] 屬性。如果需要顯式控制序列化格式,您的類還必須實現 ISerializable 接口。有關如何使用格式序列化的詳細信息,請參閱附錄中的如何將業務實體組件序列化爲二進制格式如何將業務實體組件序列化爲 SOAP 格式

    注意:還原序列化某個對象時,不會調用默認的構造函數。對還原序列化添加這項約束,是出於性能方面的考慮。

定義自定義實體的優點如下:

  • 代碼易讀。要訪問自定義實體類中的數據,可以使用有類型的方法和屬性,如以下代碼所示:
    // 創建一個 ProductDALC 對象
    ProductDALC dalcProduct = new ProductDALC();
    
    // 使用該 ProductDALC 對象創建和填充一個 ProductEntity 對象。
    // 此代碼假設 ProductDALC 類有一個名爲 GetProduct 的方法, 
    // 該方法使用 Product ID 作參數(本例中爲 21),並返回一個
    // 包含該產品的所有數據的 ProductEntity 對象。
    ProductEntity aProduct = dalcProduct.GetProduct(21);
    
    // 更改該產品的產品名稱
    aProduct.ProductName = "Roasted Coffee Beans";
    

    在上述示例中,產品是一個名爲 ProductEntity 的自定義實體類的一個實例。ProductDALC 類有一個名爲 GetProduct 的方法,後者創建一個 ProductEntity 對象,將某個特定產品的數據填充到該對象,然後返回 ProductEntity 對象。調用應用程序可以使用 ProductName 等屬性訪問 ProductEntity 對象中的數據,並且可以調用方法以操作該對象。

  • 封裝。自定義實體可以包含方法以封裝簡單的業務規則。這些方法操作緩存在實體組件中的業務實體數據,而不是訪問數據庫中的實時數據。請考慮以下示例:
    // 調用一個在 ProductEntity 類中定義的方法。
    aProduct.IncreaseUnitPriceBy(1.50);
    

    在上述示例中,調用應用程序對 ProductEntity 對象調用一個名爲 IncreaseUnitPriceBy 的方法。在調用應用程序對 ProductDALC 對象調用相應方法,從而將 ProductEntity 對象保存到數據庫之前,這一更改並不是永久性的。

  • 構建複雜系統的模型。在構建複雜域問題(在不同業務實體之間存在很多交互)的模型時,可以定義自定義實體類,從而將複雜性隱藏在經過很好定義的類接口的後面。
  • 本地化驗證。自定義實體類可以在其屬性存取器中執行簡單的驗證測試以檢測無效的業務實體數據。有關詳細信息,請參閱如何在業務實體組件的屬性存取器中驗證數據
  • 專用字段。您可以隱藏不希望向調用程序公開的信息。

定義自定義實體的缺點如下:

  • 業務實體集合。自定義實體表示的是單個業務實體,而不是一個業務實體集合。要保存多個業務實體,調用應用程序必須創建一個數組或一個 .NET 集合。
  • 序列化。您必須在自定義實體中實現自己的序列化機制。可以使用屬性來控制實體組件的序列化方式,也可以通過實現 ISerializable 接口來控制自己的序列化。
  • 表示業務實體中的複雜關係和層次結構。您必須在業務實體組件中實現自己的關係和層次結構表示機制。如前面所述,DataSet 通常是實現這一目的的最簡單方式。
  • 搜索和排序數據。您必須定義自己的機制來支持實體的搜索和排序。例如,可以通過實現 IComparable 接口以便將實體組件保存在一個 SortedList 集合或 Hashtable 集合中。
  • 部署。您必須在所有物理層部署包含自定義實體的程序集。
  • 支持企業服務 (COM+) 客戶端。如果一個自定義實體將由 COM+ 客戶端使用,則必須爲包含該實體的程序集提供一個嚴格名稱,並且必須在客戶端計算機上註冊。通常,該程序集安裝在全局程序集緩存中。
  • 可擴展性問題。如果修改了數據庫架構,則可能需要修改自定義實體類並重新部署程序集。

定義帶有 CRUD 行爲的自定義業務實體組件

在定義一個自定義實體時,可以提供方法以完全封裝對基礎數據訪問邏輯組件的 CRUD 操作。這是比較傳統的面向對象的方法,可能適用於複雜的對象域。客戶端應用程序不再直接訪問數據訪問邏輯組件類,而是創建一個實體組件並對該實體組件調用 CRUD 方法。這些方法將調用基礎的數據訪問邏輯組件。

圖 10 所示爲帶有 CRUD 行爲的自定義實體類的作用。

單擊此處查看大圖像

圖 10:帶有 CRUD 行爲的自定義業務實體組件的作用(單擊縮略圖以查看大圖像)

定義帶有 CRUD 行爲的自定義實體類的優點如下:

  • 封裝。自定義實體可以封裝由基礎數據訪問邏輯組件定義的操作。
  • 與調用程序的接口。調用程序必須只處理一個接口來保持業務實體數據。不必直接訪問數據訪問邏輯組件。
  • 專用字段。您可以隱藏不希望向調用程序公開的信息。

定義帶有 CRUD 行爲的自定義實體類的缺點如下:

  • 處理業務實體集合。自定義實體中的方法屬於單個業務實體實例。要支持業務實體集合,可以定義靜態方法以讀取或返回一個數組或一個實體組件集合。
  • 開發時間長。傳統的面向對象方法通常比使用現有對象(如 DataSet)需要更多的設計和開發工作。

表示數據和在層間傳遞數據的建議

在您的應用程序中表示數據的方式以及在層間傳遞數據的方式不一定要相同。然而,一套一致而有限的格式能夠降低對附加轉換層的需要,從而提高性能並方便維護。

應根據自己特定的應用程序要求和操作數據的方式選擇數據格式。這裏並沒有一個通用的表示方式,特別是由於當今的許多應用程序都需要支持多個調用程序。然而,我們還是建議遵循以下一般原則:

  • 如果您的應用程序主要處理集合並需要排序、搜索和數據綁定等功能,則建議採用 DataSet。但如果應用程序處理實例數據,則採用標量值的效果會更好。
  • 如果您的應用程序主要處理實例數據,則自定義業務實體組件可能是最佳選擇,因爲它們可以消除一個 DataSet 表示一行時的系統開銷。
  • 大多數情況下,應把應用程序設計爲使用 XML 文檔、DataSet 等以數據爲中心的格式。可以利用 DataSet 提供的靈活性及固有功能來更方便地支持多個客戶端、減少自定義代碼的數量並使用爲大多數開發人員所熟知的編程 API。雖然以面向對象的方式操作數據有很多好處,但自定義編碼複雜的業務實體會使開發和維護成本隨所提供功能的數量成比例增加。

事務處理

當今的大多數應用程序都需要支持事務處理以保持系統數據的完整性。事務處理的管理方法有多種,但每種方法都可歸於以下兩種基本編程模型之一:

  • 手動事務處理。直接在組件代碼或存儲過程中編寫使用 ADO.NET 或 Transact-SQL 事務處理支持功能的代碼。
  • 自動事務處理。使用企業服務 (COM+) 爲 .NET 類添加聲明屬性以便在運行時指定對象的事務性要求。使用這種模型可以方便地配置多個組件以執行同一事務中的工作。

本節提供一些指導原則和建議,幫助您在數據訪問邏輯組件和業務實體組件中實現事務處理支持。有關 .NET 中的事務處理示例和更深入的討論,請參閱 .NET Data Access Architecture Guide

實現事務處理

在大多數環境中,事務處理的根本是業務過程而不是數據訪問邏輯組件或業務實體組件。這是因爲業務過程一般要求事務處理跨多個業務實體而不僅僅是單個業務實體。

然而,也可能出現在沒有高層次業務過程的幫助下對單個業務實體執行事務性操作的情況。例如,要把一個新客戶添加到前面討論的數據庫中,您必須執行以下操作:

  • 在 Customer 表中插入新的一行。
  • 在 Address 表中插入新的一行或多行。

只有這兩個操作都成功後客戶纔會被添加到數據庫中。如果 Customer 業務實體不會成爲啓動該事務處理的更大的業務過程的一部分,則應在 Customer 業務實體中使用手動事務處理。手動事務處理不要求與 Microsoft 分佈式事務處理協調器 (DTC) 之間進行任何進程間通信,因此比自動事務處理要快得多。

圖 11 所示爲確定使用手動事務處理還是自動事務處理的方法。由於 COM+ 事務處理的系統開銷,建議將事務處理放到數據庫中並在存儲過程中控制事務性行爲(如果可能)。

圖 11:確定如何實現事務處理

注意:如果從基於 ASP.NET 的客戶端進行調用,並且沒有用於啓動事務處理的業務過程,則您可能會從 ASP.NET 代碼中啓動該事務處理。這種設計並不好;您決不能從基於 ASP.NET 的客戶端啓動事務處理,而應將數據的展示與業務過程相分離。此外,由於網絡滯後等問題還會導致性能問題,因爲這是要實際部署在其他層上的最常見的層。

在數據訪問邏輯組件中使用手動事務處理的建議

在數據訪問邏輯組件中實現手動事務處理時,請考慮以下建議:

  • 儘可能在存儲過程中執行處理。使用 Transact-SQL 語句 BEGIN TRANSACTION、END TRANSACTION 和 ROLLBACK TRANSACTION 控制事務處理。有關代碼示例,請參閱 .NET Data Access Architecture Guide 中的“How To Perform Transactions With Transact-SQL”。
  • 如果沒有使用存儲過程,並且也不會從業務過程中調用數據訪問邏輯組件,則可以使用 ADO.NET 來編程控制事務處理。有關代碼示例,請參閱 .NET Data Access Architecture Guide 中的“How to Code ADO.NET Manual Transactions”。

在數據訪問邏輯組件中使用自動事務處理的建議

雖然 COM+ 事務處理會帶來一些系統開銷,但自動事務處理能夠提供比手動事務處理更簡單的編程模式,而且在事務處理跨多個分佈式數據源(與 DTC 一起工作)時必須使用自動事務處理。在數據訪問邏輯組件中實現自動事務處理時,請考慮以下建議:

  • 數據訪問邏輯組件必須是從 System.EnterpriseServices 命名空間中的 ServicedComponent 類繼承而來。注意,使用 COM+ 服務註冊的所有程序集都必須具有嚴格的名稱。有關嚴格命名的程序集的詳細信息,請參閱 Creating and Using Strong-Named Assemblies
  • 使用 Transaction(TransactionOption.Supported) 屬性註釋數據訪問邏輯組件,以便可以在同一組件中執行讀寫操作。與 Transaction(TransactionOption.Required) 不同,此選項在不需要事務處理時避免了不必要的系統開銷,而前者始終會要求事務處理。

以下代碼示例顯示瞭如何在數據訪問邏輯組件類中支持自動事務處理:

using System.EnterpriseServices;

[Transaction(TransactionOption.Supported)]
public class CustomerDALC : ServicedComponent
{
  ...
}

如果使用自動事務處理,則數據訪問邏輯組件應在事務處理中表明操作是否成功。如果要隱式表明,應使用 AutoComplete 屬性註釋您的方法並在操作失敗時發出一個異常。如果要顯式表明,應對 ContextUtil 類調用 SetComplete 或 SetAbort 方法。

有關自動事務處理的詳細信息,請參閱 .NET Data Access Architecture Guide 中的“Using Automatic Transactions”。

在業務實體組件中使用自動事務處理

在實現帶有行爲的自定義業務實體組件時,可以使用自動事務處理來指定這些對象的事務性行爲。有關使用自動事務處理指定業務實體組件事務性行爲的建議與前述有關在數據訪問邏輯組件中實現自動事務處理的建議相同。

注意:如果業務實體組件不包含任何要求其在事務處理中表明是否成功的業務邏輯,則它可以忽略事務處理環境。自定義業務實體組件不需要從 ServicedComponent 繼承;事務處理環境仍將繼續其流程,但實體組件將忽略事務處理環境。

驗證

您可以在應用程序的許多層上進行數據驗證。各層適用不同的驗證類型:

  • 在提交數據之前,客戶端應用程序可以在本地驗證業務實體數據。
  • 使用 XSD 架構接收業務文檔時,業務過程可以驗證這些文檔。
  • 數據訪問邏輯組件和存儲過程可以驗證數據,以確保引用的完整性並強制遵循約束以及重要的業務規則。

常用驗證有兩種:

  • 即時點驗證。這是在一個特定時點執行的驗證。例如,在接收 XML 文檔時由業務過程對其進行驗證。
  • 連續驗證。這是在應用程序的許多不同層次上持續進行的一種驗證。連續驗證的示例包括:
    • 用戶界面可以指定字段的最大長度以防止用戶輸入過長的字符串。
    • DataSet 可以指定數據列的最大長度。
    • 自定義業務實體組件可以對實體數據執行範圍檢查、長度檢查、非空檢查以及其他簡單測試。
    • 數據訪問邏輯組件、存儲過程和數據庫本身可以執行類似的測試,以便在將數據保存到數據庫之前確保其有效性。

有時,您可能希望實現額外的聚合過程或轉換過程。這種方法在驗證和轉換經常變化時可能很有用,但會損失性能。例如,如果一個 ISV 想要使用相同的組件支持數據庫架構的兩個版本,則您可以創建一個單獨的組件來執行兩個數據庫架構版本之間的驗證和轉換。

如何使用 XSD 架構驗證 XML

要使用 XSD 架構驗證 XML 文檔,請執行以下步驟:

  1. 創建一個 XmlValidatingReader 對象作爲 XmlTextReader 對象的包裝,如以下代碼所示:
    ' 創建 XmlValidatingReader 對象,以讀取和驗證 Product.xml
    XmlTextReader tr = new XmlTextReader("Product.xml");
    XmlValidatingReader vr = new XmlValidatingReader(tr);
    
  2. 通過使用 ValidationType 枚舉指定所需的驗證類型。.NET Framework 支持三種類型的 XML 驗證:
    • 文檔類型定義 (DTD);指定 ValidationType.DTD
    • Microsoft XML 精簡數據 (XDR) 架構;指定 ValidationType.XDR
    • W3C 標準 XSD 架構;指定 ValidationType.Schema

      以下代碼顯示了 ValidationType 枚舉的使用:

      vr.ValidationType = ValidationType.Schema; ' 指定 XSD 架構驗證
      
  3. 註冊一個驗證事件處理程序方法,如以下代碼所示:
    vr.ValidationEventHandler += new ValidationEventHandler(MyHandlerMethod);
    
  4. 提供一個驗證事件處理程序方法的實現,如以下代碼所示:
    public void MyHandlerMethod(object sender, ValidationEventArgs e)
    {
       Console.WriteLine("驗證錯誤:" + e.Message);
    }
    
  5. 讀取和驗證文檔,如以下代碼所示。驗證錯誤將被驗證事件處理程序方法拾取。
    try
    {
       while (vr.Read())
       {
          // 適當處理 XML 數據...
       }
    }
    catch (XmlException ex)
    {
       Console.WriteLine("XmlException: " + ex.Message);
    }
    vr.Close();
    

如何在業務實體組件的屬性存取器中驗證數據

以下代碼片段顯示瞭如何在自定義實體的屬性存取器中進行簡單驗證。如果驗證測試失敗,您可以發出一個異常以顯示問題的性質。也可以在屬性存取器集合中使用正則表達式來驗證特定的數據和格式。

public class ProductDALC
{
 ...
  public short ReorderLevel
  {
    get { return reorderLevel; }
  }
  set
  {
    if (value < 0)
    {
      throw new ArgumentOutOfRangeException("ReorderLevel 不能爲負數。");
    }
    reorderLevel = value;
  }

  // 加上 ProductDALC 類中的其他成員...
}

異常管理

當 .NET 應用程序出現錯誤時,通常的建議是發出異常而不是從方法返回錯誤值。這一建議暗示了您編寫數據訪問邏輯組件和業務實體組件的方式。異常大體上有兩種:

  • 技術異常,它包括:
    • ADO.NET
    • 數據庫連接
    • 資源(如數據庫、網絡共享、消息隊列等)不可用
  • 業務邏輯異常,它包括:
    • 驗證錯誤
    • 實現業務邏輯的存儲過程中的錯誤

在數據訪問邏輯組件中管理異常的建議

數據訪問邏輯組件應該傳播異常,並且僅在能夠使客戶端對異常的管理更加容易時才包裝異常類型。將異常包裝爲兩種主要異常類型(技術異常和業務異常)有利於各種可能的調用程序的異常處理結構和異常發佈邏輯。

您的應用程序應當發佈異常信息。可以將技術異常發佈到一個由系統管理員或 Windows 管理規範 (WMI) 監視工具(如 Microsoft Operations Manager)監視的日誌中;將業務異常發佈到一個特定的應用程序日誌中。通常,應允許從數據訪問邏輯組件傳播異常並允許由調用程序發佈異常,以便您瞭解異常的整個環境。

以下示例說明了這些建議:

public class CustomerDALC
{
  public void UpdateCustomer(Dataset aCustomer)
  {
    try
    {
      // 更新數據庫中的客戶...
    }
    catch (SqlException se)
    {
      // 捕獲幷包裝異常,然後重新發出
      throw new DataAccessException("數據庫不可用", se);
    }
    finally
    {
      // 清除代碼
    }
  }
}

在業務實體組件中管理異常的建議

業務實體組件應當向調用程序傳播異常。在業務實體組件執行驗證或者當調用程序試圖執行某一操作而未提供該操作所需的數據時,業務實體組件也可以產生異常。

以下示例顯示了業務實體組件如何產生異常。在此示例中,如果沒有提供客戶的名字,Update 方法將發出一個異常:

public class CustomerEntity
{
  public void Update()
  {
   // 檢查用戶已提供了所需數據。這裏是客戶
   // 的名字
    if (FirstName == "" )
    {
       // 發出一個已定義的新的應用程序異常
       throw new MyArgumentException("您必須提供名字。");
    }
    ...
  }
}

有關在 .NET 應用程序中處理異常的詳細信息,請參閱 Exception Management in .NET。可以從 Exception Management Application Block 提供的 ApplicationException 類或 BaseApplicationException 類中繼承自定義技術異常和自定義業務異常。

授權與安全性

本節說明如何將安全性應用於數據訪問邏輯組件和業務實體組件。.NET 公共語言運行庫使用權限對象實現其對託管代碼的強制限制機制。權限對象有三種,各自具有特定的用途:

  • 代碼訪問安全性。這些權限對象用於防止未經授權使用資源和操作。
  • 身份標識。這些權限對象指定運行程序集時所必需的身份標識特徵。
  • 基於角色的安全性。這些權限對象提供了一個機制,用於判斷用戶(或用戶的代理人)是否具有特定身份標識,或者是否是指定角色的成員。PrincipalPermission 對象是唯一基於角色的安全性權限對象。

託管代碼可以使用 Principal 對象(包含對 Identity 對象的引用)來判斷當事人的身份標識或角色。把 Identity 對象和 Principal 對象與用戶、組帳戶等大家所熟悉的概念比較可能會更容易理解。在 .NET Framework 中,Identity 對象表示用戶,而角色表示成員身份和安全性環境。Principal 對象封裝了 Identity 對象和角色。.NET Framework 中的應用程序根據 Principal 對象的身份標識或角色成員身份(後者更常見)授予 Principal 對象權限。

有關 .NET 中的權限與安全性的詳細信息,請參閱 Key Security Concepts

數據訪問邏輯組件中的安全性建議

數據訪問邏輯組件的設計目的是供其他應用程序組件使用,它也是您的應用程序代碼中在調用程序可以訪問數據之前實現安全性的最後一個地方。

通常,數據訪問邏輯組件可以依賴於由調用程序設置的安全性環境。然而,有些情況下數據訪問邏輯組件必須執行自己的授權檢查,以確定是否允許當事人執行所請求的操作。授權在身份驗證後進行,並使用當事人身份標識與角色等有關信息來確定該當事人可以訪問的資源。

在以下情況下,應在數據訪問邏輯組件層次上執行授權檢查:

  • 需要與不完全信任的業務過程開發人員共享數據訪問邏輯組件
  • 需要保護對數據存儲提供的強大功能的訪問

在定義了 Identity 對象和 Principal 對象後,可以用三種方式執行基於角色的安全性檢查:

  • 使用 PrincipalPermission 對象執行強制性安全性檢查。
  • 使用 PrincipalPermissionAttribute 屬性執行說明性安全性檢查。
  • 使用 Principal 對象中的屬性和 IsInRole 方法執行顯式安全性檢查。

以下代碼示例顯示瞭如何使用 PrincipalPermissionAttribute 爲數據訪問邏輯組件類中的方法指定基於角色的聲明性安全性檢查:

using System;
using System.Security.Permissions;

public class CustomerDALC 
{

  public CustomerDALC()
  {
  }

  // 使用 PrincipalPermissionAttribute 要求此方法的調用程序
  // 具有一個名爲“MyUser”的身份標識,並且屬於角色“Administrator”。
  [PrincipalPermissionAttribute(SecurityAction.Demand,
                                Name="MyUser", Role="Administrator")]
  public void DeleteCustomer(string customerID)
  {
    // 在此處刪除客戶代碼 
  }
}

以下代碼顯示瞭如何創建具有所需身份標識和角色的 Principal 對象,以便對 CustomerDALC 對象調用 DeleteCustomer 方法:

using System;
using System.Security.Principal;
using System.Threading;

public class MainClass
{
  public static int Main(string[] args)
  {
    Console.Write("用戶名:");
    string UserName = Console.ReadLine();

    Console.Write("密碼:");
    string Password = Console.ReadLine();

    if (Password == "password" && UserName == "MyUser")
    {
      // 創建一個名爲“MyUser”的通用身份標識
      GenericIdentity MyIdentity = new GenericIdentity("MyUser");

      // 創建角色
      String[] MyString = {"Administrator", "User"};

      // 創建一個通用當事人
      GenericPrincipal MyPrincipal = new GenericPrincipal(MyIdentity,
MyString);
      
      // 設置此線程的當前當事人,以用於基於角色的安全性
      Thread.CurrentPrincipal = MyPrincipal;
    }

    // 創建一個 CustomerDALC 對象,並嘗試調用它的 DeleteCustomer 方法。
    // 僅在當前當事人的身份標識和角色合格時這一步驟才能成功。
    CustomerDALC c = new CustomerDALC();
    c.DeleteCustomer("VINET");
  }
}

Windows 身份驗證

理想情況下,在連接到數據庫時應使用 Windows 身份驗證而不是 SQL Server 身份驗證。然而,應使用服務帳戶並避免模擬連接到數據庫,因爲它會妨礙連接池。連接池需要相同的連接字符串;如果嘗試使用不同的連接字符串打開數據庫,就會創建單獨的連接池,而這將限制可縮放性。

有關 Windows 身份驗證和連接池的詳細信息,請參閱 .NET Data Access Architecture Guide 中的“Managing Database Connections”。

安全通信建議

要實現調用應用程序與數據訪問邏輯組件之間的安全通信,請考慮以下建議:

  • 如果數據訪問邏輯組件是通過各種層的線路調用的,並且信息交換包含需要保護的機密信息,則應使用分佈式組件對象模型 (DCOM)、安全套接字層 (SSL)、安全 Internet 協議 (IPSec) 等安全通信技術。
  • 如果數據是加密存儲在數據庫中,則通常由數據訪問邏輯組件負責數據的加密與解密。如果信息暴露會導致巨大損害,則必須考慮保護與數據訪問邏輯組件進行通信的通道。

業務實體組件中的安全性建議

如果將業務實體實現爲數據結構(如 XML 或 DataSet),則不需要實現安全性檢查。然而,如果將業務實體實現爲帶有 CRUD 操作的自定義業務實體組件,請考慮以下建議:

  • 如果將實體提供給您不完全信任的業務過程,應在業務實體組件和數據訪問邏輯組件中實現授權檢查。然而,如果在這兩個層次上都實現檢查,可能會產生保持安全性策略同步的維護問題。
  • 業務實體組件不應處理通信安全性或數據加密,應把這些任務留給相應的數據訪問邏輯組件。

部署

本節提供一些建議以幫助您確定如何部署數據訪問邏輯組件和業務實體組件。

部署數據訪問邏輯組件

部署數據訪問邏輯組件的方法有兩種:

  • 與業務過程對象一起部署數據訪問邏輯組件。這種部署方法具有最佳的數據傳輸性能,還有一些額外的技術優勢:
    • 事務處理可以在業務過程對象和數據訪問邏輯組件之間無縫流動。然而,事務處理不能跨越遠程通道無縫流動。在遠程方案下,需要使用 DCOM 來實現事務處理。此外,如果業務過程與數據訪問邏輯組件被防火牆分開,還需要打開這兩個物理層之間的防火牆端口以啓用 DTC 通信。
    • 一起部署業務過程對象和數據訪問邏輯組件可以減少事務處理失敗節點的數目。
    • 安全性環境自動在業務過程對象和數據訪問邏輯組件之間流動,無需設置當事人對象。
  • 與用戶界面代碼一起部署數據訪問邏輯組件。有時需要直接從 UI 組件和 UI 過程組件使用數據訪問邏輯組件。爲提高 Web 方案下的性能,可以與 UI 代碼一起部署數據訪問邏輯組件;這種部署方法可以使 UI 層充分利用數據讀取器流以獲得最佳性能。然而,在使用這種部署方法時必須牢記以下事項:
    • 不與 UI 代碼一起部署數據訪問邏輯組件的一個常見原因是防止通過 Web 領域直接對數據源進行網絡訪問。
    • 如果您的 Web 領域部署在 DMZ 環境中,則必須打開防火牆端口才能訪問 SQL Server。如果使用 COM+ 事務處理,還必須爲 DTC 通信打開其他的防火牆端口。有關詳細信息,請參閱 .NET Data Access Architecture Guide

部署業務實體

應用程序的許多不同層都要使用業務實體。根據業務實體的實現方式,如果您的應用程序跨越各個物理層,則需要將業務實體部署到多個位置。下面列出了在不同實現方案中部署業務實體的方法:

  • 部署作爲有類型的 DataSet 實現的業務實體。有類型的 DataSet 類必須由數據訪問邏輯組件和調用應用程序訪問。因此,建議在一個要部署在多個層的公共程序集中定義有類型的 DataSet 類。
  • 部署作爲自定義業務實體組件實現的業務實體。根據數據訪問邏輯組件中定義的方法簽名,自定義實體類可能需要由數據訪問邏輯組件訪問。請遵循與有類型的 DataSet 相同的建議,即在一個要部署在多個層的公共程序集中定義自定義實體類。
  • 部署作爲通用 DataSet 或 XML 字符串實現的業務實體。通用 DataSet 和 XML 字符串不表示單獨的數據類型。以這兩種格式實現的業務實體不存在部署問題。

附錄

如何定義數據訪問邏輯組件類
如何使用 XML 表示數據的集合和層次結構
如何在 .NET 應用程序中編程應用樣式表
如何創建有類型的 DataSet
如何定義業務實體組件
如何表示業務實體組件中數據的集合和層次結構
如何將業務實體組件綁定到用戶界面控件
如何在業務實體組件中提供事件
如何將業務實體組件序列化爲 XML 格式
如何將業務實體組件序列化爲 SOAP 格式
如何將業務實體組件序列化爲二進制格式

如何定義數據訪問邏輯組件類

以下代碼示例定義一個名爲 CustomerDALC 的類,它是用於 Customer 業務實體的數據訪問邏輯組件類。CustomerDALC 類爲 Customer 業務實體實現 CRUD 操作,並提供了其他方法爲此對象封裝業務邏輯。

public class CustomerDALC
{
  private string conn_string;

  public CustomerDALC()
  {
    // 從安全或加密的位置獲取連接字符串
    // 並將其分配給 conn_string
  }

  public CustomerDataSet GetCustomer(string id)
  {
    // 檢索包含 Customer 數據的有類型的 DataSet
  }

  public string CreateCustomer(string name,
                               string address, string city, string state,
string zip)
  {
    // 根據傳遞給此方法的標量參數,在數據庫中創建一個
    // 新客戶。
    // 從此方法返回 customerID。
  }

  public void UpdateCustomer(CustomerDataSet updatedCustomer)
  {
    // 根據作爲類型 CustomerDataSet 的參數發送的 Customer 數據,更新
    // 數據庫。
  }

  public void DeleteCustomer(string id)
  {
    // 刪除具有指定 ID 的客戶
  }

  public DataSet GetCustomersWhoPurchasedProduct(int productID)
  {
    // 使用通用 DataSet 檢索客戶,因爲此方法
    // 不需要檢索與客戶關聯的全部信息
  }
}

如何使用 XML 表示數據的集合和層次結構

以下示例顯示瞭如何在 XML 文檔中表示數據的集合和層次結構。該 XML 文檔表示客戶的一個訂單;注意,元素 <OrderDetails> 包含一個該訂單的詳細信息集合。

<Order xmlns="urn:aUniqueNamespace">
  <OrderID>10248</OrderID>
  <CustomerID>VINET</CustomerID>
  <OrderDate>1996-07-04</OrderDate>
  <ShippedDate>1996-07-16</ShippedDate>
  <OrderDetails>
    <OrderDetail>
      <ProductID>11</ProductID>
      <UnitPrice>14.00</UnitPrice>
      <Quantity>12</Quantity>
    </OrderDetail>
    <OrderDetail>
      <ProductID>42</ProductID>
      <UnitPrice>9.80</UnitPrice>
      <Quantity>10</Quantity>
    </OrderDetail>
    <OrderDetail>
      <ProductID>72</ProductID>
      <UnitPrice>34.80</UnitPrice>
      <Quantity>5</Quantity>
    </OrderDetail>
  </OrderDetails>
</Order>

如何在 .NET 應用程序中編程應用樣式表

要在 .NET 應用程序中編程應用樣式表,請執行以下步驟:

  1. 導入 System.Xml.Xsl 命名空間,如以下代碼所示。System.Xml.Xsl 命名空間包含 .NET Framework 類庫中的 XSLT 轉換類。
    using System.Xml.Xsl;
    
  2. 創建一個 XslTransform 對象,如以下代碼所示:
    XslTransform stylesheet = new XslTransform();
    
  3. 將所需樣式表加載到 XslTransform 對象,如以下代碼所示:
    stylesheet.Load("MyStylesheet.xsl");
    
  4. 調用 XslTransform 對象的 Transform 方法,如以下代碼所示。調用 Transform 方法指定 XML 源文檔和結果文檔的名稱。
    stylesheet.Transform(sourceDoc, resultDoc);
    

如何創建有類型的 DataSet

可以使用有類型的 DataSet 表示業務實體。創建有類型的 DataSet 的方法有多種:

  • 從 Microsoft Visual Studio ®.NET 中的數據適配器創建
  • 從 Visual Studio .NET 中的 XSD 架構文件創建
  • 使用 XSD 架構定義工具 (xsd.exe) 從 .NET Framework 命令提示窗口創建
注意:也可以編程定義有類型的 DataSet,即從 DataSet 繼承並定義方法、屬性和嵌套類以表示該 DataSet 的結構。最簡單的方法是使用以下過程之一創建一個有類型的 DataSet,然後將此有類型的 DataSet 類用作將來您自己的有類型的 DataSet 類的基礎。

使用數據適配器創建有類型的 DataSet

要使用數據適配器創建有類型的 DataSet,請執行以下步驟:

  1. 在 Visual Studio .NET 中,向您的窗體或組件添加一個數據適配器。在數據適配器的配置嚮導中,指定該數據適配器的連接信息。同時根據具體情況,爲數據適配器的 Select、Insert、Update 和 Delete 命令指定 SQL 字符串或存儲過程。
  2. 在組件設計器中,在數據適配器對象上單擊鼠標右鍵,然後單擊 Generate DataSet(生成 DataSet)。
  3. 在 Generate DataSet(生成 DataSet)對話框中,單擊 New(新建),鍵入新 DataSet 類的名稱,然後單擊 OK(確定)。
  4. 爲確認已創建該有類型的 DataSet,可以在解決方案資源管理器中單擊 Show All Files(顯示所有文件)按鈕。展開 XSD 架構文件的節點,確認存在一個與 XSD 架構相關聯的代碼文件。該代碼文件定義了新的有類型的 DataSet 類。

從 XSD 架構文件創建有類型的 DataSet

要使用 Visual Studio .NET 從 XSD 架構文件創建有類型的 DataSet,請執行以下步驟:

  1. 在 Visual Studio .NET中,創建一個新項目或打開一個現有項目。
  2. 爲項目添加一個現有的 XSD 架構,或在組件設計器中創建一個新的 XSD 架構。
  3. 在解決方案資源管理器中,雙擊 XSD 架構文件,在組件設計器中查看該 XSD 架構。
  4. 在組件設計器中選擇主 XSD 架構元素。
  5. 在 Schema(架構)菜單中,單擊 Generate DataSet(生成 DataSet)。
  6. 爲確認已創建該有類型的 DataSet,可以在解決方案資源管理器中單擊 Show All Files(顯示所有文件)按鈕。展開 XSD 架構文件的節點,確認存在一個與 XSD 架構相關聯的代碼文件。該代碼文件定義了新的有類型的 DataSet 類。

使用 XSD 架構定義工具 (xsd.exe) 創建有類型的 DataSet

XML 架構定義工具可以從 XSD 架構文件、XDR 架構文件或 XML 實例文檔生成有類型的 DataSet。以下命令使用名爲 XsdSchemaFile.xsd 的 XSD 架構文件,在當前目錄中名爲 XsdSchemaFile.cs 的 Visual C# 源文件中生成一個有類型的 DataSet:

xsd /dataset /language:C# XsdSchemaFile.xsd

有關詳細信息,請參閱 Generating a Strongly Typed DataSet

如何定義業務實體組件

以下示例顯示瞭如何爲 Product 業務實體定義自定義實體類:

public class ProductEntity
{
  // 專用字段,用於保存 Product 實體的狀態
  private int productID;
  private string productName;
  private string quantityPerUnit;
  private decimal unitPrice;
  private short unitsInStock;
  private short unitsOnOrder;
  private short reorderLevel;

  // 公共屬性,用於公開 Product 實體的狀態
  public int ProductID
  {
    get { return productID; }
    set { productID = value; }
  }
  public string ProductName
  {
    get { return productName; }
    set { productName = value; }
  }
  public string QuantityPerUnit
  {
    get { return quantityPerUnit; }
    set { quantityPerUnit = value; }
  }
  public decimal UnitPrice
  {
    get { return unitPrice; }
    set { unitPrice = value; }
  }
  public short UnitsInStock
  {
    get { return unitsInStock; }
    set { unitsInStock = value; }
  }
  public short UnitsOnOrder
  {
    get { return unitsOnOrder; }
    set { unitsOnOrder = value; }
  }
  public short ReorderLevel
  {
    get { return reorderLevel; }
    set { reorderLevel = value; }
  }

  // 執行本地化處理的方法和屬性
  public void IncreaseUnitPriceBy(decimal amount)
  {
    unitPrice += amount;
  }
  public short UnitsAboveReorderLevel
  {
    get { return (short)(unitsInStock - reorderLevel); }
  }
  public string StockStatus
  {
    get 
    { 
      return "庫存:"+ unitsInStock + ",訂購:" + unitsOnOrder;
    }
  }
}

如何表示業務實體組件中數據的集合和層次結構

以下示例顯示瞭如何爲 Order 業務實體定義自定義實體類。每個訂單都包含許多訂購項目,這些訂購項目保存在 OrderEntity 類的一個 DataSet 中。

public class OrderEntity
{
  // 專用字段,用於保存訂單信息
  private int orderID;
  private string customerID;
  private DateTime orderDate;
  private DateTime shippedDate;

  // 專用字段,用於保存訂單詳細信息
  private DataSet orderDetails;

  // 公共屬性,用於提供訂單信息
  public int OrderID
  {
    get { return orderID; }
    set { orderID = value; }
  }
  public string CustomerID
  {
    get { return customerID; }
    set { customerID = value; }
  }
  public DateTime OrderDate
  {
    get { return orderDate; }
    set { orderDate = value; }
  }
  public DateTime ShippedDate
  {
    get { return shippedDate; }
    set { shippedDate = value; }
  }

  // 公共屬性,用於提供訂單詳細信息
  public DataSet OrderDetails
  {
    get { return orderDetails; }
    set { orderDetails = value; }
  }

  // 附加方法,用於簡化對訂單詳細信息的訪問
  public bool IsProductOrdered(int productID)
  {
    // 必須在 DataTable 中定義主關鍵字列
    DataRow row = orderDetails.Tables[0].Rows.Find(productID);
    
    if (row != null)
  return true;
    else
  return false;
  }

  // 附加屬性,用於簡化對訂單詳細信息的訪問
  public int NumberOfOrderItems
  {
    get
    {
      return orderDetails.Tables[0].Rows.Count;
    }
  }
} 

關於 OrderEntity 類,請注意以下幾點:

  • 該類包含用於保存有關訂單的信息的專用字段。還有一個專用 DataSet 字段,用於保存訂單的其他詳細信息。數據訪問邏輯組件將在創建 OrderEntity 對象時填充所有這些字段。
  • 該類包含用於提供有關訂單的信息的公共屬性。此外還有一個用於提供該 DataSet 的屬性,以便使調用應用程序能夠訪問訂單詳細信息。
  • 該類包含一個附加方法和一個附加屬性,用於簡化對訂單詳細信息的訪問:
    • IsProductOrdered 方法接收一個 ProductID 參數,並返回一個布爾值以表明該產品是否出現在訂單中。
    • NumberOfOrderItems 屬性表明訂單中的訂購行數目。

如何將業務實體組件綁定到用戶界面控件

可以將用戶界面控件綁定到 Windows 窗體和 ASP.NET 應用程序中的自定義實體。有兩種可能的方案:

  • 在用戶界面控件上綁定單個業務實體。以下代碼示例顯示瞭如何從 OrderDALC 對象獲取一個 OrderEntity 對象並將其綁定到 Windows 窗體的控件上。當用戶更改這些控件中的值時,基礎 OrderEntity 對象中的數據也將自動更改。
    // 創建 OrderDALC 對象。
    OrderDALC dalcOrder = new OrderDALC();
    
    // 使用 dalcOrder 爲訂單 ID 10248 獲取一個 OrderEntity 對象。 
    // 此代碼假設 OrderDALC 類有一個名爲 GetOrder() 的方法,
    // 該方法爲特定訂單 ID 返回一個 OrderEntity 對象。
    OrderEntity order = dalcOrder.GetOrder(10248);
    
    // 將 OrderEntity 的 OrderID 屬性綁定到 TextBox 控件。
    textBox1.DataBindings.Add("Text", order, "OrderID");
    
    // 將 OrderEntity 的 CustomerID 屬性綁定到另一個 TextBox 控件。 
    control.
    textBox2.DataBindings.Add("Text", order, "CustomerID");
    
    // 將 OrderEntity 的 OrderDate 屬性綁定到 DatePicker 控件。
    dateTimePicker1.DataBindings.Add("Value", order, "OrderDate");
    
    // 將 OrderEntity 的 ShippedDate 屬性綁定到另一個 DatePicker 控件。
    dateTimePicker2.DataBindings.Add("Value", order, "ShippedDate");
    
    // 將 OrderEntity 的 OrderDetails DataSet 綁定到 DataGrid 控件。
    // DataGrid 分別用網格中的一行顯示 DataSet 的各個 DataRow。
    dataGrid1.DataSource = order.OrderDetails.Tables[0].DefaultView;
    

    準備好後,您可以將修改後的 OrderEntity 對象傳遞給 OrderDALC,以便將數據保存到數據庫中,如以下代碼所示。

    // 通過 dalcOrder 將 OrderEntity 對象保存到數據庫中。
    // 此代碼假設 OrderDALC 類有一個名爲 UpdateOrder() 的方法,
    // 該方法接收一個 OrderEntity 參數,並更新數據庫中的相應項
    dalcOrder.UpdateOrder(order);
    
  • 將業務實體集合綁定到 DataGrid 控件。以下代碼示例顯示瞭如何從 OrderDALC 獲取一個 OrderEntity 對象數組並將其綁定到 Windows 窗體的 DataGrid 控件。DataGrid 分別用網格中的一行顯示每個數組元素(即每個 OrderEntity 對象)。
    // 創建 OrderDALC 對象。
    OrderDALC dalcOrder = new OrderDALC();
    
    // 使用 dalcOrder 獲取客戶“VINET”的 OrderEntity 對象數組。
    // 此代碼假設 OrderDALC 類有一個名爲
    GetOrdersForCustomer(),
    // 的方法,該方法返回特定客戶的 OrderEntity 對象數組。
    OrderEntity[] orderEntities = dalcOrder.GetOrdersForCustomer("VINET");
    
    // 將該數組綁定到 DataGrid 控件。
    dataGrid1.DataSource = orderEntities;
    

    準備好後,您可以將修改後的數組傳遞給 OrderDALC,以便將數據保存到數據庫中,如以下代碼所示:

    // 通過 dalcOrder 將 OrderEntity 對象保存到數據庫中。
    // 此代碼假設 OrderDALC 類有一個名爲 UpdateOrder() 的方法,該方法獲取
    // 一個 OrderEntity 對象數組,並更新數據庫中的相應項。
    dalcOrder.UpdateOrders(orderEntities);
    

如何在業務實體組件中提供事件

自定義實體可以在業務實體狀態修改時產生事件。這些事件可用於獲得豐富的客戶端用戶界面設計,因爲這使得無論數據顯示在哪裏都可以對其進行刷新。以下代碼示例顯示瞭如何在 OrderEntity 類中產生業務實體相關事件:

// 爲所有業務實體事件定義公用事件類
public class EntityEventArgs : EventArgs
{
  // 定義事件成員,用於提供有關事件的信息
}

// 定義一個代理,用於爲業務實體相關事件指定簽名
public delegate void EntityEventHandler(Object source, EntityEventArgs e);

// 定義自定義實體類,它可以在業務實體狀態改變時產生事件
public class OrderEntity
{
  // 定義業務實體狀態改變的“before”事件和“after”事件
  public event EntityEventHandler BeforeChange, AfterChange;

  // 專用字段,用於保存業務實體的狀態
  private int orderID;
  private int customerID;
  private DateTime orderDate;
  private DateTime shippedDate;
  private DataSet orderDetails;

  // 公共屬性,用於提供業務實體的狀態
  public int OrderID
  {
    get { return orderID; }
    set
    { 
      BeforeChange(this, new EntityEventArgs());   // 產生“before”事件
      orderID = value;
      AfterChange(this, new EntityEventArgs());    // 產生“after”事件
    }
  }
  public int CustomerID
  {
    get { return customerID; }
    set
    { 
      BeforeChange(this, new EntityEventArgs());   // 產生“before”事件
      customerID = value;
      AfterChange(this, new EntityEventArgs());    // 產生“after”事件
    }
  }
  public DateTime OrderDate
  {
    get { return orderDate; }
    set
    {
      BeforeChange(this, new EntityEventArgs());   // 產生“before”事件
      orderDate = value;
      AfterChange(this, new EntityEventArgs());    // 產生“after”事件
    }
  }
  public DateTime ShippedDate
  {
    get { return shippedDate; }
    set
    {
      BeforeChange(this, new EntityEventArgs());   // 產生“before”事件
      shippedDate = value;
      AfterChange(this, new EntityEventArgs());    // 產生“after”事件
    }
  }

  // 必要時使用更多成員...
}

關於上述代碼,請注意以下幾點:

  • EntityEvent 類提供有關業務實體相關事件的信息。EntityEventHandler 代理爲自定義實體類產生的所有業務實體相關事件指定簽名。該代理簽名遵循所建議的 .NET Framework 事件處理程序代理的原則。有關在 .NET 中定義和使用事件的原則,請參閱 Event Usage Guidelines
  • OrderEntity 類定義了兩個名爲 BeforeChange 和 AfterChange 的事件。
  • OrderEntity 中的屬性設置器在業務實體狀態改變前產生一個 BeforeChange 事件,在業務實體狀態改變後產生一個 AfterChange 事件。

如何將業務實體組件序列化爲 XML 格式

本節討論以下問題:

  • 使用 XmlSerializer 序列化自定義實體對象
  • XML Web services 中對象的 XML 序列化
  • 序列化自定義實體對象的默認 XML 格式
  • 控制序列化自定義實體對象的 XML 格式

使用 XmlSerializer 序列化自定義實體對象

以下代碼示例顯示瞭如何使用 XmlSerializer 類將 OrderEntity 對象序列化爲 XML 格式:

using System.Xml.Serialization;     // 此命名空間包含 XmlSerializer 類
...
// 創建一個 XmlSerializer 對象,用於序列化 OrderEntity 類型的對象
XmlSerializer serializer = new XmlSerializer(typeof(OrderEntity));

// 將 OrderEntity 對象序列化爲名爲“MyXmlOrderEntity.xml”的 XML 文件
TextWriter writer = new StreamWriter("MyXmlOrderEntity.xml");
serializer.Serialize(writer, order);
writer.Close();

在 XML Web services 中序列化對象

以下代碼示例顯示瞭如何編寫使用自定義實體對象的 XML Web services:

namespace MyWebService
{
  [WebService(Namespace="urn:MyWebServiceNamespace")]
  public class OrderWS : System.Web.Services.WebService
  {
    [WebMethod]
    public OrderEntity GetOrder(int orderID)
    {
     // 創建 OrderDALC 對象
     OrderDALC dalcOrder = new OrderDALC();

     // 使用 dalcOrder 獲取指定訂單 ID 的 OrderEntity 對象。
     // 此代碼假設 OrderDALC 類有一個名爲 GetOrder 的方法,
     // 該方法獲取一個訂單 ID 作爲參數,並返回一個 OrderEntity 對象,
     // 其中包含該訂單的所有數據。
     OrderEntity order = dalcOrder.GetOrder(10248);

      // 返回 OrderEntity 對象, 該對象將自動序列化。
      return order;
    }

    [WebMethod]
    public void UpdateOrder(OrderEntity order)
    {
     // 創建 OrderDALC 對象。
     OrderDALC dalcOrder = new OrderDALC();

     // 使用 dalcOrder 將 OrderEntity 對象的數據保存到數據庫中。
     // 此代碼假設 OrderDALC 類有一個名爲 UpdateOrder 的方法,
     // 該方法接收一個 OrderEntity 對象並將數據保存到數據庫中。
    dalcOrder.UpdateOrder(order);
    }

關於上述代碼,請注意以下幾點:

  • GetOrder 方法接收一個訂單 ID 作爲參數,並返回包含該訂單的數據的 OrderEntity 對象。
  • UpdateOrder 方法接收一個 OrderEntity 對象並將該對象的數據保存到數據庫中。
  • 如果客戶端應用程序調用 GetOrder 和 UpdateOrder 方法,OrderEntity 對象將爲該方法調用自動序列化爲 XML 格式。

序列化自定義實體對象的默認 XML 格式

以下 XML 文檔顯示了 OrderEntity 對象的默認 XML 序列化格式:

<?xml version="1.0" encoding="utf-8"?>
<OrderEntity xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <OrderID>10248</OrderID>
  <CustomerID>VINET</CustomerID>
  <OrderDate>1996-07-04T00:00:00.0000000+01:00</OrderDate>
  <OrderDetails> ... see below ... </OrderDetails>
  <ShippedDate>1996-07-16T00:00:00.0000000+01:00</ShippedDate>
</OrderEntity>

上述文檔說明了 XML 序列化的默認規則:

  • 該 XML 文檔的根元素與類名稱 OrderEntity 相同。
  • OrderEntity 對象中的每個公共屬性(及字段)都被序列化爲具有相同名稱的元素。

OrderEntity 類中的 OrderDetails 屬性是一個 DataSet,DataSet 提供了內置的 XML 序列化支持。OrderDetails DataSet 的序列化結果如下:

<OrderDetails>
  <xs:schema id="NewDataSet" xmlns=""
             xmlns:xs="http://www.w3.org/2001/XMLSchema"
             xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale="en-
      UK">
      <xs:complexType>
        <xs:choice maxOccurs="unbounded">
          <xs:element name="OrderDetails">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="OrderID" type="xs:int" minOccurs="0" />
                <xs:element name="ProductID" type="xs:int" minOccurs="0" />
                <xs:element name="UnitPrice" type="xs:decimal" minOccurs="0"
                  />
                <xs:element name="Quantity" type="xs:short" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
                   xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
    <NewDataSet>
      <OrderDetails diffgr:id="OrderDetails1" msdata:rowOrder="0"
                    diffgr:hasChanges="inserted">
        <OrderID>10248</OrderID>
        <ProductID>11</ProductID>
        <UnitPrice>14</UnitPrice>
        <Quantity>12</Quantity>
      </OrderDetails>
     <OrderDetails diffgr:id="OrderDetails2" msdata:rowOrder="1"
                   diffgr:hasChanges="inserted">
        <OrderID>10248</OrderID>
        <ProductID>42</ProductID>
        <UnitPrice>9.8</UnitPrice>
        <Quantity>10</Quantity>
      </OrderDetails>
      <OrderDetails diffgr:id="OrderDetails3" msdata:rowOrder="2"
                    diffgr:hasChanges="inserted">
        <OrderID>10248</OrderID>
        <ProductID>72</ProductID>
        <UnitPrice>34.8</UnitPrice>
        <Quantity>5</Quantity>
      </OrderDetails>
    </NewDataSet>
  </diffgr:diffgram>
</OrderDetails>

關於 DataSet 的序列化,請注意以下幾點:

  • <xs:schema> 段描述了 DataSet 的結構,包括表、列名稱和列類型。
  • <xs:diffgram> 段包含該 DataSet 的數據。每個 <OrderDetails> 元素表示該 DataSet 中 OrderDetails 表中的單獨一行。

控制序列化自定義實體對象的 XML 格式

您可以在自定義實體類中使用 .NET 屬性來控制屬性和字段序列化爲 XML 的方式。請考慮以下修訂後的 OrderEntity 類:

 [XmlRoot(ElementName="Order", Namespace="urn:MyNamespace")]
public class OrderEntity
{
  [XmlAttribute(AttributeName="ID")]
  public int OrderID {...獲取和設置代碼,同前...}

  [XmlAttribute(AttributeName="CustID")]
  public string CustomerID {...獲取和設置代碼,同前...}

  [XmlElement(ElementName="Ordered")]
  public DateTime OrderDate {...獲取和設置代碼,同前...}

  public DataSet OrderDetails {...獲取和設置代碼,同前...}

  [XmlElement(ElementName="Shipped")
  public DateTime ShippedDate {...獲取和設置代碼,同前...}

  // 必要時使用更多成員...
}
  • 將 OrderEntity 對象序列化爲 XML 後,其格式如下:
<?xml version="1.0" encoding="utf-8" ?>
<Order ID="10248"
       CustID="VINET"
       xmlns="urn:MyNamespace"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Ordered>1996-07-04T00:00:00.0000000+01:00</Ordered>
  <OrderDetails>...詳細代碼同前...</OrderDetails>
  <Shipped>1996-07-16T00:00:00.0000000+01:00</Shipped>
</Order>

有關如何使用屬性來控制 XML 序列化的詳細信息,請參閱 Attributes that Control XML Serialization

如何將業務實體組件序列化爲 SOAP 格式

以下代碼示例顯示瞭如何使用 SoapFormatter 類將 OrderEntity 對象序列化爲 SOAP 格式。當使用 SOAP 協議向或從 XML Web services 傳遞對象,或者當使用 HTTP 遠程通道向或從 Remoting 服務器傳遞對象時,也會發生 SOAP 序列化(隱式)。此外,您也可以在使用 TCP 遠程通道時指定 SOAP 格式化。

using System.Runtime.Serialization.Formatters.Soap;    // 用於 SoapFormatter 類
...
// 創建 SoapFormatter 對象,用於序列化 OrderEntity 類型的對象
SoapFormatter formatter = new SoapFormatter();

// 將 OrderEntity 對象序列化爲名爲“MySoapOrderEntity.xml”的 SOAP (XML) 文件
FileStream stream = File.Create("MySoapOrderEntity.xml");
formatter.Serialize(stream, order);
stream.Close();

要對自定義實體組件使用 SOAP 序列化,必須使用 Serializable 屬性註釋您的實體類,如以下代碼所示:

 [Serializable]
public class OrderEntity
{
  // 成員,同前

如果要自定義序列化過程中生成的 SOAP 格式,實體類必須實現 ISerializable 接口。您必須提供一個 GetObjectData 方法供 SoapFormatter 在序列化過程中調用,並提供一個特殊構造函數供 SoapFormatter 在還原序列化過程中調用以重新創建對象。以下代碼顯示了 ISerializable 接口、GetObjectData 方法和特殊構造函數的使用:

using System.Runtime.Serialization;   // 用於 ISerializable 接口以及相關類型
...
[Serializable]
public class OrderEntity : ISerializable
{
  // 序列化函數,由 SoapFormatter 在序列化過程中調用
  void ISerializable.GetObjectData(SerializationInfo info, StreamingContext
ctxt)
  {
    // 向 SerializationInfo 對象中添加每個字段
    info.AddValue("OrderID", orderID);
    // 必要時使用更多代碼...
  }

  // 還原序列化構造函數,由 SoapFormatter 在還原序列化過程中調用
  public OrderEntity(SerializationInfo info, StreamingContext ctxt)
  {
    // 從 SerializationInfo 對象中還原序列化出各個 OrderEntity 字段
    orderID = (int)info.GetValue("OrderID", typeof(int));
    // 必要時使用更多代碼...
  }
  
  // 其他成員,同前...
}

有關自定義 SOAP 序列化的詳細信息,請參閱 Basic Serialization

如何將業務實體組件序列化爲二進制格式

以下代碼示例顯示瞭如何使用 BinaryFormatter 類將 OrderEntity 對象序列化爲二進制格式。當使用 TCP 遠程通道向或從 Remoting 服務器傳遞對象時,也會發生二進制序列化(隱式)。此外,爲提高性能,您也可以在使用 HTTP 遠程通道時指定二進制格式化。

using System.Runtime.Serialization.Formatters.Binary;    // 用於 BinaryFormatter 類
...
// 創建 BinaryFormatter 對象,用於序列化 OrderEntity 類型的對象
BinaryFormatter formatter = new BinaryFormatter();

// 將 OrderEntity 對象序列化爲名爲“MyBinaryOrderEntity.dat”的二進制文件
FileStream stream = File.Create("MyBinaryOrderEntity.dat");
formatter.Serialize(stream, order);
stream.Close();

要對自定義實體對象使用二進制序列化,必須使用 Serializable 屬性註釋您的自定義實體類。要自定義序列化過程中生成的二進制格式,自定義實體類必須實現 ISerializable 接口。這兩種方案中的詳細代碼與 SOAP 序列化的代碼相同。

有關二進制序列化的詳細信息,請參閱 Binary Serialization

合作者

衷心感謝以下投稿人和審閱人:Luca Bolognese、Mike Pizzo、Keith Short、Martin Petersen - Frey (PSS)、Pablo De Grande、Bernard Chen (Sapient)、Dimitris Georgakopoulos (Sapient)、Kenny Jones、Chris Brooks、Lance Hendrix、Pradyumna Siddhartha、Franco A. Ceruti (VBNext)、Diego Gonzalez (Lagash)、Chris Schoon。

同時,感謝以下內容小組成員:Chris Sfanos、Angela Crocker、Andy Olsen、Sharon Smith。

發佈了44 篇原創文章 · 獲贊 0 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章