適用于:
Microsoft® .NET 應用程序
摘要:
學習向 Microsoft .NET 應用程序公開數據的最佳方式,以及如何實現一個有效的策略以便在分布式應用程序的層間傳遞數據。(本文包含一些指向英文站點的鏈接。)
目錄
簡介
在設計分布式應用程序時需要確定如何訪問和表示與該應用程序相關聯的業務數據。本文提供一些指導原則以幫助您選擇公開數據、保持數據和在應用程序的層間傳遞數據的最佳方式。
圖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:假想的關系數據庫中的表關系
下表總結了示例數據庫中的關系類型。

當定義業務實體以在數據庫中建立信息模型時,應考慮要如何在您的應用程序中使用這些信息。應當標識封裝您的應用程序的功能的核心業務實體,而不是為每個表定義單獨的業務實體。
該假想零售商的應用程序中的典型操作如下:
- 獲。ɑ蚋拢┛蛻舻挠嘘P信息(包括地址)
- 獲取客戶的訂單列表
- 獲取特定訂單的訂購項目列表
- 創建新訂單
- 獲。ɑ蚋拢┮粋或一組產品的有關信息
為滿足這些應用程序要求,該應用程序要處理三個邏輯業務實體: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> 元素。
- 序列化。DataSet 本身支持 XML 序列化,并且可以跨層序列化。
這種方法的缺點如下:
- 性能。實例化和封送處理 DataSet 會增加運行時負擔。
- 表示單個業務實體。DataSet 是為處理一組數據而設計的。如果您的應用程序主要處理實例數據,則標量值或自定義實體是更好的方法,后者不會影響性能。
將自定義業務實體組件作為輸入和輸出傳遞
這種方法的優點如下:
- 維護。更改架構不會影響數據訪問邏輯組件方法簽名。然而,如果業務實體組件包含在嚴格命名的程序集中,就會出現與有類型的 DataSet 同樣的問題。
- 業務實體集合?梢詫⒆远x業務實體組件的數組和集合傳入和傳出方法。
這種方法的缺點如下:
- 支持開放式并發。要方便地支持開放式并發,必須在數據庫中定義時間戳列并將其作為實例數據的一部分。
- 集成限制。當使用自定義業務實體組件作為數據訪問邏輯組件的輸入時,調用程序必須知道業務實體的類型,而這會限制不使用 .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 以跳過對當前行的更新,但是繼續更新該更新命令中的其他行。
使用數據適配器測試并發錯誤的另一種方法是在調用 Update 方法之前把 DataAdapter.ContinueUpdateOnError 屬性設置為 true。完成更新后,調用 DataTable 對象的 GetErrors 方法以確定哪些行發生了錯誤。然后,使用這些行的 RowError 屬性找到特定的詳細錯誤信息。
以下代碼示例顯示了 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 值。
- 在方法簽名中使用自動兼容的數據類型
實現業務實體
業務實體具有以下特點:
- 業務實體提供對業務數據及相關功能(在某些設計中)的狀態編程訪問。
- 業務實體可以使用具有復雜架構的數據來構建。這種數據通常來自數據庫中的多個相關表。
- 業務實體數據可以作為業務過程的部分 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 表的列名稱,但并不要求一定這樣?梢赃x擇對您的應用程序有意義的名稱。
- 使用以下方法之一以 XML 格式檢索您的業務實體:
- 如果您使用的是 SQL Server 2000,則可以在查詢或存儲過程中使用 FOR XML 子句。在性能測試中,使用 FOR XML 只比返回 DataSet 稍微快一點。
- 檢索 DataSet 并將其轉換為 XML 流或以 XML 流的格式寫出。這種方法會帶來創建 DataSet 的系統開銷和額外的轉換開銷(如果執行轉換)。
- 使用輸出參數或數據讀取器構建一個 XML 文檔。數據讀取器是從數據庫檢索多個行的最快方法,但與構建 XML 相關聯的過程可能會減弱這種性能優勢。
將業務實體表示為 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 中的表、約束及關系。
將業務實體表示為通用 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 類繼承,這會禁止使用任何其他基本類。
定義自定義業務實體組件
表示業務實體的自定義類通常包含以下成員:
- 用于在本地緩存業務實體的數據的專用字段。這些字段在數據訪問邏輯組件從數據庫檢索數據時保存數據庫數據的一個快照。
- 用于訪問實體的狀態和訪問實體內數據的子集及層次結構的公共屬性。這些屬性的名稱可以與數據庫的列名稱相同,但這并不是一個絕對要求?梢愿鶕膽贸绦虻男枰x擇屬性名,而不必使用數據庫中的名稱。
- 用以使用實體組件中的數據執行本地化處理的方法和屬性。
- 用以通知實體組件內部狀態變化的事件。
圖9 所示為使用自定義實體類的方法。注意,實體類并不知道數據訪問邏輯組件或基礎數據庫;所有數據庫訪問都由數據訪問邏輯組件執行,以集中數據訪問策略和業務邏輯。此外,在層間傳遞業務實體數據的方式與表示業務實體的格式也沒有直接關系;例如,可以在本地將業務實體表示為對象,而用另一種方法(如標量值或 XML)將業務實體數據傳遞到其他層。

