歡迎來(lái)到 MSDN >
體系結構 >
智能客戶(hù)端體系結構與設計指南第 2 章 — 處理數據
發(fā)布日期: 8/20/2004 | 更新日期: 8/20/2004
智能客戶(hù)端體系結構與設計指南
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.mspxApplication Architecture for .NET: Designing Applications and Services
http://msdn.microsoft.com/library/en-us/dnbda/html/distapp.asp摘要:本章分析在客戶(hù)端處理數據時(shí)的各種注意事項,包括數據緩存、數據并發(fā)以及數據集和 Windows 窗體數據綁定的使用。
本頁(yè)內容
數據類(lèi)型緩存數據數據并發(fā)使用 ADO.NET 數據集來(lái)管理數據Windows 窗體數據綁定小結在智能客戶(hù)端中,可在客戶(hù)端上使用應用程序數據。要使您的智能客戶(hù)端有效工作,很重要的一點(diǎn)是對該數據進(jìn)行適當的管理,以確保其有效、一致和安全。
可以通過(guò)服務(wù)器端應用程序(例如,通過(guò) Web 服務(wù))向客戶(hù)端提供應用程序數據,或者應用程序可以使用它自己的本地數據。如果數據是由應用程序提供的,則智能客戶(hù)端應用程序可以緩存數據以改善性能或者支持脫機使用。在這種情況下,您需要決定客戶(hù)端應用程序應該如何處理就該服務(wù)器而言已經(jīng)過(guò)時(shí)的數據。
如果智能客戶(hù)端應用程序提供在本地修改數據的能力,則必須在以后將客戶(hù)端更改與服務(wù)器端應用程序進(jìn)行同步。在這種情況下,您必須決定客戶(hù)端應用程序如何處理數據沖突,以及如何跟蹤需要發(fā)送到服務(wù)器的更改。
在設計您的智能客戶(hù)端應用程序時(shí),您需要認真考慮這些問(wèn)題以及其他許多問(wèn)題。本章分析了在客戶(hù)端上處理數據時(shí)的各種注意事項,包括:
•
數據類(lèi)型。
•
緩存數據。
•
數據并發(fā)。
•
使用 ADO.NET 數據集來(lái)管理數據。
•
Windows 窗體數據綁定。
本章未討論其他許多與處理數據有關(guān)的問(wèn)題。具體說(shuō)來(lái),在
第 5 章:安全性注意事項中討論了數據處理安全性問(wèn)題,在
第 4 章:偶爾連接的智能客戶(hù)端中討論了脫機注意事項。
數據類(lèi)型
智能客戶(hù)端通常必須處理兩種類(lèi)別的數據:
•
只讀引用數據
•
瞬態(tài)數據
通常情況下,需要以不同的方式處理這些類(lèi)型的數據,因此更詳細地分析一下每種類(lèi)型將是很有用的。
只讀引用數據
只讀引用數據是不會(huì )由客戶(hù)端更改并且被客戶(hù)端用于引用目的的數據。因此,從客戶(hù)端的觀(guān)點(diǎn)看來(lái),該數據為只讀數據,并且客戶(hù)端不會(huì )對其執行更新、插入或刪除操作。只讀引用數據很容易在客戶(hù)端上進(jìn)行緩存。引用數據在智能客戶(hù)端應用程序中具有許多種用途,包括:
•
提供靜態(tài)引用或查找數據。這方面的示例包括產(chǎn)品信息、價(jià)格表、發(fā)貨選項和價(jià)格。
•
支持數據驗證,允許檢查用戶(hù)輸入數據的正確性。示例有針對交貨時(shí)間表檢查輸入的日期。
•
幫助與遠程服務(wù)進(jìn)行通訊。示例在本地將用戶(hù)選擇轉化為產(chǎn)品 ID,然后將該信息發(fā)送到 Web 服務(wù)。
•
呈現數據。示例包括呈現幫助文本或用戶(hù)界面標簽。
通過(guò)在客戶(hù)端上存儲和使用引用數據,您可以減少需要從客戶(hù)端傳輸到服務(wù)器的數據量,改善應用程序的性能,幫助啟用脫機功能,并提供早期數據驗證以提高應用程序的可用性。
盡管客戶(hù)端無(wú)法更改只讀引用數據,但可以在服務(wù)器上進(jìn)行更改(例如,由管理員或超級用戶(hù)更改)。您需要確定在發(fā)生數據更改時(shí)用于更新客戶(hù)端的策略。此類(lèi)策略可能涉及到在發(fā)生更改時(shí)將更改推到客戶(hù)端上,或者按照特定的時(shí)間間隔或在客戶(hù)端上執行某些操作之前從服務(wù)器拉入更改。但是,因為數據在客戶(hù)端上是只讀的,所以您無(wú)須跟蹤客戶(hù)端更改。這就簡(jiǎn)化了需要對只讀引用數據進(jìn)行處理的方式。
瞬態(tài)數據
瞬態(tài)數據既可以在服務(wù)器上更改,也可以在客戶(hù)端上更改。通常情況下,瞬態(tài)數據作為用戶(hù)輸入和操作的直接或間接結果而發(fā)生更改。在此情況下,在客戶(hù)端或服務(wù)器進(jìn)行的更改都需要在某個(gè)時(shí)刻進(jìn)行同步。這種類(lèi)型的數據在智能客戶(hù)端中具有許多種用途,包括:
•
添加新信息。示例包括添加銀行業(yè)務(wù)交易或客戶(hù)詳細信息。
•
修改現有信息。示例更新客戶(hù)詳細信息。
•
刪除現有信息。示例從數據庫中刪除客戶(hù)。
在智能客戶(hù)端處理瞬態(tài)數據的最困難的方面之一在于,這些數據通??赡茉诙鄠€(gè)客戶(hù)端上同時(shí)進(jìn)行修改。當數據非常不穩定時(shí),該問(wèn)題將惡化,因為所做更改更有可能互相沖突。
您需要跟蹤您對瞬態(tài)數據進(jìn)行的任何客戶(hù)端更改。在與服務(wù)器同步數據并且已經(jīng)解決任何沖突之前,您不應該認為瞬態(tài)數據已被確認。您應該非常小心以避免依賴(lài)未確認的數據進(jìn)行重要決策,或者在未認真考慮如何保證數據一致性(甚至在同步失敗時(shí))的情況下使用該數據作為其他本地更改的基礎。
有關(guān)圍繞脫機時(shí)處理數據的問(wèn)題以及如何處理數據同步的詳細信息,請參閱
第 4 章:偶爾連接的智能客戶(hù)端。
返回頁(yè)首緩存數據
智能客戶(hù)端通常需要在本地緩存數據(無(wú)論是只讀引用數據還是瞬態(tài)數據)。通過(guò)緩存數據,有可能改善應用程序的性能并提供脫機工作所需的數據。但是,您需要認真考慮在客戶(hù)端緩存哪些數據、如何管理這些數據以及可以在哪個(gè)上下文中使用這些數據。
要啟用數據緩存,您的智能客戶(hù)端應用程序應該實(shí)現某種形式的緩存基礎結構,以便透明地處理數據緩存細節。您的緩存基礎結構應該包括下列緩存機制中的一種或兩種:
•
短期數據緩存。在內存中緩存數據對性能有益,但不能持久,因此您可能需要在重新運行應用程序時(shí)從源拉入數據。這樣做可能會(huì )妨礙您的應用程序脫機操作。
•
長(cháng)期數據緩存。通過(guò)在持久性媒體(如獨立存儲或本地文件系統)中緩存數據,可以在沒(méi)有連接到服務(wù)器時(shí)使用應用程序。您可以選擇將長(cháng)期存儲與短期存儲結合起來(lái)以改善性能。
無(wú)論您采用哪種緩存機制,都應該確保僅將用戶(hù)有權訪(fǎng)問(wèn)的數據提供給客戶(hù)端。而且,在客戶(hù)端緩存的敏感數據要求進(jìn)行認真處理以確保它的安全。因此,您可能需要在將數據傳輸到客戶(hù)端以及在客戶(hù)端存儲數據時(shí),對數據進(jìn)行加密。有關(guān)詳細信息,請參閱
第 5 章:安全性注意事項中的“處理敏感數據”。
當您設計智能客戶(hù)端以支持數據緩存時(shí),您應該考慮為客戶(hù)端提供一種請求新數據的機制,而無(wú)論緩存的狀態(tài)如何。這意味著(zhù)您可以確保應用程序隨時(shí)能夠執行新的事務(wù),并且不會(huì )使用過(guò)時(shí)的數據。您還可以將客戶(hù)端配置為預先獲取數據,以便減少在緩存數據到期時(shí)處于脫機狀態(tài)的風(fēng)險。
只要有可能,您都應該將某種形式的元數據與該數據關(guān)聯(lián)起來(lái),以便使客戶(hù)端能夠以聰明的方式管理這些數據。此類(lèi)元數據可用于指定數據的標識和任何約束,或者指定所需的與該數據關(guān)聯(lián)的行為。您的客戶(hù)端緩存基礎結構應該消耗該元數據,并且使用它來(lái)適當處理緩存的數據。
客戶(hù)端緩存的所有數據都應該是可以唯一標識的(例如,通過(guò)版本號或日期戳),以便在確定是否需要更新數據時(shí),可以正確地識別相應的數據。這樣,您的緩存基礎結構就能夠詢(xún)問(wèn)服務(wù)器它所具有的數據當前是否有效,并且確定是否需要進(jìn)行更新。
元數據還可以用來(lái)指定與緩存數據的使用和處理相關(guān)的約束或行為。示例包括:
•
時(shí)間約束。這些約束指定可以使用緩存數據的時(shí)間或日期范圍。當該數據過(guò)時(shí)或到期時(shí),可以將其從緩存中丟棄,或者通過(guò)從服務(wù)器獲取最新數據來(lái)自動(dòng)刷新數據。在某些情況下,合適的做法可能是讓客戶(hù)端使用過(guò)時(shí)的引用數據,并且在與服務(wù)器進(jìn)行同步時(shí)將過(guò)時(shí)數據映射到最新數據。
•
地理約束。某些數據可能僅適用于特定地區。例如,您可能對于不同的地點(diǎn)有不同的價(jià)格表??梢允褂媚木彺婊A結構分別針對不同的地點(diǎn)來(lái)訪(fǎng)問(wèn)和存儲數據。
•
安全性要求??梢詫?zhuān)門(mén)提供給特定用戶(hù)的數據加密,以確保只有相應的用戶(hù)可以訪(fǎng)問(wèn)這些數據。在此情況下,所提供的數據已經(jīng)進(jìn)行了加密,并且用戶(hù)必須向緩存基礎結構提供憑據以便對數據進(jìn)行解密。
•
業(yè)務(wù)規則。您可能擁有與緩存數據關(guān)聯(lián)的業(yè)務(wù)規則,用來(lái)規定如何使用這些數據。例如,您的緩存基礎結構可能考慮用戶(hù)的角色,以便確定向該用戶(hù)提供哪些數據以及如何處理這些數據。
您的緩存基礎結構可以通過(guò)與數據關(guān)聯(lián)的元數據來(lái)適當地處理這些數據,從而使您的應用程序無(wú)須關(guān)心數據緩存問(wèn)題或實(shí)現細節。您可以在引用數據本身內部傳遞與這些數據關(guān)聯(lián)的元數據,或者您可以使用帶外機制。用于將元數據傳輸到客戶(hù)端的確切機制取決于您的應用程序與網(wǎng)絡(luò )服務(wù)的通訊方式。當使用 Web 服務(wù)時(shí),利用 SOAP 頭將元數據傳輸到客戶(hù)端是一種很好的解決方案。
只讀引用數據與瞬態(tài)數據之間存在的區別有時(shí)意味著(zhù)您需要使用兩個(gè)緩存,一個(gè)用于引用數據,一個(gè)用于瞬態(tài)數據。引用數據在客戶(hù)端是只讀的,并且不需要回過(guò)頭來(lái)與服務(wù)器進(jìn)行同步,但它的確需要偶爾進(jìn)行刷新以反映在服務(wù)器上進(jìn)行的任何更改和更新。
瞬態(tài)數據既可以在服務(wù)器上更改,也可以在客戶(hù)端上更改。既然有時(shí)在客戶(hù)端更新緩存中的數據,有時(shí)在服務(wù)器更新,有時(shí)在這兩個(gè)位置更新,那么對客戶(hù)端數據進(jìn)行的更改需要在某個(gè)時(shí)刻與服務(wù)器進(jìn)行同步。如果數據同時(shí)在服務(wù)器上發(fā)生了更改,則會(huì )發(fā)生數據沖突,需要對其進(jìn)行適當的處理。
要幫助確保維持數據一致性,并且避免不適當地使用數據,您應該小心地跟蹤您在客戶(hù)端對瞬態(tài)數據進(jìn)行的任何更改。在成功地與服務(wù)器進(jìn)行同步或確認之前,此類(lèi)更改是未提交的 或暫定的。
您應該對您的智能客戶(hù)端應用程序進(jìn)行適當的設計,以使其能夠區分已經(jīng)成功地與服務(wù)器進(jìn)行同步的數據和仍然暫定的數據。這一區分過(guò)程可以幫助應用程序更加容易地檢測和處理數據沖突。而且,您可能需要禁止應用程序或用戶(hù)基于暫定數據進(jìn)行重要決策或者啟動(dòng)重要操作。在將此類(lèi)數據與服務(wù)器進(jìn)行同步之前,不應該依賴(lài)它們。通過(guò)使用適當的緩存基礎結構,可以跟蹤暫定數據和已經(jīng)確認的數據。
緩存應用程序塊(Caching Application Block)
緩存應用程序塊是一個(gè) Microsoft? .NET 框架擴展,它使開(kāi)發(fā)人員可以容易地緩存來(lái)自服務(wù)提供程序的數據。生成和設計它的目的是將 Microsoft 建議的緩存準則封裝在 .NET 框架應用程序中,如位于 http://msdn.microsoft.com/library/en-us/dnbda/html/CachingArch.asp 的 Caching Architecture Guide for .NET Framework Applicationss 所述。
緩存塊的總體體系結構如圖 2.1 所示。
圖 2.1 緩存塊工作流
緩存工作流包含下列步驟:
1.
客戶(hù)端或服務(wù)代理向 CacheManager 發(fā)出對緩存數據項的請求。
2.
如果該數據項已被緩存,則 CacheManager 會(huì )從存儲中檢索該項,并將其作為 CacheItem 對象返回。如果該項尚未緩存,則會(huì )通知客戶(hù)端。
3.
在從服務(wù)提供程序檢索未緩存的數據之后,客戶(hù)端將該數據發(fā)送給 CacheManager。CacheManager 會(huì )將一個(gè)簽名(即,元數據)如密鑰、到期時(shí)間或優(yōu)先級等添加到該數據項中,并將其加載到緩存中。
4.
CacheService 監控 CacheItems 的生存期。當 CacheItem 到期時(shí),CacheService 會(huì )刪除它并根據情況調用回調委托。
5.
CacheService 還可以將所有數據項從緩存中清除出去。
緩存塊提供了多種緩存到期選項,如表 2.1 所述。
表 2.1 緩存塊到期選項
類(lèi) 說(shuō)明
AbsoluteTime
用于設置到期時(shí)間的絕對時(shí)間。
ExtendedFormatTime
用于基于表達式(如 every minute 或 every Monday)設置到期時(shí)間。
FileDependency
用于基于文件是否更改來(lái)設置到期時(shí)間。
SlidingTime
用于設置項的生存期,方法是基于項的上次訪(fǎng)問(wèn)時(shí)間來(lái)指定到期時(shí)間。
下列存儲機制可供緩存塊使用:
•
內存映射文件 (MMF)。MMF 最適合于基于客戶(hù)端的高性能緩存方案。您可以使用 MMF 來(lái)開(kāi)發(fā)可在同一臺計算機中的多個(gè)應用程序域和進(jìn)程之間共享的緩存。.NET 框架不支持 MMF,因此 MMF 緩存的任何實(shí)現都以非托管代碼的形式運行,并且不會(huì )從任何 .NET 框架功能中受益,包括內存管理功能(如垃圾回收)和安全性功能(如代碼訪(fǎng)問(wèn)安全性)。
•
Singleton 對象??梢允褂?.NET 遠程處理 singleton 對象來(lái)緩存可在一臺或多臺計算機中的進(jìn)程之間共享的數據。方法是使用通過(guò) .NET 遠程處理為多個(gè)客戶(hù)端提供服務(wù)的 singleton 對象來(lái)實(shí)現緩存服務(wù)。單例緩存的實(shí)現很簡(jiǎn)單,但它缺乏基于 Microsoft SQL Server™ 的解決方案所提供的性能和可伸縮性。
•
Microsoft SQL Server 2000 數據庫。SQL Server 2000 存儲最適合于應用程序要求具有高持續性或者您需要緩存大量數據的場(chǎng)合。因為緩存服務(wù)需要通過(guò)網(wǎng)絡(luò )訪(fǎng)問(wèn) SQL Server,并且使用數據庫查詢(xún)檢索數據,所以數據訪(fǎng)問(wèn)的速度相對比較慢。
•
Microsoft SQL Server 桌面引擎 (MSDE)。MSDE 是 SQL Server 2000 的輕型數據庫替代產(chǎn)品。它提供了可靠性和安全性功能,但具有比 SQL Server 更小的客戶(hù)端足跡,因此它需要較少的設置和配置。因為 MSDE 支持 SQL,所以開(kāi)發(fā)人員可以得到數據庫的很多功能。如有必要,您可以將 MSDE 數據庫遷移到 SQL Server 數據庫。
返回頁(yè)首數據并發(fā)
正如前面所提到的,使用智能客戶(hù)端的一個(gè)問(wèn)題是:在將任何客戶(hù)端更改與服務(wù)器進(jìn)行同步之前,服務(wù)器上保存的數據可能發(fā)生更改。您需要采用某種機制來(lái)確保在對數據進(jìn)行同步時(shí),數據沖突能夠得到適當的處理,并且最后得到的數據是一致和正確的。數據能夠由多個(gè)客戶(hù)端進(jìn)行更新的能力稱(chēng)為“數據并發(fā)”。
您可以使用兩種方法來(lái)處理數據并發(fā):
•
保守式并發(fā)。保守式并發(fā)允許一個(gè)客戶(hù)端保持數據上的鎖,以禁止任何其他客戶(hù)端修改數據,直至客戶(hù)端自己的更改完成為止。在這種情況下,如果另一個(gè)客戶(hù)端嘗試修改數據,則在鎖的擁有者釋放該鎖之前,這些嘗試將失敗或者被阻止。
•
保守式并發(fā)可能有問(wèn)題,因為單個(gè)用戶(hù)或客戶(hù)端可能由于疏忽而長(cháng)時(shí)間地保持鎖定。所以,該鎖可能會(huì )妨礙重要資源(如數據庫行或文件)及時(shí)得到釋放,從而嚴重影響應用程序的可伸縮性和可用性。但是,當您需要完全控制對重要資源所做的更改時(shí),保守式并發(fā)可能是適當的。請注意,如果您的客戶(hù)端要脫機工作,則不能使用這種并發(fā),因為客戶(hù)端無(wú)法對數據加鎖。
•
開(kāi)放式并發(fā)。開(kāi)放式并發(fā)不會(huì )鎖定數據。要判斷是否實(shí)際需要更新,可以將原始數據隨更新請求和已更改的數據一起發(fā)送。隨后,將針對當前數據檢查原始數據,以查看是否同時(shí)對原始數據進(jìn)行了更新。如果原始數據和當前數據匹配,則執行更新;否則,拒絕請求,并產(chǎn)生開(kāi)放式失敗。要優(yōu)化該過(guò)程,您可以在數據中使用時(shí)間戳或更新計數器,而不必發(fā)送原始數據,此時(shí)只需要檢查時(shí)間戳或計數器。
開(kāi)放式并發(fā)提供了一種良好的機制,可用來(lái)更新不會(huì )非常頻繁更改的主數據,如客戶(hù)的電話(huà)號碼或地址。開(kāi)放式并發(fā)允許每個(gè)人讀取數據,在發(fā)生更新的概率小于讀取操作的情況下,開(kāi)放式失敗的風(fēng)險或許是可以接受的。在數據頻繁更改以及開(kāi)放式更新可能經(jīng)常失敗的情況下,開(kāi)放式并發(fā)可能并不適合。
在大多數智能客戶(hù)端方案(包括客戶(hù)端將要脫機工作的方案)中,開(kāi)放式并發(fā)是正確的方法,因為它允許多個(gè)客戶(hù)端同時(shí)使用數據,而不會(huì )不必要地鎖定數據和影響所有其他客戶(hù)端。
有關(guān)開(kāi)放式和保守式并發(fā)的詳細信息,請參閱 .NET Framework Developer‘s Guide 中的“Optimistic Concurrency”,網(wǎng)址為:
http://msdn.microsoft.com/library/en-us/cpguide/html/cpconoptimisticconcurrency.asp。
返回頁(yè)首使用 ADO.NET 數據集來(lái)管理數據
DataSet 是一個(gè)表示一個(gè)或多個(gè)關(guān)系數據庫表的對象。數據集在斷開(kāi)連接的緩存中存儲數據。DataSets的結構與關(guān)系數據庫類(lèi)似:它公開(kāi)了一個(gè)由表、行和列組成的層次結構對象模型。另外,它還包含為DataSets定義的約束和關(guān)系。
ADO.NET DataSet 包含零個(gè)或更多個(gè)由 DataTable 對象表示的表組成的集合。DataTable 在 System.Data 命名空間中定義,并且表示單個(gè)由內存駐留數據組成的表。它包含由 DataColumnCollection 表示的列和由 ConstraintCollection 表示的約束組成的集合,它們共同定義了該表的架構。DataTable 還包含由 DataRowCollection(它包含該表中的數據)表示的行組成的集合。與其當前狀態(tài)一起,DataRow 保留其當前版本和原始版本,以便標識對該行中存儲的值所做的更改。
DataSets可以強類(lèi)型化或非類(lèi)型化。類(lèi)型化的 DataSet 從 DataSet 基類(lèi)繼承,但是向 DataSet 中添加了強類(lèi)型化的語(yǔ)言功能,從而使用戶(hù)可以用更加強類(lèi)型化的編程方式訪(fǎng)問(wèn)內容。在生成應用程序時(shí),可以使用任一種類(lèi)型。但是,Microsoft Visual Studio ® 開(kāi)發(fā)系統對類(lèi)型化DataSets具有更多支持,它們使得用DataSets編程變得更加容易,而且更不容易出錯。
DataSets在智能客戶(hù)端環(huán)境中尤其有用,因為它們提供了能夠幫助客戶(hù)端在脫機狀態(tài)下使用數據的功能。它們可以跟蹤對數據進(jìn)行的本地更改,這有助于與服務(wù)器同步數據以及協(xié)調數據沖突,并且它們還可用于合并來(lái)自不同源的數據。
有關(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合并數據
DataSets具有將 DataSet、DataTable 或 DataRow 對象的內容合并到現有DataSets的能力。對于跟蹤在客戶(hù)端上進(jìn)行的更改以及與服務(wù)器的已更新內容進(jìn)行合并而言,該功能尤其有用。圖 2.2 顯示了一個(gè)從 Web 服務(wù)請求更新的智能客戶(hù)端,新數據作為數據傳輸對象 (DTO) 返回。DTO 是一種企業(yè)模式,它使您可以將所有需要與 Web 服務(wù)進(jìn)行通訊的數據打包到一個(gè)對象中。使用 DTO 通常意味著(zhù)您可以對 Web 服務(wù)進(jìn)行單個(gè)調用而不是多個(gè)調用。
圖 2.2 通過(guò)使用DataSets合并客戶(hù)端上的數據
在該示例中,當 DTO 被返回到客戶(hù)端時(shí),該 DTO 將被用于在客戶(hù)端上以本地方式創(chuàng )建一個(gè)新的DataSets。
注 在合并操作之后,ADO.NET 不會(huì )自動(dòng)將行狀態(tài)從 modified 更改為 unchanged。因此,在將新的DataSets與本地客戶(hù)端DataSets合并之后,您需要調用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通??梢园罅繑祿?,如果通過(guò)網(wǎng)絡(luò )傳遞這些數據,則可能導致性能問(wèn)題。幸而,通過(guò) ADO.NET DataSets,您可以使用DataSets上的 GetChanges 方法來(lái)確保只在客戶(hù)端和服務(wù)器之間傳送在DataSets中更改過(guò)的數據,并且將該數據打包到 DTO 中。該數據隨后將被合并到其目的地的DataSets中。
圖 2.3 顯示了一個(gè)智能客戶(hù)端,它對本地數據進(jìn)行更改,并且使用DataSets上的 GetChanges 方法僅將已更改的數據提交給服務(wù)器。出于性能原因,該數據被傳輸給 DTO。
圖 2.3 使用 DTO 改善性能
可以將 GetChanges 方法用于需要脫機工作的智能客戶(hù)端應用程序。當應用程序重新聯(lián)機時(shí),您可以使用 GetChanges 方法確定哪些信息已經(jīng)更改,并且隨后生成一個(gè)與 Web 服務(wù)通訊的 DTO,以便確保將更改提交給數據庫。
返回頁(yè)首Windows 窗體數據綁定
通過(guò) Windows 窗體數據綁定,您可以將應用程序的用戶(hù)界面連接到該應用程序的基礎數據。Windows 窗體數據綁定支持雙向綁定,因此您可以將數據結構綁定到用戶(hù)界面,向用戶(hù)顯示當前數據值,使用戶(hù)可以編輯數據,然后使用用戶(hù)輸入的值自動(dòng)更新基礎數據。
您可以使用 Windows 窗體數據綁定將幾乎任何數據結構或對象綁定到用戶(hù)界面控件的任何屬性。您可以將單個(gè)數據項綁定到控件的單個(gè)屬性,還可以將更為復雜的數據(例如,數據項集合或數據庫表)綁定到該控件,以便它可以在數據網(wǎng)格或列表框中顯示所有數據。
注 您可以綁定任何支持一個(gè)或多個(gè)公共屬性的對象。您只能綁定到類(lèi)的公共屬性而不是公共成員。
通過(guò) Windows 窗體數據綁定,您可以隨您的應用程序一起提供靈活的、數據驅動(dòng)的用戶(hù)界面。您可以使用數據綁定提供對用戶(hù)界面外觀(guān)的自定義控制(例如,通過(guò)綁定到某些控件屬性,如背景或前景顏色、大小、圖像或圖標)。
數據綁定具有許多種用途。例如,可以使用它完成下列任務(wù):
•
向用戶(hù)顯示只讀數據。
•
使用戶(hù)可以從用戶(hù)界面更新數據。
•
提供數據上的主從視圖。
•
使用戶(hù)可以瀏覽復雜的相關(guān)數據項。
•
提供查找表功能,使用戶(hù)界面可以連接用戶(hù)友好的顯示名稱(chēng)。
本節分析數據綁定的一些功能,并討論一些您經(jīng)常需要在智能客戶(hù)端應用程序中實(shí)現的數據綁定功能。
有關(guān)數據綁定的詳細信息,請參閱“Windows Forms Data Binding and Objects”,網(wǎng)址為:
http://msdn.microsoft.com/library/en-us/dnadvnet/html/vbnet02252003.asp。
Windows 窗體數據綁定體系結構
Windows 窗體數據綁定提供了一種用于將數據雙向連接到用戶(hù)界面的靈活的基礎結構。圖 2.4 顯示了 Windows 窗體數據綁定的總體體系結構的示意圖。
圖 2.4 Windows 窗體數據綁定的體系結構
Windows 窗體數據綁定使用下列對象:
•
數據源。數據源是包含要綁定到用戶(hù)界面的數據的對象。數據提供程序可以是任何具有公共屬性的對象,可以是支持 IList 接口的數組或集合,還可以是復雜數據類(lèi)(例如,DataSet 或 DataTable)的實(shí)例。
•
CurrencyManager。CurrencyManager 對象用于跟蹤綁定到用戶(hù)界面的數組、集合或表內的數據的當前位置。通過(guò) CurrencyManager 可以將數據集合綁定到用戶(hù)界面以及在相應的數據中導航,同時(shí)更新用戶(hù)界面以反映集合內當前選擇的項。
•
PropertyManager。PropertyManager 對象負責維護綁定到控件的對象的當前屬性。PropertyManager 類(lèi)和 CurrencyManager 類(lèi)都從公用基類(lèi) BindingManagerBase 中繼承。所有綁定到控件的數據提供程序都具有一個(gè)關(guān)聯(lián)的 CurrencyManager 或 PropertyManager 對象。
•
BindingContext。每個(gè) Windows 窗體都具有一個(gè)默認的 BindingContext 對象,該對象跟蹤相應窗體上的所有 CurrencyManager 和 PropertyManager 對象。通過(guò) BindingContext 對象可以容易地檢索特定數據源的 CurrencyManager 或 PropertyManager 對象。您可以將特定的 BindingContext 對象分配給包含數據綁定控件的容器控件(如 GroupBox、Panel 或 TabControl)。這樣做可以使窗體的每個(gè)部分都由它自己的 CurrencyManager 或 PropertyManager 對象管理。
•
Binding。Binding 對象用于在控件的單個(gè)屬性與另一個(gè)對象的屬性或某個(gè)對象列表中當前對象的屬性之間創(chuàng )建和維護簡(jiǎn)單綁定。
將數據綁定到 Windows 窗體控件
有許多可用于綁定到特定 Windows 窗體控件的屬性和方法。表 2.2 顯示了其中一些比較重要的屬性和方法。
表 2.2 用于綁定到 Windows 窗體控件的屬性和方法
屬性或方法 Windows 窗體控件 說(shuō)明
DataSource 屬性
ListControls(例如,ListBox 或 Combo Box)、
DataGrid 控件
使您可以指定要綁定到用戶(hù)界面控件的數據提供程序對象。
DisplayMember 屬性
ListControls
使您可以指定要顯示給用戶(hù)的數據提供程序的成員。
ValueMember 屬性
ListControls
使您可以指定與顯示值相關(guān)聯(lián)的、供您的應用程序內部使用的值。
DataMember 屬性
DataGrid 控件
如果數據源包含多個(gè)數據源(例如,如果您指定了包含多個(gè)表的DataSets),請使用 DataMember 屬性來(lái)指定要綁定到網(wǎng)格的數據源。(參閱表后面的備注。)
SetDataBinding 方法
DataGrid 控件
使您可以在運行時(shí)重置 DataSource 方法。
注 如果 DataSource 是 DataTable、DataView、集合或數組,則無(wú)須設置 DataMember 屬性。
您還可以使用所有 Windows 窗體控件對象上提供的 DataBindings 集合屬性將 Binding 對象顯式添加到任何控件對象。Binding 對象用于將控件上的單個(gè)屬性綁定到數據提供程序的單個(gè)數據成員。下面的代碼示例在一個(gè)文本框控件的 Text 屬性和一個(gè)數據集的 customers 表中的客戶(hù)名稱(chēng)之間添加了綁定。
textBox1.DataBindings.Add(new Binding( "Text", DataSet, "customers.customerName" ) );
當您用 Binding 構造函數構建 Binding 示例時(shí),您必須指定要綁定到的控件屬性的名稱(chēng)、數據源以及可解析為該數據源中的列表或屬性的導航路徑。該導航路徑可以是空字符串、單個(gè)屬性名或句點(diǎn)分隔的名稱(chēng)層次結構。您可以使用分層的導航路徑在 DataSet 對象中的數據表和關(guān)系中導航,或者在對象的屬性向其他對象返回實(shí)例的對象模型中導航。如果您將導航路徑設置為空字符串,則會(huì )在基礎數據源對象上調用 ToString 方法。
注 如果屬性是只讀的(即,對象不支持對該屬性進(jìn)行的設置操作),則數據綁定默認情況下不會(huì )使綁定的 Windows 窗體控件成為只讀的。這可能給用戶(hù)帶來(lái)混亂,因為用戶(hù)可以編輯用戶(hù)界面中的值,但綁定對象中的值將不會(huì )得到更新。所以,請確保將所有被綁定到只讀屬性的 Windows 窗體控件的只讀標志設置為 true。
將控件綁定到DataSets
將控件綁定到數據集通常是有用的。這樣做使您可以在數據網(wǎng)格中顯示數據集數據,并且使用戶(hù)可以容易地更新數據。您可以使用以下代碼將數據網(wǎng)格控件綁定到 DataSet。
DataSet newDataSet = webServiceProxy.GetDataSet();this.DataGrid.SetDataBinding( newDataSet, "tableName" );
有時(shí),在已經(jīng)建立與您的控件的所有綁定之后,您需要替換數據集的內容。但是,在用新的集合替換現有集合時(shí),所有綁定仍將指向舊的數據集。
比用新的數據源手動(dòng)重新創(chuàng )建數據綁定更好的辦法是,您可以使用 DataSet 類(lèi)的 Merge 方法將新數據集中的數據導入現有數據集,如下面的代碼示例所示。
DataSet newDataSet = myService.GetDataSet();this.DataSet1.Clear();this.DataSet1.Merge( newDataSet );
注 要避免線(xiàn)程化問(wèn)題,您應該只在 UI 線(xiàn)程上更新綁定的數據對象。有關(guān)詳細信息,請參閱
第 6 章:使用多個(gè)線(xiàn)程。
在數據集合中導航
如果您的數據源包含項集合,則可以將該數據集合綁定到 Windows 窗體控件,并且在該數據集合中逐項導航。用戶(hù)界面將自動(dòng)更新以反映集合中的當前項。
您可以綁定到任何支持 IList 接口的集合對象。當您綁定到對象集合時(shí),您可以讓用戶(hù)導航該集合中的每個(gè)項,并自動(dòng)更新每個(gè)項的用戶(hù)界面。.NET Framework 提供的許多集合和復雜數據類(lèi)已經(jīng)支持 IList 接口,因此您可以容易地綁定到數組或復雜數據,如數據行或數據視圖。例如,任何作為 System.Array 類(lèi)的實(shí)例的數組對象默認情況下都實(shí)現了 IList 接口,因而可以綁定到用戶(hù)界面。許多 ADO.NET 對象還支持 IList 接口或它的派生接口,從而使這些對象也可以容易地綁定。例如,DataViewManager、DataSet、DataTable、DataView 和 DataColumn 類(lèi)都以這種方式支持數據綁定。
實(shí)現了 IList 接口的數據源由 CurrencyManager 對象管理。該對象通過(guò)它的 Position 屬性維護數據集合的索引。該索引用于確保綁定到該數據源的所有控件都讀/寫(xiě)數據集合中的相同項。
如果您的窗體包含綁定到多個(gè)數據源的控件,則它將具有多個(gè) CurrencyManager 對象,分別對應于各個(gè)獨立的數據源。BindingContext 對象提供對該窗體上的所有 CurrencyManager 對象的方便訪(fǎng)問(wèn)。下面的代碼示例顯示了如何在 customers 集合內部遞增當前位置。
this.BindingContext[ DataSet, "customers" ].Position += 1;
您應該像以下代碼示例中所示的那樣,使用 CurrencyManager 對象上的 Count 屬性來(lái)確保不會(huì )設置無(wú)效位置。
if ( this.BindingContext[ DataSet, "customer" ].Position <( this.BindingContext[ DataSet, "customer" ].Count – 1 ) ){this.BindingContext[ DataSet, "customers" ].Position += 1;}
CurrencyManager 對象還支持 PositionChanged 事件。您可以創(chuàng )建該事件的處理程序,以便更新您的用戶(hù)界面以反映當前綁定位置。下面的代碼示例顯示了一個(gè)標簽,以說(shuō)明當前位置和記錄總數。
this.BindingContext[ DataSet, "customers" ].PositionChanged +=new EventHandler( this.BindingPositionChanged );
方法 BindingPositionChanged 的實(shí)現方式如下所示。
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 );}自定義格式和數據類(lèi)型轉換
您可以使用 Binding 類(lèi)的Format 和 Parse 事件為綁定到控件的數據提供自定義格式。通過(guò)這些事件,您可以控制在用戶(hù)界面中顯示數據的方式以及從用戶(hù)界面中獲取數據和分析數據的方式,以便更新基礎數據。還可以使用這些事件來(lái)轉換數據類(lèi)型,以便源數據類(lèi)型和目標數據類(lèi)型兼容。
注 如果控件上綁定屬性的數據類(lèi)型與數據源中數據的數據類(lèi)型不匹配,則會(huì )引發(fā)異常。如果您需要綁定不兼容的類(lèi)型,則應該使用 Binding 對象上的 Format 和 Parse 事件。
當從數據源中讀取數據并將其顯示在控件中時(shí),以及當從控件中讀取數據并使用它來(lái)更新數據源時(shí),將發(fā)生 Format 事件。當從數據源中讀取數據時(shí),Binding 對象將使用 Format 事件在控件中顯示格式化數據。當從控件中讀取數據并使用它來(lái)更新數據源時(shí),Binding 對象將使用 Parse 事件來(lái)分析數據。
Format 和 Parse 事件使您可以創(chuàng )建用于顯示數據的自定義格式。例如,如果表中的數據的類(lèi)型是 Decimal,則您可以通過(guò)將 ConvertEventArgs 對象的 Value 屬性設置為 Format 事件中的格式化值,以本地貨幣格式顯示數據。因此,您必須在 Parse 事件中格式化顯示的值。
下面的代碼示例將訂單金額綁定到文本框。Format 和 Parse 事件用于在文本框期望的 string 類(lèi)型和數據源期望的 decimal 類(lèi)型之間進(jì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 theDesiredType.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 );}使用模型-視圖-控制器模式來(lái)實(shí)現數據驗證
通過(guò)將數據結構綁定到用戶(hù)界面元素,用戶(hù)可以編輯數據并確保所做更改隨后被寫(xiě)回到基礎數據結構。通常,您需要檢查用戶(hù)對數據所做的更改,以確保輸入的值有效。
上一節中介紹的 Format 和 Parse 事件提供了一種用于截獲用戶(hù)對數據所做更改的方法,以便可以檢查數據的有效性。但是,該方法要求與自定義格式代碼一起實(shí)現數據驗證邏輯(通常是在窗體級別)。如果在事件處理程序中同時(shí)實(shí)現這兩種職責,則會(huì )使您的代碼難以理解和維護。
更為雅致的辦法是對代碼進(jìn)行設計,以使其使用模型-視圖-控制器 (MVC) 模式。該模式提供了在通過(guò)數據綁定編輯和更改數據時(shí)涉及到的各種職責的自然分隔。您應該在負責以特定格式呈現數據的窗體內實(shí)現自定義格式,然后將驗證規則與數據本身相關(guān)聯(lián),以便在多個(gè)窗體中重新使用這些規則。
在 MVC 模式中,數據本身被封裝在模型對象中。視圖對象是數據所綁定到的 Windows 窗體控件。對該模型所做的所有更改都由一個(gè)中間控制器對象處理,該對象負責提供對數據的訪(fǎng)問(wèn),并且負責控制通過(guò)視圖對象對數據所做的任何更改??刂破鲗ο筇峁┝艘粋€(gè)用于驗證對數據所做更改的自然位置,所有用戶(hù)界面驗證邏輯都應該在這里實(shí)現。
圖 2.5 描繪了 MVC 模式中的三個(gè)對象之間的結構關(guān)系。
圖 2.5 模型-視圖-控制器模式中的對象
以這種方式使用控制器對象具有許多優(yōu)點(diǎn)。您可以配置一個(gè)普通的控制器以提供自定義驗證規則,這些規則可以在運行時(shí)根據某些上下文信息(例如,用戶(hù)的角色)進(jìn)行配置?;蛘?,您還可以提供許多個(gè)控制器對象,每個(gè)控制器對象都實(shí)現特定的驗證規則,然后在運行時(shí)選擇適當的對象。無(wú)論采用哪種方法,因為所有驗證邏輯都被封裝在控制器對象中,所以視圖和模型對象都不需要更改。
除了分隔數據、驗證邏輯和用戶(hù)界面控件以外,MVC 模型還為您提供了一種在基礎數據更改時(shí)自動(dòng)更新用戶(hù)界面的簡(jiǎn)單方法??刂破鲗ο筘撠熢诎l(fā)生通過(guò)其他某些編程手段對數據進(jìn)行更改時(shí)通知用戶(hù)界面。Windows 窗體數據綁定偵聽(tīng)由綁定到控件的對象生成的事件,以便用戶(hù)界面可以自動(dòng)響應對基礎數據所做的更改。
要實(shí)現用戶(hù)界面的自動(dòng)更新,您應該確??刂破鳛槊總€(gè)可能更改的屬性實(shí)現一個(gè)更改通知事件。事件應該遵循命名約定<property>Changed,其中 <property> 是屬性的名稱(chēng)。例如,如果控制器支持 Name 屬性,則它還應該支持 NameChanged 事件。如果名稱(chēng)屬性的值更改,則應該激發(fā)該事件,以便 Windows 窗體數據綁定可以處理它并更新用戶(hù)界面。
下面的代碼示例定義了一個(gè) Customer 對象,該對象實(shí)現了 Name 屬性。CustomerController 對象處理 Customer 對象的驗證邏輯并支持 Name 屬性,而該屬性又表示基礎 Customer 對象上的 Name 屬性。每當該名稱(chēng)更改時(shí),此控制器都將激發(fā)一個(gè)事件。
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ìn)行初始化。在前面的示例中,需要將 customer.Name 成員初始化為空字符串。這是因為在數據綁定發(fā)生之前,.NET 框架沒(méi)有機會(huì )與該對象進(jìn)行交互并設置默認的空字符串設置。如果未初始化 customer 數據源成員,則在嘗試從未初始化的變量中檢索值時(shí),將導致運行時(shí)異常。
在下面的代碼示例中,窗體具有一個(gè) TextBox 對象 textbox1,它需要綁定到客戶(hù)的名稱(chēng)。代碼將 TextBox 對象的 Text 屬性綁定到控制器的 Name 屬性。
_customer = new Customer( "Kelly Blue" );_controller = new CustomerController( _customer );Binding binding = new Binding( "Text", _controller, "Name" );textBox1.DataBindings.Add( binding );
如果更改了客戶(hù)的名稱(chēng)(使用控制器上的 Name 屬性),則會(huì )激發(fā) NameChanged 事件,并且自動(dòng)更新文本框以反映新的名稱(chēng)值。
在基礎數據更改時(shí)更新用戶(hù)界面
您可以使用 Windows 窗體數據綁定在相應的基礎數據更改時(shí)自動(dòng)更新用戶(hù)界面。通過(guò)在綁定的對象上實(shí)現一個(gè)更改通知事件,可以完成該任務(wù)。更改通知事件按照以下約定命名。
public event EventHandler Changed;
因此,假設您將某個(gè)對象的 Name 屬性綁定到用戶(hù)界面,然后該對象的名稱(chēng)由于其他某種處理而更改,則您可以通過(guò)實(shí)現綁定對象上的 NameChanged 事件來(lái)自動(dòng)更新用戶(hù)界面,以反映新的 Name 值。
返回頁(yè)首小結
在確定如何在智能客戶(hù)端處理數據時(shí),涉及到許多不同的注意事項。您需要確定是否緩存以及如何緩存您的數據,并且確定如何處理數據并發(fā)問(wèn)題。您將經(jīng)常決定使用 ADO.NET 數據集來(lái)處理您的數據,并且您還可能將決定利用 Windows 窗體數據綁定功能。
在許多情況下,只讀引用數據和瞬態(tài)數據需要進(jìn)行不同的處理。因為智能客戶(hù)通常使用這兩種類(lèi)型的數據,所以您需要確定在應用程序中處理各個(gè)類(lèi)別數據的最佳方式。
轉到原英文頁(yè)面返回頁(yè)首