第 2 章 — 處理數(shù)據(jù)
發(fā)布日期: 8/20/2004 | 更新日期: 8/20/2004
智能客戶端體系結(jié)構(gòu)與設(shè)計指南
David Hill, Brenton Webster, Edward A. Jezierski, Srinath Vasireddy and Mohammad Al-Sabt, Microsoft Corporation; Blaine Wastell, Ascentium Corporation; Jonathan Rasmusson and Paul Gale, ThoughtWorks; and Paul Slater, Wadeware LLC
相關(guān)鏈接
Microsoft? patterns & practices 庫 http://www.microsoft.com/resources/practices/default.mspx
Application Architecture for .NET: Designing Applications and Serviceshttp://msdn.microsoft.com/library/en-us/dnbda/html/distapp.asp
摘要:本章分析在客戶端處理數(shù)據(jù)時的各種注意事項,包括數(shù)據(jù)緩存、數(shù)據(jù)并發(fā)以及數(shù)據(jù)集和 Windows 窗體數(shù)據(jù)綁定的使用。
本頁內(nèi)容
在智能客戶端中,可在客戶端上使用應用程序數(shù)據(jù)。要使您的智能客戶端有效工作,很重要的一點是對該數(shù)據(jù)進行適當?shù)墓芾恚源_保其有效、一致和安全。
可以通過服務器端應用程序(例如,通過 Web 服務)向客戶端提供應用程序數(shù)據(jù),或者應用程序可以使用它自己的本地數(shù)據(jù)。如果數(shù)據(jù)是由應用程序提供的,則智能客戶端應用程序可以緩存數(shù)據(jù)以改善性能或者支持脫機使用。在這種情況下,您需要決定客戶端應用程序應該如何處理就該服務器而言已經(jīng)過時的數(shù)據(jù)。
如果智能客戶端應用程序提供在本地修改數(shù)據(jù)的能力,則必須在以后將客戶端更改與服務器端應用程序進行同步。在這種情況下,您必須決定客戶端應用程序如何處理數(shù)據(jù)沖突,以及如何跟蹤需要發(fā)送到服務器的更改。
在設(shè)計您的智能客戶端應用程序時,您需要認真考慮這些問題以及其他許多問題。本章分析了在客戶端上處理數(shù)據(jù)時的各種注意事項,包括:
? |
數(shù)據(jù)類型。 |
? |
緩存數(shù)據(jù)。 |
? |
數(shù)據(jù)并發(fā)。 |
? |
使用 ADO.NET 數(shù)據(jù)集來管理數(shù)據(jù)。 |
? |
Windows 窗體數(shù)據(jù)綁定。 |
本章未討論其他許多與處理數(shù)據(jù)有關(guān)的問題。具體說來,在第 5 章:安全性注意事項中討論了數(shù)據(jù)處理安全性問題,在第 4 章:偶爾連接的智能客戶端中討論了脫機注意事項。
數(shù)據(jù)類型
智能客戶端通常必須處理兩種類別的數(shù)據(jù):
? |
只讀引用數(shù)據(jù) |
? |
瞬態(tài)數(shù)據(jù) |
通常情況下,需要以不同的方式處理這些類型的數(shù)據(jù),因此更詳細地分析一下每種類型將是很有用的。
只讀引用數(shù)據(jù)
只讀引用數(shù)據(jù)是不會由客戶端更改并且被客戶端用于引用目的的數(shù)據(jù)。因此,從客戶端的觀點看來,該數(shù)據(jù)為只讀數(shù)據(jù),并且客戶端不會對其執(zhí)行更新、插入或刪除操作。只讀引用數(shù)據(jù)很容易在客戶端上進行緩存。引用數(shù)據(jù)在智能客戶端應用程序中具有許多種用途,包括:
? |
提供靜態(tài)引用或查找數(shù)據(jù)。這方面的示例包括產(chǎn)品信息、價格表、發(fā)貨選項和價格。 |
? |
支持數(shù)據(jù)驗證,允許檢查用戶輸入數(shù)據(jù)的正確性。示例有針對交貨時間表檢查輸入的日期。 |
? |
幫助與遠程服務進行通訊。示例在本地將用戶選擇轉(zhuǎn)化為產(chǎn)品 ID,然后將該信息發(fā)送到 Web 服務。 |
? |
呈現(xiàn)數(shù)據(jù)。示例包括呈現(xiàn)幫助文本或用戶界面標簽。 |
通過在客戶端上存儲和使用引用數(shù)據(jù),您可以減少需要從客戶端傳輸?shù)椒掌鞯臄?shù)據(jù)量,改善應用程序的性能,幫助啟用脫機功能,并提供早期數(shù)據(jù)驗證以提高應用程序的可用性。
盡管客戶端無法更改只讀引用數(shù)據(jù),但可以在服務器上進行更改(例如,由管理員或超級用戶更改)。您需要確定在發(fā)生數(shù)據(jù)更改時用于更新客戶端的策略。此類策略可能涉及到在發(fā)生更改時將更改推到客戶端上,或者按照特定的時間間隔或在客戶端上執(zhí)行某些操作之前從服務器拉入更改。但是,因為數(shù)據(jù)在客戶端上是只讀的,所以您無須跟蹤客戶端更改。這就簡化了需要對只讀引用數(shù)據(jù)進行處理的方式。
瞬態(tài)數(shù)據(jù)
瞬態(tài)數(shù)據(jù)既可以在服務器上更改,也可以在客戶端上更改。通常情況下,瞬態(tài)數(shù)據(jù)作為用戶輸入和操作的直接或間接結(jié)果而發(fā)生更改。在此情況下,在客戶端或服務器進行的更改都需要在某個時刻進行同步。這種類型的數(shù)據(jù)在智能客戶端中具有許多種用途,包括:
? |
添加新信息。示例包括添加銀行業(yè)務交易或客戶詳細信息。 |
? |
修改現(xiàn)有信息。示例更新客戶詳細信息。 |
? |
刪除現(xiàn)有信息。示例從數(shù)據(jù)庫中刪除客戶。 |
在智能客戶端處理瞬態(tài)數(shù)據(jù)的最困難的方面之一在于,這些數(shù)據(jù)通常可能在多個客戶端上同時進行修改。當數(shù)據(jù)非常不穩(wěn)定時,該問題將惡化,因為所做更改更有可能互相沖突。
您需要跟蹤您對瞬態(tài)數(shù)據(jù)進行的任何客戶端更改。在與服務器同步數(shù)據(jù)并且已經(jīng)解決任何沖突之前,您不應該認為瞬態(tài)數(shù)據(jù)已被確認。您應該非常小心以避免依賴未確認的數(shù)據(jù)進行重要決策,或者在未認真考慮如何保證數(shù)據(jù)一致性(甚至在同步失敗時)的情況下使用該數(shù)據(jù)作為其他本地更改的基礎(chǔ)。
有關(guān)圍繞脫機時處理數(shù)據(jù)的問題以及如何處理數(shù)據(jù)同步的詳細信息,請參閱第 4 章:偶爾連接的智能客戶端。
緩存數(shù)據(jù)
智能客戶端通常需要在本地緩存數(shù)據(jù)(無論是只讀引用數(shù)據(jù)還是瞬態(tài)數(shù)據(jù))。通過緩存數(shù)據(jù),有可能改善應用程序的性能并提供脫機工作所需的數(shù)據(jù)。但是,您需要認真考慮在客戶端緩存哪些數(shù)據(jù)、如何管理這些數(shù)據(jù)以及可以在哪個上下文中使用這些數(shù)據(jù)。
要啟用數(shù)據(jù)緩存,您的智能客戶端應用程序應該實現(xiàn)某種形式的緩存基礎(chǔ)結(jié)構(gòu),以便透明地處理數(shù)據(jù)緩存細節(jié)。您的緩存基礎(chǔ)結(jié)構(gòu)應該包括下列緩存機制中的一種或兩種:
? |
短期數(shù)據(jù)緩存。在內(nèi)存中緩存數(shù)據(jù)對性能有益,但不能持久,因此您可能需要在重新運行應用程序時從源拉入數(shù)據(jù)。這樣做可能會妨礙您的應用程序脫機操作。 |
? |
長期數(shù)據(jù)緩存。通過在持久性媒體(如獨立存儲或本地文件系統(tǒng))中緩存數(shù)據(jù),可以在沒有連接到服務器時使用應用程序。您可以選擇將長期存儲與短期存儲結(jié)合起來以改善性能。 |
無論您采用哪種緩存機制,都應該確保僅將用戶有權(quán)訪問的數(shù)據(jù)提供給客戶端。而且,在客戶端緩存的敏感數(shù)據(jù)要求進行認真處理以確保它的安全。因此,您可能需要在將數(shù)據(jù)傳輸?shù)娇蛻舳艘约霸诳蛻舳舜鎯?shù)據(jù)時,對數(shù)據(jù)進行加密。有關(guān)詳細信息,請參閱第 5 章:安全性注意事項中的“處理敏感數(shù)據(jù)”。
當您設(shè)計智能客戶端以支持數(shù)據(jù)緩存時,您應該考慮為客戶端提供一種請求新數(shù)據(jù)的機制,而無論緩存的狀態(tài)如何。這意味著您可以確保應用程序隨時能夠執(zhí)行新的事務,并且不會使用過時的數(shù)據(jù)。您還可以將客戶端配置為預先獲取數(shù)據(jù),以便減少在緩存數(shù)據(jù)到期時處于脫機狀態(tài)的風險。
只要有可能,您都應該將某種形式的元數(shù)據(jù)與該數(shù)據(jù)關(guān)聯(lián)起來,以便使客戶端能夠以聰明的方式管理這些數(shù)據(jù)。此類元數(shù)據(jù)可用于指定數(shù)據(jù)的標識和任何約束,或者指定所需的與該數(shù)據(jù)關(guān)聯(lián)的行為。您的客戶端緩存基礎(chǔ)結(jié)構(gòu)應該消耗該元數(shù)據(jù),并且使用它來適當處理緩存的數(shù)據(jù)。
客戶端緩存的所有數(shù)據(jù)都應該是可以唯一標識的(例如,通過版本號或日期戳),以便在確定是否需要更新數(shù)據(jù)時,可以正確地識別相應的數(shù)據(jù)。這樣,您的緩存基礎(chǔ)結(jié)構(gòu)就能夠詢問服務器它所具有的數(shù)據(jù)當前是否有效,并且確定是否需要進行更新。
元數(shù)據(jù)還可以用來指定與緩存數(shù)據(jù)的使用和處理相關(guān)的約束或行為。示例包括:
? |
時間約束。這些約束指定可以使用緩存數(shù)據(jù)的時間或日期范圍。當該數(shù)據(jù)過時或到期時,可以將其從緩存中丟棄,或者通過從服務器獲取最新數(shù)據(jù)來自動刷新數(shù)據(jù)。在某些情況下,合適的做法可能是讓客戶端使用過時的引用數(shù)據(jù),并且在與服務器進行同步時將過時數(shù)據(jù)映射到最新數(shù)據(jù)。 |
? |
地理約束。某些數(shù)據(jù)可能僅適用于特定地區(qū)。例如,您可能對于不同的地點有不同的價格表。可以使用您的緩存基礎(chǔ)結(jié)構(gòu)分別針對不同的地點來訪問和存儲數(shù)據(jù)。 |
? |
安全性要求。可以將專門提供給特定用戶的數(shù)據(jù)加密,以確保只有相應的用戶可以訪問這些數(shù)據(jù)。在此情況下,所提供的數(shù)據(jù)已經(jīng)進行了加密,并且用戶必須向緩存基礎(chǔ)結(jié)構(gòu)提供憑據(jù)以便對數(shù)據(jù)進行解密。 |
? |
業(yè)務規(guī)則。您可能擁有與緩存數(shù)據(jù)關(guān)聯(lián)的業(yè)務規(guī)則,用來規(guī)定如何使用這些數(shù)據(jù)。例如,您的緩存基礎(chǔ)結(jié)構(gòu)可能考慮用戶的角色,以便確定向該用戶提供哪些數(shù)據(jù)以及如何處理這些數(shù)據(jù)。 |
您的緩存基礎(chǔ)結(jié)構(gòu)可以通過與數(shù)據(jù)關(guān)聯(lián)的元數(shù)據(jù)來適當?shù)靥幚磉@些數(shù)據(jù),從而使您的應用程序無須關(guān)心數(shù)據(jù)緩存問題或?qū)崿F(xiàn)細節(jié)。您可以在引用數(shù)據(jù)本身內(nèi)部傳遞與這些數(shù)據(jù)關(guān)聯(lián)的元數(shù)據(jù),或者您可以使用帶外機制。用于將元數(shù)據(jù)傳輸?shù)娇蛻舳说拇_切機制取決于您的應用程序與網(wǎng)絡(luò)服務的通訊方式。當使用 Web 服務時,利用 SOAP 頭將元數(shù)據(jù)傳輸?shù)娇蛻舳耸且环N很好的解決方案。
只讀引用數(shù)據(jù)與瞬態(tài)數(shù)據(jù)之間存在的區(qū)別有時意味著您需要使用兩個緩存,一個用于引用數(shù)據(jù),一個用于瞬態(tài)數(shù)據(jù)。引用數(shù)據(jù)在客戶端是只讀的,并且不需要回過頭來與服務器進行同步,但它的確需要偶爾進行刷新以反映在服務器上進行的任何更改和更新。
瞬態(tài)數(shù)據(jù)既可以在服務器上更改,也可以在客戶端上更改。既然有時在客戶端更新緩存中的數(shù)據(jù),有時在服務器更新,有時在這兩個位置更新,那么對客戶端數(shù)據(jù)進行的更改需要在某個時刻與服務器進行同步。如果數(shù)據(jù)同時在服務器上發(fā)生了更改,則會發(fā)生數(shù)據(jù)沖突,需要對其進行適當?shù)奶幚怼?/P>
要幫助確保維持數(shù)據(jù)一致性,并且避免不適當?shù)厥褂脭?shù)據(jù),您應該小心地跟蹤您在客戶端對瞬態(tài)數(shù)據(jù)進行的任何更改。在成功地與服務器進行同步或確認之前,此類更改是未提交的 或暫定的。
您應該對您的智能客戶端應用程序進行適當?shù)脑O(shè)計,以使其能夠區(qū)分已經(jīng)成功地與服務器進行同步的數(shù)據(jù)和仍然暫定的數(shù)據(jù)。這一區(qū)分過程可以幫助應用程序更加容易地檢測和處理數(shù)據(jù)沖突。而且,您可能需要禁止應用程序或用戶基于暫定數(shù)據(jù)進行重要決策或者啟動重要操作。在將此類數(shù)據(jù)與服務器進行同步之前,不應該依賴它們。通過使用適當?shù)木彺婊A(chǔ)結(jié)構(gòu),可以跟蹤暫定數(shù)據(jù)和已經(jīng)確認的數(shù)據(jù)。
緩存應用程序塊(Caching Application Block)
緩存應用程序塊是一個 Microsoft? .NET 框架擴展,它使開發(fā)人員可以容易地緩存來自服務提供程序的數(shù)據(jù)。生成和設(shè)計它的目的是將 Microsoft 建議的緩存準則封裝在 .NET 框架應用程序中,如位于 http://msdn.microsoft.com/library/en-us/dnbda/html/CachingArch.asp 的 Caching Architecture Guide for .NET Framework Applicationss 所述。
緩存塊的總體體系結(jié)構(gòu)如圖 2.1 所示。
圖 2.1 緩存塊工作流
緩存工作流包含下列步驟:
1. |
客戶端或服務代理向 CacheManager 發(fā)出對緩存數(shù)據(jù)項的請求。 |
2. |
如果該數(shù)據(jù)項已被緩存,則 CacheManager 會從存儲中檢索該項,并將其作為 CacheItem 對象返回。如果該項尚未緩存,則會通知客戶端。 |
3. |
在從服務提供程序檢索未緩存的數(shù)據(jù)之后,客戶端將該數(shù)據(jù)發(fā)送給 CacheManager。CacheManager 會將一個簽名(即,元數(shù)據(jù))如密鑰、到期時間或優(yōu)先級等添加到該數(shù)據(jù)項中,并將其加載到緩存中。 |
4. |
CacheService 監(jiān)控 CacheItems 的生存期。當 CacheItem 到期時,CacheService 會刪除它并根據(jù)情況調(diào)用回調(diào)委托。 |
5. |
CacheService 還可以將所有數(shù)據(jù)項從緩存中清除出去。 |
緩存塊提供了多種緩存到期選項,如表 2.1 所述。
表 2.1 緩存塊到期選項
AbsoluteTime |
用于設(shè)置到期時間的絕對時間。 |
ExtendedFormatTime |
用于基于表達式(如 every minute 或 every Monday)設(shè)置到期時間。 |
FileDependency |
用于基于文件是否更改來設(shè)置到期時間。 |
SlidingTime |
用于設(shè)置項的生存期,方法是基于項的上次訪問時間來指定到期時間。 |
下列存儲機制可供緩存塊使用:
? |
內(nèi)存映射文件 (MMF)。MMF 最適合于基于客戶端的高性能緩存方案。您可以使用 MMF 來開發(fā)可在同一臺計算機中的多個應用程序域和進程之間共享的緩存。.NET 框架不支持 MMF,因此 MMF 緩存的任何實現(xiàn)都以非托管代碼的形式運行,并且不會從任何 .NET 框架功能中受益,包括內(nèi)存管理功能(如垃圾回收)和安全性功能(如代碼訪問安全性)。 |
? |
Singleton 對象。可以使用 .NET 遠程處理 singleton 對象來緩存可在一臺或多臺計算機中的進程之間共享的數(shù)據(jù)。方法是使用通過 .NET 遠程處理為多個客戶端提供服務的 singleton 對象來實現(xiàn)緩存服務。單例緩存的實現(xiàn)很簡單,但它缺乏基于 Microsoft SQL Server? 的解決方案所提供的性能和可伸縮性。 |
? |
Microsoft SQL Server 2000 數(shù)據(jù)庫。SQL Server 2000 存儲最適合于應用程序要求具有高持續(xù)性或者您需要緩存大量數(shù)據(jù)的場合。因為緩存服務需要通過網(wǎng)絡(luò)訪問 SQL Server,并且使用數(shù)據(jù)庫查詢檢索數(shù)據(jù),所以數(shù)據(jù)訪問的速度相對比較慢。 |
? |
Microsoft SQL Server 桌面引擎 (MSDE)。MSDE 是 SQL Server 2000 的輕型數(shù)據(jù)庫替代產(chǎn)品。它提供了可靠性和安全性功能,但具有比 SQL Server 更小的客戶端足跡,因此它需要較少的設(shè)置和配置。因為 MSDE 支持 SQL,所以開發(fā)人員可以得到數(shù)據(jù)庫的很多功能。如有必要,您可以將 MSDE 數(shù)據(jù)庫遷移到 SQL Server 數(shù)據(jù)庫。 |
數(shù)據(jù)并發(fā)
正如前面所提到的,使用智能客戶端的一個問題是:在將任何客戶端更改與服務器進行同步之前,服務器上保存的數(shù)據(jù)可能發(fā)生更改。您需要采用某種機制來確保在對數(shù)據(jù)進行同步時,數(shù)據(jù)沖突能夠得到適當?shù)奶幚恚⑶易詈蟮玫降臄?shù)據(jù)是一致和正確的。數(shù)據(jù)能夠由多個客戶端進行更新的能力稱為“數(shù)據(jù)并發(fā)”。
您可以使用兩種方法來處理數(shù)據(jù)并發(fā):
? |
保守式并發(fā)。保守式并發(fā)允許一個客戶端保持數(shù)據(jù)上的鎖,以禁止任何其他客戶端修改數(shù)據(jù),直至客戶端自己的更改完成為止。在這種情況下,如果另一個客戶端嘗試修改數(shù)據(jù),則在鎖的擁有者釋放該鎖之前,這些嘗試將失敗或者被阻止。 |
? |
保守式并發(fā)可能有問題,因為單個用戶或客戶端可能由于疏忽而長時間地保持鎖定。所以,該鎖可能會妨礙重要資源(如數(shù)據(jù)庫行或文件)及時得到釋放,從而嚴重影響應用程序的可伸縮性和可用性。但是,當您需要完全控制對重要資源所做的更改時,保守式并發(fā)可能是適當?shù)摹U堊⒁猓绻目蛻舳艘摍C工作,則不能使用這種并發(fā),因為客戶端無法對數(shù)據(jù)加鎖。 |
? |
開放式并發(fā)。開放式并發(fā)不會鎖定數(shù)據(jù)。要判斷是否實際需要更新,可以將原始數(shù)據(jù)隨更新請求和已更改的數(shù)據(jù)一起發(fā)送。隨后,將針對當前數(shù)據(jù)檢查原始數(shù)據(jù),以查看是否同時對原始數(shù)據(jù)進行了更新。如果原始數(shù)據(jù)和當前數(shù)據(jù)匹配,則執(zhí)行更新;否則,拒絕請求,并產(chǎn)生開放式失敗。要優(yōu)化該過程,您可以在數(shù)據(jù)中使用時間戳或更新計數(shù)器,而不必發(fā)送原始數(shù)據(jù),此時只需要檢查時間戳或計數(shù)器。
開放式并發(fā)提供了一種良好的機制,可用來更新不會非常頻繁更改的主數(shù)據(jù),如客戶的電話號碼或地址。開放式并發(fā)允許每個人讀取數(shù)據(jù),在發(fā)生更新的概率小于讀取操作的情況下,開放式失敗的風險或許是可以接受的。在數(shù)據(jù)頻繁更改以及開放式更新可能經(jīng)常失敗的情況下,開放式并發(fā)可能并不適合。 |
在大多數(shù)智能客戶端方案(包括客戶端將要脫機工作的方案)中,開放式并發(fā)是正確的方法,因為它允許多個客戶端同時使用數(shù)據(jù),而不會不必要地鎖定數(shù)據(jù)和影響所有其他客戶端。
有關(guān)開放式和保守式并發(fā)的詳細信息,請參閱 .NET Framework Developer's Guide 中的“Optimistic Concurrency”,網(wǎng)址為:http://msdn.microsoft.com/library/en-us/cpguide/html/cpconoptimisticconcurrency.asp。
使用 ADO.NET 數(shù)據(jù)集來管理數(shù)據(jù)
DataSet 是一個表示一個或多個關(guān)系數(shù)據(jù)庫表的對象。數(shù)據(jù)集在斷開連接的緩存中存儲數(shù)據(jù)。DataSets的結(jié)構(gòu)與關(guān)系數(shù)據(jù)庫類似:它公開了一個由表、行和列組成的層次結(jié)構(gòu)對象模型。另外,它還包含為DataSets定義的約束和關(guān)系。
ADO.NET DataSet 包含零個或更多個由 DataTable 對象表示的表組成的集合。DataTable 在 System.Data 命名空間中定義,并且表示單個由內(nèi)存駐留數(shù)據(jù)組成的表。它包含由 DataColumnCollection 表示的列和由 ConstraintCollection 表示的約束組成的集合,它們共同定義了該表的架構(gòu)。DataTable 還包含由 DataRowCollection(它包含該表中的數(shù)據(jù))表示的行組成的集合。與其當前狀態(tài)一起,DataRow 保留其當前版本和原始版本,以便標識對該行中存儲的值所做的更改。
DataSets可以強類型化或非類型化。類型化的 DataSet 從 DataSet 基類繼承,但是向 DataSet 中添加了強類型化的語言功能,從而使用戶可以用更加強類型化的編程方式訪問內(nèi)容。在生成應用程序時,可以使用任一種類型。但是,Microsoft Visual Studio ? 開發(fā)系統(tǒng)對類型化DataSets具有更多支持,它們使得用DataSets編程變得更加容易,而且更不容易出錯。
DataSets在智能客戶端環(huán)境中尤其有用,因為它們提供了能夠幫助客戶端在脫機狀態(tài)下使用數(shù)據(jù)的功能。它們可以跟蹤對數(shù)據(jù)進行的本地更改,這有助于與服務器同步數(shù)據(jù)以及協(xié)調(diào)數(shù)據(jù)沖突,并且它們還可用于合并來自不同源的數(shù)據(jù)。
有關(guān)如何使用DataSets的詳細信息,請參閱 Visual Basic and Visual C# Concepts 中的“Introduction to DataSets”,網(wǎng)址為:http://msdn.microsoft.com/library/en-us/vbcon/html/vbconDataSets.asp。
用DataSets合并數(shù)據(jù)
DataSets具有將 DataSet、DataTable 或 DataRow 對象的內(nèi)容合并到現(xiàn)有DataSets的能力。對于跟蹤在客戶端上進行的更改以及與服務器的已更新內(nèi)容進行合并而言,該功能尤其有用。圖 2.2 顯示了一個從 Web 服務請求更新的智能客戶端,新數(shù)據(jù)作為數(shù)據(jù)傳輸對象 (DTO) 返回。DTO 是一種企業(yè)模式,它使您可以將所有需要與 Web 服務進行通訊的數(shù)據(jù)打包到一個對象中。使用 DTO 通常意味著您可以對 Web 服務進行單個調(diào)用而不是多個調(diào)用。
圖 2.2 通過使用DataSets合并客戶端上的數(shù)據(jù)
在該示例中,當 DTO 被返回到客戶端時,該 DTO 將被用于在客戶端上以本地方式創(chuàng)建一個新的DataSets。
注 在合并操作之后,ADO.NET 不會自動將行狀態(tài)從 modified 更改為 unchanged。因此,在將新的DataSets與本地客戶端DataSets合并之后,您需要調(diào)用DataSets上的 AccceptChanges 方法,將 RowState 屬性重置為 unchanged。
有關(guān)如何使用DataSets的詳細信息,請參閱 .NET Framework Developer's Guide 中的“Merging DataSet Contents”,網(wǎng)址為:http://msdn.microsoft.com/library/en-us/cpguide/html/cpconmergingDataSetcontents.asp。
提高DataSets的性能
DataSets通常可以包含大量數(shù)據(jù),如果通過網(wǎng)絡(luò)傳遞這些數(shù)據(jù),則可能導致性能問題。幸而,通過 ADO.NET DataSets,您可以使用DataSets上的 GetChanges 方法來確保只在客戶端和服務器之間傳送在DataSets中更改過的數(shù)據(jù),并且將該數(shù)據(jù)打包到 DTO 中。該數(shù)據(jù)隨后將被合并到其目的地的DataSets中。
圖 2.3 顯示了一個智能客戶端,它對本地數(shù)據(jù)進行更改,并且使用DataSets上的 GetChanges 方法僅將已更改的數(shù)據(jù)提交給服務器。出于性能原因,該數(shù)據(jù)被傳輸給 DTO。
圖 2.3 使用 DTO 改善性能
可以將 GetChanges 方法用于需要脫機工作的智能客戶端應用程序。當應用程序重新聯(lián)機時,您可以使用 GetChanges 方法確定哪些信息已經(jīng)更改,并且隨后生成一個與 Web 服務通訊的 DTO,以便確保將更改提交給數(shù)據(jù)庫。
Windows 窗體數(shù)據(jù)綁定
通過 Windows 窗體數(shù)據(jù)綁定,您可以將應用程序的用戶界面連接到該應用程序的基礎(chǔ)數(shù)據(jù)。Windows 窗體數(shù)據(jù)綁定支持雙向綁定,因此您可以將數(shù)據(jù)結(jié)構(gòu)綁定到用戶界面,向用戶顯示當前數(shù)據(jù)值,使用戶可以編輯數(shù)據(jù),然后使用用戶輸入的值自動更新基礎(chǔ)數(shù)據(jù)。
您可以使用 Windows 窗體數(shù)據(jù)綁定將幾乎任何數(shù)據(jù)結(jié)構(gòu)或?qū)ο蠼壎ǖ接脩艚缑婵丶娜魏螌傩浴D梢詫蝹€數(shù)據(jù)項綁定到控件的單個屬性,還可以將更為復雜的數(shù)據(jù)(例如,數(shù)據(jù)項集合或數(shù)據(jù)庫表)綁定到該控件,以便它可以在數(shù)據(jù)網(wǎng)格或列表框中顯示所有數(shù)據(jù)。
注 您可以綁定任何支持一個或多個公共屬性的對象。您只能綁定到類的公共屬性而不是公共成員。
通過 Windows 窗體數(shù)據(jù)綁定,您可以隨您的應用程序一起提供靈活的、數(shù)據(jù)驅(qū)動的用戶界面。您可以使用數(shù)據(jù)綁定提供對用戶界面外觀的自定義控制(例如,通過綁定到某些控件屬性,如背景或前景顏色、大小、圖像或圖標)。
數(shù)據(jù)綁定具有許多種用途。例如,可以使用它完成下列任務:
? |
向用戶顯示只讀數(shù)據(jù)。 |
? |
使用戶可以從用戶界面更新數(shù)據(jù)。 |
? |
提供數(shù)據(jù)上的主從視圖。 |
? |
使用戶可以瀏覽復雜的相關(guān)數(shù)據(jù)項。 |
? |
提供查找表功能,使用戶界面可以連接用戶友好的顯示名稱。 |
本節(jié)分析數(shù)據(jù)綁定的一些功能,并討論一些您經(jīng)常需要在智能客戶端應用程序中實現(xiàn)的數(shù)據(jù)綁定功能。
有關(guān)數(shù)據(jù)綁定的詳細信息,請參閱“Windows Forms Data Binding and Objects”,網(wǎng)址為:http://msdn.microsoft.com/library/en-us/dnadvnet/html/vbnet02252003.asp。
Windows 窗體數(shù)據(jù)綁定體系結(jié)構(gòu)
Windows 窗體數(shù)據(jù)綁定提供了一種用于將數(shù)據(jù)雙向連接到用戶界面的靈活的基礎(chǔ)結(jié)構(gòu)。圖 2.4 顯示了 Windows 窗體數(shù)據(jù)綁定的總體體系結(jié)構(gòu)的示意圖。
圖 2.4 Windows 窗體數(shù)據(jù)綁定的體系結(jié)構(gòu)
Windows 窗體數(shù)據(jù)綁定使用下列對象:
? |
數(shù)據(jù)源。數(shù)據(jù)源是包含要綁定到用戶界面的數(shù)據(jù)的對象。數(shù)據(jù)提供程序可以是任何具有公共屬性的對象,可以是支持 IList 接口的數(shù)組或集合,還可以是復雜數(shù)據(jù)類(例如,DataSet 或 DataTable)的實例。 |
? |
CurrencyManager。CurrencyManager 對象用于跟蹤綁定到用戶界面的數(shù)組、集合或表內(nèi)的數(shù)據(jù)的當前位置。通過 CurrencyManager 可以將數(shù)據(jù)集合綁定到用戶界面以及在相應的數(shù)據(jù)中導航,同時更新用戶界面以反映集合內(nèi)當前選擇的項。 |
? |
PropertyManager。PropertyManager 對象負責維護綁定到控件的對象的當前屬性。PropertyManager 類和 CurrencyManager 類都從公用基類 BindingManagerBase 中繼承。所有綁定到控件的數(shù)據(jù)提供程序都具有一個關(guān)聯(lián)的 CurrencyManager 或 PropertyManager 對象。 |
? |
BindingContext。每個 Windows 窗體都具有一個默認的 BindingContext 對象,該對象跟蹤相應窗體上的所有 CurrencyManager 和 PropertyManager 對象。通過 BindingContext 對象可以容易地檢索特定數(shù)據(jù)源的 CurrencyManager 或 PropertyManager 對象。您可以將特定的 BindingContext 對象分配給包含數(shù)據(jù)綁定控件的容器控件(如 GroupBox、Panel 或 TabControl)。這樣做可以使窗體的每個部分都由它自己的 CurrencyManager 或 PropertyManager 對象管理。 |
? |
Binding。Binding 對象用于在控件的單個屬性與另一個對象的屬性或某個對象列表中當前對象的屬性之間創(chuàng)建和維護簡單綁定。 |
將數(shù)據(jù)綁定到 Windows 窗體控件
有許多可用于綁定到特定 Windows 窗體控件的屬性和方法。表 2.2 顯示了其中一些比較重要的屬性和方法。
表 2.2 用于綁定到 Windows 窗體控件的屬性和方法
DataSource 屬性 |
ListControls(例如,ListBox 或 Combo Box)、
DataGrid 控件 |
使您可以指定要綁定到用戶界面控件的數(shù)據(jù)提供程序?qū)ο蟆?/P> |
DisplayMember 屬性 |
ListControls |
使您可以指定要顯示給用戶的數(shù)據(jù)提供程序的成員。 |
ValueMember 屬性 |
ListControls |
使您可以指定與顯示值相關(guān)聯(lián)的、供您的應用程序內(nèi)部使用的值。 |
DataMember 屬性 |
DataGrid 控件 |
如果數(shù)據(jù)源包含多個數(shù)據(jù)源(例如,如果您指定了包含多個表的DataSets),請使用 DataMember 屬性來指定要綁定到網(wǎng)格的數(shù)據(jù)源。(參閱表后面的備注。) |
SetDataBinding 方法 |
DataGrid 控件 |
使您可以在運行時重置 DataSource 方法。 |
注 如果 DataSource 是 DataTable、DataView、集合或數(shù)組,則無須設(shè)置 DataMember 屬性。
您還可以使用所有 Windows 窗體控件對象上提供的 DataBindings 集合屬性將 Binding 對象顯式添加到任何控件對象。Binding 對象用于將控件上的單個屬性綁定到數(shù)據(jù)提供程序的單個數(shù)據(jù)成員。下面的代碼示例在一個文本框控件的 Text 屬性和一個數(shù)據(jù)集的 customers 表中的客戶名稱之間添加了綁定。
textBox1.DataBindings.Add(
new Binding( "Text", DataSet, "customers.customerName" ) );
當您用 Binding 構(gòu)造函數(shù)構(gòu)建 Binding 示例時,您必須指定要綁定到的控件屬性的名稱、數(shù)據(jù)源以及可解析為該數(shù)據(jù)源中的列表或?qū)傩缘膶Ш铰窂健T搶Ш铰窂娇梢允强兆址蝹€屬性名或句點分隔的名稱層次結(jié)構(gòu)。您可以使用分層的導航路徑在 DataSet 對象中的數(shù)據(jù)表和關(guān)系中導航,或者在對象的屬性向其他對象返回實例的對象模型中導航。如果您將導航路徑設(shè)置為空字符串,則會在基礎(chǔ)數(shù)據(jù)源對象上調(diào)用 ToString 方法。
注 如果屬性是只讀的(即,對象不支持對該屬性進行的設(shè)置操作),則數(shù)據(jù)綁定默認情況下不會使綁定的 Windows 窗體控件成為只讀的。這可能給用戶帶來混亂,因為用戶可以編輯用戶界面中的值,但綁定對象中的值將不會得到更新。所以,請確保將所有被綁定到只讀屬性的 Windows 窗體控件的只讀標志設(shè)置為 true。
將控件綁定到DataSets
將控件綁定到數(shù)據(jù)集通常是有用的。這樣做使您可以在數(shù)據(jù)網(wǎng)格中顯示數(shù)據(jù)集數(shù)據(jù),并且使用戶可以容易地更新數(shù)據(jù)。您可以使用以下代碼將數(shù)據(jù)網(wǎng)格控件綁定到 DataSet。
DataSet newDataSet = webServiceProxy.GetDataSet();
this.DataGrid.SetDataBinding( newDataSet, "tableName" );
有時,在已經(jīng)建立與您的控件的所有綁定之后,您需要替換數(shù)據(jù)集的內(nèi)容。但是,在用新的集合替換現(xiàn)有集合時,所有綁定仍將指向舊的數(shù)據(jù)集。
比用新的數(shù)據(jù)源手動重新創(chuàng)建數(shù)據(jù)綁定更好的辦法是,您可以使用 DataSet 類的 Merge 方法將新數(shù)據(jù)集中的數(shù)據(jù)導入現(xiàn)有數(shù)據(jù)集,如下面的代碼示例所示。
DataSet newDataSet = myService.GetDataSet();
this.DataSet1.Clear();
this.DataSet1.Merge( newDataSet );
注 要避免線程化問題,您應該只在 UI 線程上更新綁定的數(shù)據(jù)對象。有關(guān)詳細信息,請參閱第 6 章:使用多個線程。
在數(shù)據(jù)集合中導航
如果您的數(shù)據(jù)源包含項集合,則可以將該數(shù)據(jù)集合綁定到 Windows 窗體控件,并且在該數(shù)據(jù)集合中逐項導航。用戶界面將自動更新以反映集合中的當前項。
您可以綁定到任何支持 IList 接口的集合對象。當您綁定到對象集合時,您可以讓用戶導航該集合中的每個項,并自動更新每個項的用戶界面。.NET Framework 提供的許多集合和復雜數(shù)據(jù)類已經(jīng)支持 IList 接口,因此您可以容易地綁定到數(shù)組或復雜數(shù)據(jù),如數(shù)據(jù)行或數(shù)據(jù)視圖。例如,任何作為 System.Array 類的實例的數(shù)組對象默認情況下都實現(xiàn)了 IList 接口,因而可以綁定到用戶界面。許多 ADO.NET 對象還支持 IList 接口或它的派生接口,從而使這些對象也可以容易地綁定。例如,DataViewManager、DataSet、DataTable、DataView 和 DataColumn 類都以這種方式支持數(shù)據(jù)綁定。
實現(xiàn)了 IList 接口的數(shù)據(jù)源由 CurrencyManager 對象管理。該對象通過它的 Position 屬性維護數(shù)據(jù)集合的索引。該索引用于確保綁定到該數(shù)據(jù)源的所有控件都讀/寫數(shù)據(jù)集合中的相同項。
如果您的窗體包含綁定到多個數(shù)據(jù)源的控件,則它將具有多個 CurrencyManager 對象,分別對應于各個獨立的數(shù)據(jù)源。BindingContext 對象提供對該窗體上的所有 CurrencyManager 對象的方便訪問。下面的代碼示例顯示了如何在 customers 集合內(nèi)部遞增當前位置。
this.BindingContext[ DataSet, "customers" ].Position += 1;
您應該像以下代碼示例中所示的那樣,使用 CurrencyManager 對象上的 Count 屬性來確保不會設(shè)置無效位置。
if ( this.BindingContext[ DataSet, "customer" ].Position <
( this.BindingContext[ DataSet, "customer" ].Count – 1 ) )
{
this.BindingContext[ DataSet, "customers" ].Position += 1;
}
CurrencyManager 對象還支持 PositionChanged 事件。您可以創(chuàng)建該事件的處理程序,以便更新您的用戶界面以反映當前綁定位置。下面的代碼示例顯示了一個標簽,以說明當前位置和記錄總數(shù)。
this.BindingContext[ DataSet, "customers" ].PositionChanged +=
new EventHandler( this.BindingPositionChanged );
方法 BindingPositionChanged 的實現(xiàn)方式如下所示。
private void BindingPositionChanged( object sender, System.EventArgs e )
{
positionLabel.Text = string.Format( "Record {0} of {1}",
this.BindingContext[dsPubs1, "authors"].Position + 1,
this.BindingContext[dsPubs1, "authors"].Count );
}
自定義格式和數(shù)據(jù)類型轉(zhuǎn)換
您可以使用 Binding 類的Format 和 Parse 事件為綁定到控件的數(shù)據(jù)提供自定義格式。通過這些事件,您可以控制在用戶界面中顯示數(shù)據(jù)的方式以及從用戶界面中獲取數(shù)據(jù)和分析數(shù)據(jù)的方式,以便更新基礎(chǔ)數(shù)據(jù)。還可以使用這些事件來轉(zhuǎn)換數(shù)據(jù)類型,以便源數(shù)據(jù)類型和目標數(shù)據(jù)類型兼容。
注 如果控件上綁定屬性的數(shù)據(jù)類型與數(shù)據(jù)源中數(shù)據(jù)的數(shù)據(jù)類型不匹配,則會引發(fā)異常。如果您需要綁定不兼容的類型,則應該使用 Binding 對象上的 Format 和 Parse 事件。
當從數(shù)據(jù)源中讀取數(shù)據(jù)并將其顯示在控件中時,以及當從控件中讀取數(shù)據(jù)并使用它來更新數(shù)據(jù)源時,將發(fā)生 Format 事件。當從數(shù)據(jù)源中讀取數(shù)據(jù)時,Binding 對象將使用 Format 事件在控件中顯示格式化數(shù)據(jù)。當從控件中讀取數(shù)據(jù)并使用它來更新數(shù)據(jù)源時,Binding 對象將使用 Parse 事件來分析數(shù)據(jù)。
Format 和 Parse 事件使您可以創(chuàng)建用于顯示數(shù)據(jù)的自定義格式。例如,如果表中的數(shù)據(jù)的類型是 Decimal,則您可以通過將 ConvertEventArgs 對象的 Value 屬性設(shè)置為 Format 事件中的格式化值,以本地貨幣格式顯示數(shù)據(jù)。因此,您必須在 Parse 事件中格式化顯示的值。
下面的代碼示例將訂單金額綁定到文本框。Format 和 Parse 事件用于在文本框期望的 string 類型和數(shù)據(jù)源期望的 decimal 類型之間進行轉(zhuǎn)換。
private void BindControl()
{
Binding binding = new Binding( "Text", DataSet,
"customers.custToOrders.OrderAmount" );
// Add the delegates to the event.
binding.Format += new ConvertEventHandler( DecimalToCurrencyString );
binding.Parse += new ConvertEventHandler( CurrencyStringToDecimal );
text1.DataBindings.Add( binding );
}
private void DecimalToCurrencyString( object sender, ConvertEventArgs cevent )
{
// The method converts only to string type. Test this using the
DesiredType.
if( cevent.DesiredType != typeof( string ) ) return;
// Use the ToString method to format the value as currency ("c").
cevent.Value = ((decimal)cevent.Value).ToString( "c" );
}
private void CurrencyStringToDecimal( object sender, ConvertEventArgs cevent )
{
// The method converts back to decimal type only.
if( cevent.DesiredType != typeof( decimal ) ) return;
// Converts the string back to decimal using the static Parse method.
cevent.Value = Decimal.Parse( cevent.Value.ToString(),
NumberStyles.Currency, null );
}
使用模型-視圖-控制器模式來實現(xiàn)數(shù)據(jù)驗證
通過將數(shù)據(jù)結(jié)構(gòu)綁定到用戶界面元素,用戶可以編輯數(shù)據(jù)并確保所做更改隨后被寫回到基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。通常,您需要檢查用戶對數(shù)據(jù)所做的更改,以確保輸入的值有效。
上一節(jié)中介紹的 Format 和 Parse 事件提供了一種用于截獲用戶對數(shù)據(jù)所做更改的方法,以便可以檢查數(shù)據(jù)的有效性。但是,該方法要求與自定義格式代碼一起實現(xiàn)數(shù)據(jù)驗證邏輯(通常是在窗體級別)。如果在事件處理程序中同時實現(xiàn)這兩種職責,則會使您的代碼難以理解和維護。
更為雅致的辦法是對代碼進行設(shè)計,以使其使用模型-視圖-控制器 (MVC) 模式。該模式提供了在通過數(shù)據(jù)綁定編輯和更改數(shù)據(jù)時涉及到的各種職責的自然分隔。您應該在負責以特定格式呈現(xiàn)數(shù)據(jù)的窗體內(nèi)實現(xiàn)自定義格式,然后將驗證規(guī)則與數(shù)據(jù)本身相關(guān)聯(lián),以便在多個窗體中重新使用這些規(guī)則。
在 MVC 模式中,數(shù)據(jù)本身被封裝在模型對象中。視圖對象是數(shù)據(jù)所綁定到的 Windows 窗體控件。對該模型所做的所有更改都由一個中間控制器對象處理,該對象負責提供對數(shù)據(jù)的訪問,并且負責控制通過視圖對象對數(shù)據(jù)所做的任何更改。控制器對象提供了一個用于驗證對數(shù)據(jù)所做更改的自然位置,所有用戶界面驗證邏輯都應該在這里實現(xiàn)。
圖 2.5 描繪了 MVC 模式中的三個對象之間的結(jié)構(gòu)關(guān)系。
圖 2.5 模型-視圖-控制器模式中的對象
以這種方式使用控制器對象具有許多優(yōu)點。您可以配置一個普通的控制器以提供自定義驗證規(guī)則,這些規(guī)則可以在運行時根據(jù)某些上下文信息(例如,用戶的角色)進行配置。或者,您還可以提供許多個控制器對象,每個控制器對象都實現(xiàn)特定的驗證規(guī)則,然后在運行時選擇適當?shù)膶ο蟆o論采用哪種方法,因為所有驗證邏輯都被封裝在控制器對象中,所以視圖和模型對象都不需要更改。
除了分隔數(shù)據(jù)、驗證邏輯和用戶界面控件以外,MVC 模型還為您提供了一種在基礎(chǔ)數(shù)據(jù)更改時自動更新用戶界面的簡單方法。控制器對象負責在發(fā)生通過其他某些編程手段對數(shù)據(jù)進行更改時通知用戶界面。Windows 窗體數(shù)據(jù)綁定偵聽由綁定到控件的對象生成的事件,以便用戶界面可以自動響應對基礎(chǔ)數(shù)據(jù)所做的更改。
要實現(xiàn)用戶界面的自動更新,您應該確保控制器為每個可能更改的屬性實現(xiàn)一個更改通知事件。事件應該遵循命名約定<property>Changed,其中 <property> 是屬性的名稱。例如,如果控制器支持 Name 屬性,則它還應該支持 NameChanged 事件。如果名稱屬性的值更改,則應該激發(fā)該事件,以便 Windows 窗體數(shù)據(jù)綁定可以處理它并更新用戶界面。
下面的代碼示例定義了一個 Customer 對象,該對象實現(xiàn)了 Name 屬性。CustomerController 對象處理 Customer 對象的驗證邏輯并支持 Name 屬性,而該屬性又表示基礎(chǔ) Customer 對象上的 Name 屬性。每當該名稱更改時,此控制器都將激發(fā)一個事件。
public class Customer
{
private string _name;
public Customer( string name ) { _name = name; }
public string Name
{
get { return _name; }
set { _name = value; }
}
}
public class CustomerController
{
private Customer _customer = null;
public event EventHandler NameChanged;
public Customer( Customer customer )
{
this._customer = customer;
}
public string Name
{
get { return _customer.Name; }
set
{
// TODO: Validate new name to make sure it is valid.
_customer.Name = value;
// Notify bound control of change.
if ( NameChanged != null )
NameChanged( this, EventArgs.Empty );
}
}
}
注 Customer 數(shù)據(jù)源成員在聲明時需要進行初始化。在前面的示例中,需要將 customer.Name 成員初始化為空字符串。這是因為在數(shù)據(jù)綁定發(fā)生之前,.NET 框架沒有機會與該對象進行交互并設(shè)置默認的空字符串設(shè)置。如果未初始化 customer 數(shù)據(jù)源成員,則在嘗試從未初始化的變量中檢索值時,將導致運行時異常。
在下面的代碼示例中,窗體具有一個 TextBox 對象 textbox1,它需要綁定到客戶的名稱。代碼將 TextBox 對象的 Text 屬性綁定到控制器的 Name 屬性。
_customer = new Customer( "Kelly Blue" );
_controller = new CustomerController( _customer );
Binding binding = new Binding( "Text", _controller, "Name" );
textBox1.DataBindings.Add( binding );
如果更改了客戶的名稱(使用控制器上的 Name 屬性),則會激發(fā) NameChanged 事件,并且自動更新文本框以反映新的名稱值。
在基礎(chǔ)數(shù)據(jù)更改時更新用戶界面
您可以使用 Windows 窗體數(shù)據(jù)綁定在相應的基礎(chǔ)數(shù)據(jù)更改時自動更新用戶界面。通過在綁定的對象上實現(xiàn)一個更改通知事件,可以完成該任務。更改通知事件按照以下約定命名。
public event EventHandler Changed;
因此,假設(shè)您將某個對象的 Name 屬性綁定到用戶界面,然后該對象的名稱由于其他某種處理而更改,則您可以通過實現(xiàn)綁定對象上的 NameChanged 事件來自動更新用戶界面,以反映新的 Name 值。
小結(jié)
在確定如何在智能客戶端處理數(shù)據(jù)時,涉及到許多不同的注意事項。您需要確定是否緩存以及如何緩存您的數(shù)據(jù),并且確定如何處理數(shù)據(jù)并發(fā)問題。您將經(jīng)常決定使用 ADO.NET 數(shù)據(jù)集來處理您的數(shù)據(jù),并且您還可能將決定利用 Windows 窗體數(shù)據(jù)綁定功能。
在許多情況下,只讀引用數(shù)據(jù)和瞬態(tài)數(shù)據(jù)需要進行不同的處理。因為智能客戶通常使用這兩種類型的數(shù)據(jù),所以您需要確定在應用程序中處理各個類別數(shù)據(jù)的最佳方式。
轉(zhuǎn)到原英文頁面