圖9:自定義業務實體組件的作用
定義自定義業務實體組件的建議
在實現自定義實體組件時,請考慮以下建議:
- 選擇使用結構還是使用類。對于不包含分層數據或集合的簡單業務實體,可以考慮定義一個結構來表示業務實體。對于復雜的業務實體或要求繼承的業務實體,可將實體定義為類。
- 表示業務實體的狀態。對于數字、字符串等簡單值,可以使用等價的 .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 集合。
- 序列化。您必須在自定義實體中實現自己的序列化機制?梢允褂脤傩詠砜刂茖嶓w組件的序列化方式,也可以通過實現 ISerializable 接口來控制自己的序列化。
- 表示業務實體中的復雜關系和層次結構。您必須在業務實體組件中實現自己的關系和層次結構表示機制。如前面所述,DataSet 通常是實現這一目的的最簡單方式。
- 搜索和排序數據。您必須定義自己的機制來支持實體的搜索和排序。例如,可以通過實現 IComparable 接口以便將實體組件保存在一個 SortedList 集合或 Hashtable 集合中。
- 部署。您必須在所有物理層部署包含自定義實體的程序集。
- 支持企業服務 (COM+) 客戶端。如果一個自定義實體將由 COM+ 客戶端使用,則必須為包含該實體的程序集提供一個嚴格名稱,并且必須在客戶端計算機上注冊。通常,該程序集安裝在全局程序集緩存中。
- 可擴展性問題。如果修改了數據庫架構,則可能需要修改自定義實體類并重新部署程序集。
定義帶有 CRUD 行為的自定義業務實體組件
在定義一個自定義實體時,可以提供方法以完全封裝對基礎數據訪問邏輯組件的 CRUD 操作。這是比較傳統的面向對象的方法,可能適用于復雜的對象域?蛻舳藨贸绦虿辉僦苯釉L問數據訪問邏輯組件類,而是創建一個實體組件并對該實體組件調用 CRUD 方法。這些方法將調用基礎的數據訪問邏輯組件。
圖10 所示為帶有 CRUD 行為的自定義實體類的作用。

圖10:帶有 CRUD 行為的自定義業務實體組件的作用
定義帶有 CRUD 行為的自定義實體類的優點如下:
- 封裝。自定義實體可以封裝由基礎數據訪問邏輯組件定義的操作。
- 與調用程序的接口。調用程序必須只處理一個接口來保持業務實體數據。不必直接訪問數據訪問邏輯組件。
- 專用字段。您可以隱藏不希望向調用程序公開的信息。
定義帶有 CRUD 行為的自定義實體類的缺點如下:
- 處理業務實體集合。自定義實體中的方法屬于單個業務實體實例。要支持業務實體集合,可以定義靜態方法以讀取或返回一個數組或一個實體組件集合。
- 開發時間長。傳統的面向對象方法通常比使用現有對象(如 DataSet)需要更多的設計和開發工作。
表示數據和在層間傳遞數據的建議
在您的應用程序中表示數據的方式以及在層間傳遞數據的方式不一定要相同。然而,一套一致而有限的格式能夠降低對附加轉換層的需要,從而提高性能并方便維護。
應根據自己特定的應用程序要求和操作數據的方式選擇數據格式。這里并沒有一個通用的表示方式,特別是由于當今的許多應用程序都需要支持多個調用程序。然而,我們還是建議遵循以下一般原則:
- 如果您的應用程序主要處理集合并需要排序、搜索和數據綁定等功能,則建議采用 DataSet。但如果應用程序處理實例數據,則采用標量值的效果會更好。
- 如果您的應用程序主要處理實例數據,則自定義業務實體組件可能是最佳選擇,因為它們可以消除一個 DataSet 表示一行時的系統開銷。
- 大多數情況下,應把應用程序設計為使用 XML 文檔、DataSet 等以數據為中心的格式?梢岳 DataSet 提供的靈活性及固有功能來更方便地支持多個客戶端、減少自定義代碼的數量并使用為大多數開發人員所熟知的編程 API。雖然以面向對象的方式操作數據有很多好處,但自定義編碼復雜的業務實體會使開發和維護成本隨所提供功能的數量成比例增加。
事務處理
當今的大多數應用程序都需要支持事務處理以保持系統數據的完整性。事務處理的管理方法有多種,但每種方法都可歸于以下兩種基本編程模型之一:
- 手動事務處理。直接在組件代碼或存儲過程中編寫使用 ADO.NET 或 Transact-SQL 事務處理支持功能的代碼。
- 自動事務處理。使用企業服務 (COM+) 為 .NET 類添加聲明屬性以便在運行時指定對象的事務性要求。使用這種模型可以方便地配置多個組件以執行同一事務中的工作。
本節提供一些指導原則和建議,幫助您在數據訪問邏輯組件和業務實體組件中實現事務處理支持。
實現事務處理
在大多數環境中,事務處理的根本是業務過程而不是數據訪問邏輯組件或業務實體組件。這是因為業務過程一般要求事務處理跨多個業務實體而不僅僅是單個業務實體。
然而,也可能出現在沒有高層次業務過程的幫助下對單個業務實體執行事務性操作的情況。例如,要把一個新客戶添加到前面討論的數據庫中,您必須執行以下操作:
- 在 Customer 表中插入新的一行。
- 在 Address 表中插入新的一行或多行。
只有這兩個操作都成功后客戶才會被添加到數據庫中。如果 Customer 業務實體不會成為啟動該事務處理的更大的業務過程的一部分,則應在 Customer 業務實體中使用手動事務處理。手動事務處理不要求與 Microsoft 分布式事務處理協調器 (DTC) 之間進行任何進程間通信,因此比自動事務處理要快得多。
圖11 所示為確定使用手動事務處理還是自動事務處理的方法。由于 COM+ 事務處理的系統開銷,建議將事務處理放到數據庫中并在存儲過程中控制事務性行為(如果可能)。
圖11:確定如何實現事務處理
注意:如果從基于 ASP.NET 的客戶端進行調用,并且沒有用于啟動事務處理的業務過程,則您可能會從 ASP.NET 代碼中啟動該事務處理。這種設計并不好;您決不能從基于 ASP.NET 的客戶端啟動事務處理,而應將數據的展示與業務過程相分離。此外,由于網絡滯后等問題還會導致性能問題,因為這是要實際部署在其他層上的最常見的層。
在數據訪問邏輯組件中使用手動事務處理的建議
在數據訪問邏輯組件中實現手動事務處理時,請考慮以下建議:
- 盡可能在存儲過程中執行處理。使用 Transact-SQL 語句 BEGIN TRANSACTION、END TRANSACTION 和 ROLLBACK TRANSACTION 控制事務處理。
- 如果沒有使用存儲過程,并且也不會從業務過程中調用數據訪問邏輯組件,則可以使用 ADO.NET 來編程控制事務處理。
在數據訪問邏輯組件中使用自動事務處理的建議
雖然 COM+ 事務處理會帶來一些系統開銷,但自動事務處理能夠提供比手動事務處理更簡單的編程模式,而且在事務處理跨多個分布式數據源(與 DTC 一起工作)時必須使用自動事務處理。在數據訪問邏輯組件中實現自動事務處理時,請考慮以下建議:
- 數據訪問邏輯組件必須是從 System.EnterpriseServices 命名空間中的 ServicedComponent 類繼承而來。注意,使用 COM+ 服務注冊的所有程序集都必須具有嚴格的名稱。
- 使用 Transaction(TransactionOption.Supported) 屬性注釋數據訪問邏輯組件,以便可以在同一組件中執行讀寫操作。與 Transaction(TransactionOption.Required) 不同,此選項在不需要事務處理時避免了不必要的系統開銷,而前者始終會要求事務處理。
以下代碼示例顯示了如何在數據訪問邏輯組件類中支持自動事務處理:
using System.EnterpriseServices;
[Transaction(TransactionOption.Supported)]
public class CustomerDALC : ServicedComponent
{
...
}
|
如果使用自動事務處理,則數據訪問邏輯組件應在事務處理中表明操作是否成功。如果要隱式表明,應使用 AutoComplete 屬性注釋您的方法并在操作失敗時發出一個異常。如果要顯式表明,應對 ContextUtil 類調用 SetComplete 或 SetAbort 方法。
在業務實體組件中使用自動事務處理
在實現帶有行為的自定義業務實體組件時,可以使用自動事務處理來指定這些對象的事務性行為。有關使用自動事務處理指定業務實體組件事務性行為的建議與前述有關在數據訪問邏輯組件中實現自動事務處理的建議相同。
注意:如果業務實體組件不包含任何要求其在事務處理中表明是否成功的業務邏輯,則它可以忽略事務處理環境。自定義業務實體組件不需要從 ServicedComponent 繼承;事務處理環境仍將繼續其流程,但實體組件將忽略事務處理環境。
驗證
您可以在應用程序的許多層上進行數據驗證。各層適用不同的驗證類型:
- 在提交數據之前,客戶端應用程序可以在本地驗證業務實體數據。
- 使用 XSD 架構接收業務文檔時,業務過程可以驗證這些文檔。
- 數據訪問邏輯組件和存儲過程可以驗證數據,以確保引用的完整性并強制遵循約束以及重要的業務規則。
常用驗證有兩種:
- 即時點驗證。這是在一個特定時點執行的驗證。例如,在接收 XML 文檔時由業務過程對其進行驗證。
- 連續驗證。這是在應用程序的許多不同層次上持續進行的一種驗證。連續驗證的示例包括:
- 用戶界面可以指定字段的最大長度以防止用戶輸入過長的字符串。
- DataSet 可以指定數據列的最大長度。
- 自定義業務實體組件可以對實體數據執行范圍檢查、長度檢查、非空檢查以及其他簡單測試。
- 數據訪問邏輯組件、存儲過程和數據庫本身可以執行類似的測試,以便在將數據保存到數據庫之前確保其有效性。
有時,您可能希望實現額外的聚合過程或轉換過程。這種方法在驗證和轉換經常變化時可能很有用,但會損失性能。例如,如果一個 ISV 想要使用相同的組件支持數據庫架構的兩個版本,則您可以創建一個單獨的組件來執行兩個數據庫架構版本之間的驗證和轉換。
如何使用 XSD 架構驗證 XML
要使用 XSD 架構驗證 XML 文檔,請執行以下步驟:
- 創建一個 XmlValidatingReader 對象作為 XmlTextReader 對象的包裝,如以下代碼所示:
' 創建 XmlValidatingReader 對象,以讀取和驗證 Product.xml
XmlTextReader tr = new XmlTextReader("Product.xml");
XmlValidatingReader vr = new XmlValidatingReader(tr);
|
- 通過使用 ValidationType 枚舉指定所需的驗證類型。.NET Framework 支持三種類型的 XML 驗證:
文檔類型定義 (DTD);指定 ValidationType.DTD
Microsoft XML 精簡數據 (XDR) 架構;指定 ValidationType.XDR
W3C 標準 XSD 架構;指定 ValidationType.Schema
以下代碼顯示了 ValidationType 枚舉的使用:
vr.ValidationType = ValidationType.Schema; ' 指定 XSD 架構驗證
|
- 注冊一個驗證事件處理程序方法,如以下代碼所示:
vr.ValidationEventHandler += new ValidationEventHandler(MyHandlerMethod);
|
- 提供一個驗證事件處理程序方法的實現,如以下代碼所示:
public void MyHandlerMethod(object sender, ValidationEventArgs e)
{
Console.WriteLine("驗證錯誤:" + e.Message);
}
|
- 讀取和驗證文檔,如以下代碼所示。驗證錯誤將被驗證事件處理程序方法拾取。
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
- 數據庫連接
- 資源(如數據庫、網絡共享、消息隊列等)不可用
業務邏輯異常,它包括:
- 驗證錯誤
- 實現業務邏輯的存儲過程中的錯誤
在數據訪問邏輯組件中管理異常的建議
數據訪問邏輯組件應該傳播異常,并且僅在能夠使客戶端對異常的管理更加容易時才包裝異常類型。將異常包裝為兩種主要異常類型(技術異常和業務異常)有利于各種可能的調用程序的異常處理結構和異常發布邏輯。
您的應用程序應當發布異常信息?梢詫⒓夹g異常發布到一個由系統管理員或 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 類中繼承自定義技術異常和自定義業務異常。
授權與安全性
學習向 Microsoft .NET 應用程序公開數據的最佳方式,以及如何實現一個有效的策略以便在分布式應用程序的層間傳遞數據。(本文包含一些指向英文站點的鏈接。)
本節說明如何將安全性應用于數據訪問邏輯組件和業務實體組件。.NET 公共語言運行庫使用權限對象實現其對托
文章來源于領測軟件測試網 http://www.kjueaiud.com/
TAG:
net
設計
數據
應用
與
老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月