引自:http://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/SCArchDeGuide/Chapter1Introduction.mspx
第 2 章 — 處理數據

智能客戶端體系結構與設計指南
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
相關鏈接
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
摘要:本章分析在客戶端處理數據時的各種注意事項,包括數據緩存、數據并發以及數據集和 Windows 窗體數據綁定的使用。

本頁內容
![]() |
數據類型 |
![]() |
緩存數據 |
![]() |
數據并發 |
![]() |
使用 ADO.NET 數據集來管理數據 |
![]() |
Windows 窗體數據綁定 |
![]() |
小結 |
在智能客戶端中,可在客戶端上使用應用程序數據。要使您的智能客戶端有效工作,很重要的一點是對該數據進行適當的管理,以確保其有效、一致和安全。
可以通過服務器端應用程序(例如,通過 Web 服務)向客戶端提供應用程序數據,或者應用程序可以使用它自己的本地數據。如果數據是由應用程序提供的,則智能客戶端應用程序可以緩存數據以改善性能或者支持脫機使用。在這種情況下,您需要決定客戶端應用程序應該如何處理就該服務器而言已經過時的數據。
如果智能客戶端應用程序提供在本地修改數據的能力,則必須在以后將客戶端更改與服務器端應用程序進行同步。在這種情況下,您必須決定客戶端應用程序如何處理數據沖突,以及如何跟蹤需要發送到服務器的更改。
在設計您的智能客戶端應用程序時,您需要認真考慮這些問題以及其他許多問題。本章分析了在客戶端上處理數據時的各種注意事項,包括:
? |
數據類型。 |
? |
緩存數據。 |
? |
數據并發。 |
? |
使用 ADO.NET 數據集來管理數據。 |
? |
Windows 窗體數據綁定。 |
本章未討論其他許多與處理數據有關的問題。具體說來,在第 5 章:安全性注意事項中討論了數據處理安全性問題,在第 4 章:偶爾連接的智能客戶端中討論了脫機注意事項。
數據類型
智能客戶端通常必須處理兩種類別的數據:
? |
只讀引用數據 |
? |
瞬態數據 |
通常情況下,需要以不同的方式處理這些類型的數據,因此更詳細地分析一下每種類型將是很有用的。
只讀引用數據
只讀引用數據是不會由客戶端更改并且被客戶端用于引用目的的數據。因此,從客戶端的觀點看來,該數據為只讀數據,并且客戶端不會對其執行更新、插入或刪除操作。只讀引用數據很容易在客戶端上進行緩存。引用數據在智能客戶端應用程序中具有許多種用途,包括:
? |
提供靜態引用或查找數據。這方面的示例包括產品信息、價格表、發貨選項和價格。 |
? |
支持數據驗證,允許檢查用戶輸入數據的正確性。示例有針對交貨時間表檢查輸入的日期。 |
? |
幫助與遠程服務進行通訊。示例在本地將用戶選擇轉化為產品 ID,然后將該信息發送到 Web 服務。 |
? |
呈現數據。示例包括呈現幫助文本或用戶界面標簽。 |
通過在客戶端上存儲和使用引用數據,您可以減少需要從客戶端傳輸到服務器的數據量,改善應用程序的性能,幫助啟用脫機功能,并提供早期數據驗證以提高應用程序的可用性。
盡管客戶端無法更改只讀引用數據,但可以在服務器上進行更改(例如,由管理員或超級用戶更改)。您需要確定在發生數據更改時用于更新客戶端的策略。此類策略可能涉及到在發生更改時將更改推到客戶端上,或者按照特定的時間間隔或在客戶端上執行某些操作之前從服務器拉入更改。但是,因為數據在客戶端上是只讀的,所以您無須跟蹤客戶端更改。這就簡化了需要對只讀引用數據進行處理的方式。
瞬態數據
瞬態數據既可以在服務器上更改,也可以在客戶端上更改。通常情況下,瞬態數據作為用戶輸入和操作的直接或間接結果而發生更改。在此情況下,在客戶端或服務器進行的更改都需要在某個時刻進行同步。這種類型的數據在智能客戶端中具有許多種用途,包括:
? |
添加新信息。示例包括添加銀行業務交易或客戶詳細信息。 |
? |
修改現有信息。示例更新客戶詳細信息。 |
? |
刪除現有信息。示例從數據庫中刪除客戶。 |
在智能客戶端處理瞬態數據的最困難的方面之一在于,這些數據通常可能在多個客戶端上同時進行修改。當數據非常不穩定時,該問題將惡化,因為所做更改更有可能互相沖突。
您需要跟蹤您對瞬態數據進行的任何客戶端更改。在與服務器同步數據并且已經解決任何沖突之前,您不應該認為瞬態數據已被確認。您應該非常小心以避免依賴未確認的數據進行重要決策,或者在未認真考慮如何保證數據一致性(甚至在同步失敗時)的情況下使用該數據作為其他本地更改的基礎。
有關圍繞脫機時處理數據的問題以及如何處理數據同步的詳細信息,請參閱第 4 章:偶爾連接的智能客戶端。
緩存數據
智能客戶端通常需要在本地緩存數據(無論是只讀引用數據還是瞬態數據)。通過緩存數據,有可能改善應用程序的性能并提供脫機工作所需的數據。但是,您需要認真考慮在客戶端緩存哪些數據、如何管理這些數據以及可以在哪個上下文中使用這些數據。
要啟用數據緩存,您的智能客戶端應用程序應該實現某種形式的緩存基礎結構,以便透明地處理數據緩存細節。您的緩存基礎結構應該包括下列緩存機制中的一種或兩種:
? |
短期數據緩存。在內存中緩存數據對性能有益,但不能持久,因此您可能需要在重新運行應用程序時從源拉入數據。這樣做可能會妨礙您的應用程序脫機操作。 |
? |
長期數據緩存。通過在持久性媒體(如獨立存儲或本地文件系統)中緩存數據,可以在沒有連接到服務器時使用應用程序。您可以選擇將長期存儲與短期存儲結合起來以改善性能。 |
無論您采用哪種緩存機制,都應該確保僅將用戶有權訪問的數據提供給客戶端。而且,在客戶端緩存的敏感數據要求進行認真處理以確保它的安全。因此,您可能需要在將數據傳輸到客戶端以及在客戶端存儲數據時,對數據進行加密。有關詳細信息,請參閱第 5 章:安全性注意事項中的“處理敏感數據”。
當您設計智能客戶端以支持數據緩存時,您應該考慮為客戶端提供一種請求新數據的機制,而無論緩存的狀態如何。這意味著您可以確保應用程序隨時能夠執行新的事務,并且不會使用過時的數據。您還可以將客戶端配置為預先獲取數據,以便減少在緩存數據到期時處于脫機狀態的風險。
只要有可能,您都應該將某種形式的元數據與該數據關聯起來,以便使客戶端能夠以聰明的方式管理這些數據。此類元數據可用于指定數據的標識和任何約束,或者指定所需的與該數據關聯的行為。您的客戶端緩存基礎結構應該消耗該元數據,并且使用它來適當處理緩存的數據。
客戶端緩存的所有數據都應該是可以唯一標識的(例如,通過版本號或日期戳),以便在確定是否需要更新數據時,可以正確地識別相應的數據。這樣,您的緩存基礎結構就能夠詢問服務器它所具有的數據當前是否有效,并且確定是否需要進行更新。
元數據還可以用來指定與緩存數據的使用和處理相關的約束或行為。示例包括:
? |
時間約束。這些約束指定可以使用緩存數據的時間或日期范圍。當該數據過時或到期時,可以將其從緩存中丟棄,或者通過從服務器獲取最新數據來自動刷新數據。在某些情況下,合適的做法可能是讓客戶端使用過時的引用數據,并且在與服務器進行同步時將過時數據映射到最新數據。 |
? |
地理約束。某些數據可能僅適用于特定地區。例如,您可能對于不同的地點有不同的價格表。可以使用您的緩存基礎結構分別針對不同的地點來訪問和存儲數據。 |
? |
安全性要求。可以將專門提供給特定用戶的數據加密,以確保只有相應的用戶可以訪問這些數據。在此情況下,所提供的數據已經進行了加密,并且用戶必須向緩存基礎結構提供憑據以便對數據進行解密。 |
? |
業務規則。您可能擁有與緩存數據關聯的業務規則,用來規定如何使用這些數據。例如,您的緩存基礎結構可能考慮用戶的角色,以便確定向該用戶提供哪些數據以及如何處理這些數據。 |
您的緩存基礎結構可以通過與數據關聯的元數據來適當地處理這些數據,從而使您的應用程序無須關心數據緩存問題或實現細節。您可以在引用數據本身內部傳遞與這些數據關聯的元數據,或者您可以使用帶外機制。用于將元數據傳輸到客戶端的確切機制取決于您的應用程序與網絡服務的通訊方式。當使用 Web 服務時,利用 SOAP 頭將元數據傳輸到客戶端是一種很好的解決方案。
只讀引用數據與瞬態數據之間存在的區別有時意味著您需要使用兩個緩存,一個用于引用數據,一個用于瞬態數據。引用數據在客戶端是只讀的,并且不需要回過頭來與服務器進行同步,但它的確需要偶爾進行刷新以反映在服務器上進行的任何更改和更新。
瞬態數據既可以在服務器上更改,也可以在客戶端上更改。既然有時在客戶端更新緩存中的數據,有時在服務器更新,有時在這兩個位置更新,那么對客戶端數據進行的更改需要在某個時刻與服務器進行同步。如果數據同時在服務器上發生了更改,則會發生數據沖突,需要對其進行適當的處理。
要幫助確保維持數據一致性,并且避免不適當地使用數據,您應該小心地跟蹤您在客戶端對瞬態數據進行的任何更改。在成功地與服務器進行同步或確認之前,此類更改是未提交的 或暫定的。
您應該對您的智能客戶端應用程序進行適當的設計,以使其能夠區分已經成功地與服務器進行同步的數據和仍然暫定的數據。這一區分過程可以幫助應用程序更加容易地檢測和處理數據沖突。而且,您可能需要禁止應用程序或用戶基于暫定數據進行重要決策或者啟動重要操作。在將此類數據與服務器進行同步之前,不應該依賴它們。通過使用適當的緩存基礎結構,可以跟蹤暫定數據和已經確認的數據。
緩存應用程序塊(Caching Application Block)
緩存應用程序塊是一個 Microsoft? .NET 框架擴展,它使開發人員可以容易地緩存來自服務提供程序的數據。生成和設計它的目的是將 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. |
客戶端或服務代理向 CacheManager 發出對緩存數據項的請求。 |
2. |
如果該數據項已被緩存,則 CacheManager 會從存儲中檢索該項,并將其作為 CacheItem 對象返回。如果該項尚未緩存,則會通知客戶端。 |
3. |
在從服務提供程序檢索未緩存的數據之后,客戶端將該數據發送給 CacheManager。CacheManager 會將一個簽名(即,元數據)如密鑰、到期時間或優先級等添加到該數據項中,并將其加載到緩存中。 |
4. |
CacheService 監控 CacheItems 的生存期。當 CacheItem 到期時,CacheService 會刪除它并根據情況調用回調委托。 |
5. |
CacheService 還可以將所有數據項從緩存中清除出去。 |
緩存塊提供了多種緩存到期選項,如表 2.1 所述。
表 2.1 緩存塊到期選項
類 | 說明 |
AbsoluteTime |
用于設置到期時間的絕對時間。 |
ExtendedFormatTime |
用于基于表達式(如 every minute 或 every Monday)設置到期時間。 |
FileDependency |
用于基于文件是否更改來設置到期時間。 |
SlidingTime |
用于設置項的生存期,方法是基于項的上次訪問時間來指定到期時間。 |
下列存儲機制可供緩存塊使用:
? |
內存映射文件 (MMF)。MMF 最適合于基于客戶端的高性能緩存方案。您可以使用 MMF 來開發可在同一臺計算機中的多個應用程序域和進程之間共享的緩存。.NET 框架不支持 MMF,因此 MMF 緩存的任何實現都以非托管代碼的形式運行,并且不會從任何 .NET 框架功能中受益,包括內存管理功能(如垃圾回收)和安全性功能(如代碼訪問安全性)。 |
? |
Singleton 對象。可以使用 .NET 遠程處理 singleton 對象來緩存可在一臺或多臺計算機中的進程之間共享的數據。方法是使用通過 .NET 遠程處理為多個客戶端提供服務的 singleton 對象來實現緩存服務。單例緩存的實現很簡單,但它缺乏基于 Microsoft SQL Server? 的解決方案所提供的性能和可伸縮性。 |
? |
Microsoft SQL Server 2000 數據庫。SQL Server 2000 存儲最適合于應用程序要求具有高持續性或者您需要緩存大量數據的場合。因為緩存服務需要通過網絡訪問 SQL Server,并且使用數據庫查詢檢索數據,所以數據訪問的速度相對比較慢。 |
? |
Microsoft SQL Server 桌面引擎 (MSDE)。MSDE 是 SQL Server 2000 的輕型數據庫替代產品。它提供了可靠性和安全性功能,但具有比 SQL Server 更小的客戶端足跡,因此它需要較少的設置和配置。因為 MSDE 支持 SQL,所以開發人員可以得到數據庫的很多功能。如有必要,您可以將 MSDE 數據庫遷移到 SQL Server 數據庫。 |
數據并發
正如前面所提到的,使用智能客戶端的一個問題是:在將任何客戶端更改與服務器進行同步之前,服務器上保存的數據可能發生更改。您需要采用某種機制來確保在對數據進行同步時,數據沖突能夠得到適當的處理,并且最后得到的數據是一致和正確的。數據能夠由多個客戶端進行更新的能力稱為“數據并發”。
您可以使用兩種方法來處理數據并發:
? |
保守式并發。保守式并發允許一個客戶端保持數據上的鎖,以禁止任何其他客戶端修改數據,直至客戶端自己的更改完成為止。在這種情況下,如果另一個客戶端嘗試修改數據,則在鎖的擁有者釋放該鎖之前,這些嘗試將失敗或者被阻止。 |
? |
保守式并發可能有問題,因為單個用戶或客戶端可能由于疏忽而長時間地保持鎖定。所以,該鎖可能會妨礙重要資源(如數據庫行或文件)及時得到釋放,從而嚴重影響應用程序的可伸縮性和可用性。但是,當您需要完全控制對重要資源所做的更改時,保守式并發可能是適當的。請注意,如果您的客戶端要脫機工作,則不能使用這種并發,因為客戶端無法對數據加鎖。 |
? |
開放式并發。開放式并發不會鎖定數據。要判斷是否實際需要更新,可以將原始數據隨更新請求和已更改的數據一起發送。隨后,將針對當前數據檢查原始數據,以查看是否同時對原始數據進行了更新。如果原始數據和當前數據匹配,則執行更新;否則,拒絕請求,并產生開放式失敗。要優化該過程,您可以在數據中使用時間戳或更新計數器,而不必發送原始數據,此時只需要檢查時間戳或計數器。 開放式并發提供了一種良好的機制,可用來更新不會非常頻繁更改的主數據,如客戶的電話號碼或地址。開放式并發允許每個人讀取數據,在發生更新的概率小于讀取操作的情況下,開放式失敗的風險或許是可以接受的。在數據頻繁更改以及開放式更新可能經常失敗的情況下,開放式并發可能并不適合。 |
在大多數智能客戶端方案(包括客戶端將要脫機工作的方案)中,開放式并發是正確的方法,因為它允許多個客戶端同時使用數據,而不會不必要地鎖定數據和影響所有其他客戶端。
有關開放式和保守式并發的詳細信息,請參閱 .NET Framework Developer's Guide 中的“Optimistic Concurrency”,網址為:http://msdn.microsoft.com/library/en-us/cpguide/html/cpconoptimisticconcurrency.asp。
使用 ADO.NET 數據集來管理數據
DataSet 是一個表示一個或多個關系數據庫表的對象。數據集在斷開連接的緩存中存儲數據。DataSets的結構與關系數據庫類似:它公開了一個由表、行和列組成的層次結構對象模型。另外,它還包含為DataSets定義的約束和關系。
ADO.NET DataSet 包含零個或更多個由 DataTable 對象表示的表組成的集合。DataTable 在 System.Data 命名空間中定義,并且表示單個由內存駐留數據組成的表。它包含由 DataColumnCollection 表示的列和由 ConstraintCollection 表示的約束組成的集合,它們共同定義了該表的架構。DataTable 還包含由 DataRowCollection(它包含該表中的數據)表示的行組成的集合。與其當前狀態一起,DataRow 保留其當前版本和原始版本,以便標識對該行中存儲的值所做的更改。
DataSets可以強類型化或非類型化。類型化的 DataSet 從 DataSet 基類繼承,但是向 DataSet 中添加了強類型化的語言功能,從而使用戶可以用更加強類型化的編程方式訪問內容。在生成應用程序時,可以使用任一種類型。但是,Microsoft Visual Studio ? 開發系統對類型化DataSets具有更多支持,它們使得用DataSets編程變得更加容易,而且更不容易出錯。
DataSets在智能客戶端環境中尤其有用,因為它們提供了能夠幫助客戶端在脫機狀態下使用數據的功能。它們可以跟蹤對數據進行的本地更改,這有助于與服務器同步數據以及協調數據沖突,并且它們還可用于合并來自不同源的數據。
有關如何使用DataSets的詳細信息,請參閱 Visual Basic and Visual C# Concepts 中的“Introduction to DataSets”,網址為:http://msdn.microsoft.com/library/en-us/vbcon/html/vbconDataSets.asp。
用DataSets合并數據
DataSets具有將 DataSet、DataTable 或 DataRow 對象的內容合并到現有DataSets的能力。對于跟蹤在客戶端上進行的更改以及與服務器的已更新內容進行合并而言,該功能尤其有用。圖 2.2 顯示了一個從 Web 服務請求更新的智能客戶端,新數據作為數據傳輸對象 (DTO) 返回。DTO 是一種企業模式,它使您可以將所有需要與 Web 服務進行通訊的數據打包到一個對象中。使用 DTO 通常意味著您可以對 Web 服務進行單個調用而不是多個調用。

圖 2.2 通過使用DataSets合并客戶端上的數據
在該示例中,當 DTO 被返回到客戶端時,該 DTO 將被用于在客戶端上以本地方式創建一個新的DataSets。
注 在合并操作之后,ADO.NET 不會自動將行狀態從 modified 更改為 unchanged。因此,在將新的DataSets與本地客戶端DataSets合并之后,您需要調用DataSets上的 AccceptChanges 方法,將 RowState 屬性重置為 unchanged。
有關如何使用DataSets的詳細信息,請參閱 .NET Framework Developer's Guide 中的“Merging DataSet Contents”,網址為:http://msdn.microsoft.com/library/en-us/cpguide/html/cpconmergingDataSetcontents.asp。
提高DataSets的性能
DataSets通常可以包含大量數據,如果通過網絡傳遞這些數據,則可能導致性能問題。幸而,通過 ADO.NET DataSets,您可以使用DataSets上的 GetChanges 方法來確保只在客戶端和服務器之間傳送在DataSets中更改過的數據,并且將該數據打包到 DTO 中。該數據隨后將被合并到其目的地的DataSets中。
圖 2.3 顯示了一個智能客戶端,它對本地數據進行更改,并且使用DataSets上的 GetChanges 方法僅將已更改的數據提交給服務器。出于性能原因,該數據被傳輸給 DTO。

圖 2.3 使用 DTO 改善性能
可以將 GetChanges 方法用于需要脫機工作的智能客戶端應用程序。當應用程序重新聯機時,您可以使用 GetChanges 方法確定哪些信息已經更改,并且隨后生成一個與 Web 服務通訊的 DTO,以便確保將更改提交給數據庫。
Windows 窗體數據綁定
通過 Windows 窗體數據綁定,您可以將應用程序的用戶界面連接到該應用程序的基礎數據。Windows 窗體數據綁定支持雙向綁定,因此您可以將數據結構綁定到用戶界面,向用戶顯示當前數據值,使用戶可以編輯數據,然后使用用戶輸入的值自動更新基礎數據。
您可以使用 Windows 窗體數據綁定將幾乎任何數據結構或對象綁定到用戶界面控件的任何屬性。您可以將單個數據項綁定到控件的單個屬性,還可以將更為復雜的數據(例如,數據項集合或數據庫表)綁定到該控件,以便它可以在數據網格或列表框中顯示所有數據。
注 您可以綁定任何支持一個或多個公共屬性的對象。您只能綁定到類的公共屬性而不是公共成員。
通過 Windows 窗體數據綁定,您可以隨您的應用程序一起提供靈活的、數據驅動的用戶界面。您可以使用數據綁定提供對用戶界面外觀的自定義控制(例如,通過綁定到某些控件屬性,如背景或前景顏色、大小、圖像或圖標)。
數據綁定具有許多種用途。例如,可以使用它完成下列任務:
? |
向用戶顯示只讀數據。 |
? |
使用戶可以從用戶界面更新數據。 |
? |
提供數據上的主從視圖。 |
? |
使用戶可以瀏覽復雜的相關數據項。 |
? |
提供查找表功能,使用戶界面可以連接用戶友好的顯示名稱。 |
本節分析數據綁定的一些功能,并討論一些您經常需要在智能客戶端應用程序中實現的數據綁定功能。
有關數據綁定的詳細信息,請參閱“Windows Forms Data Binding and Objects”,網址為:http://msdn.microsoft.com/library/en-us/dnadvnet/html/vbnet02252003.asp。
Windows 窗體數據綁定體系結構
Windows 窗體數據綁定提供了一種用于將數據雙向連接到用戶界面的靈活的基礎結構。圖 2.4 顯示了 Windows 窗體數據綁定的總體體系結構的示意圖。

圖 2.4 Windows 窗體數據綁定的體系結構
Windows 窗體數據綁定使用下列對象:
? |
數據源。數據源是包含要綁定到用戶界面的數據的對象。數據提供程序可以是任何具有公共屬性的對象,可以是支持 IList 接口的數組或集合,還可以是復雜數據類(例如,DataSet 或 DataTable)的實例。 |
? |
CurrencyManager。CurrencyManager 對象用于跟蹤綁定到用戶界面的數組、集合或表內的數據的當前位置。通過 CurrencyManager 可以將數據集合綁定到用戶界面以及在相應的數據中導航,同時更新用戶界面以反映集合內當前選擇的項。 |
? |
PropertyManager。PropertyManager 對象負責維護綁定到控件的對象的當前屬性。PropertyManager 類和 CurrencyManager 類都從公用基類 BindingManagerBase 中繼承。所有綁定到控件的數據提供程序都具有一個關聯的 CurrencyManager 或 PropertyManager 對象。 |
? |
BindingContext。每個 Windows 窗體都具有一個默認的 BindingContext 對象,該對象跟蹤相應窗體上的所有 CurrencyManager 和 PropertyManager 對象。通過 BindingContext 對象可以容易地檢索特定數據源的 CurrencyManager 或 PropertyManager 對象。您可以將特定的 BindingContext 對象分配給包含數據綁定控件的容器控件(如 GroupBox、Panel 或 TabControl)。這樣做可以使窗體的每個部分都由它自己的 CurrencyManager 或 PropertyManager 對象管理。 |
? |
Binding。Binding 對象用于在控件的單個屬性與另一個對象的屬性或某個對象列表中當前對象的屬性之間創建和維護簡單綁定。 |
將數據綁定到 Windows 窗體控件
有許多可用于綁定到特定 Windows 窗體控件的屬性和方法。表 2.2 顯示了其中一些比較重要的屬性和方法。
表 2.2 用于綁定到 Windows 窗體控件的屬性和方法
屬性或方法 | Windows 窗體控件 | 說明 |
DataSource 屬性 |
ListControls(例如,ListBox 或 Combo Box)、 DataGrid 控件 |
使您可以指定要綁定到用戶界面控件的數據提供程序對象。 |
DisplayMember 屬性 |
ListControls |
使您可以指定要顯示給用戶的數據提供程序的成員。 |
ValueMember 屬性 |
ListControls |
使您可以指定與顯示值相關聯的、供您的應用程序內部使用的值。 |
DataMember 屬性 |
DataGrid 控件 |
如果數據源包含多個數據源(例如,如果您指定了包含多個表的DataSets),請使用 DataMember 屬性來指定要綁定到網格的數據源。(參閱表后面的備注。) |
SetDataBinding 方法 |
DataGrid 控件 |
使您可以在運行時重置 DataSource 方法。 |
注 如果 DataSource 是 DataTable、DataView、集合或數組,則無須設置 DataMember 屬性。
您還可以使用所有 Windows 窗體控件對象上提供的 DataBindings 集合屬性將 Binding 對象顯式添加到任何控件對象。Binding 對象用于將控件上的單個屬性綁定到數據提供程序的單個數據成員。下面的代碼示例在一個文本框控件的 Text 屬性和一個數據集的 customers 表中的客戶名稱之間添加了綁定。
textBox1.DataBindings.Add( new Binding( "Text", DataSet, "customers.customerName" ) );
當您用 Binding 構造函數構建 Binding 示例時,您必須指定要綁定到的控件屬性的名稱、數據源以及可解析為該數據源中的列表或屬性的導航路徑。該導航路徑可以是空字符串、單個屬性名或句點分隔的名稱層次結構。您可以使用分層的導航路徑在 DataSet 對象中的數據表和關系中導航,或者在對象的屬性向其他對象返回實例的對象模型中導航。如果您將導航路徑設置為空字符串,則會在基礎數據源對象上調用 ToString 方法。
注 如果屬性是只讀的(即,對象不支持對該屬性進行的設置操作),則數據綁定默認情況下不會使綁定的 Windows 窗體控件成為只讀的。這可能給用戶帶來混亂,因為用戶可以編輯用戶界面中的值,但綁定對象中的值將不會得到更新。所以,請確保將所有被綁定到只讀屬性的 Windows 窗體控件的只讀標志設置為 true。
將控件綁定到DataSets
將控件綁定到數據集通常是有用的。這樣做使您可以在數據網格中顯示數據集數據,并且使用戶可以容易地更新數據。您可以使用以下代碼將數據網格控件綁定到 DataSet。
DataSet newDataSet = webServiceProxy.GetDataSet(); this.DataGrid.SetDataBinding( newDataSet, "tableName" );
有時,在已經建立與您的控件的所有綁定之后,您需要替換數據集的內容。但是,在用新的集合替換現有集合時,所有綁定仍將指向舊的數據集。
比用新的數據源手動重新創建數據綁定更好的辦法是,您可以使用 DataSet 類的 Merge 方法將新數據集中的數據導入現有數據集,如下面的代碼示例所示。
DataSet newDataSet = myService.GetDataSet(); this.DataSet1.Clear(); this.DataSet1.Merge( newDataSet );
注 要避免線程化問題,您應該只在 UI 線程上更新綁定的數據對象。有關詳細信息,請參閱第 6 章:使用多個線程。
在數據集合中導航
如果您的數據源包含項集合,則可以將該數據集合綁定到 Windows 窗體控件,并且在該數據集合中逐項導航。用戶界面將自動更新以反映集合中的當前項。
您可以綁定到任何支持 IList 接口的集合對象。當您綁定到對象集合時,您可以讓用戶導航該集合中的每個項,并自動更新每個項的用戶界面。.NET Framework 提供的許多集合和復雜數據類已經支持 IList 接口,因此您可以容易地綁定到數組或復雜數據,如數據行或數據視圖。例如,任何作為 System.Array 類的實例的數組對象默認情況下都實現了 IList 接口,因而可以綁定到用戶界面。許多 ADO.NET 對象還支持 IList 接口或它的派生接口,從而使這些對象也可以容易地綁定。例如,DataViewManager、DataSet、DataTable、DataView 和 DataColumn 類都以這種方式支持數據綁定。
實現了 IList 接口的數據源由 CurrencyManager 對象管理。該對象通過它的 Position 屬性維護數據集合的索引。該索引用于確保綁定到該數據源的所有控件都讀/寫數據集合中的相同項。
如果您的窗體包含綁定到多個數據源的控件,則它將具有多個 CurrencyManager 對象,分別對應于各個獨立的數據源。BindingContext 對象提供對該窗體上的所有 CurrencyManager 對象的方便訪問。下面的代碼示例顯示了如何在 customers 集合內部遞增當前位置。
this.BindingContext[ DataSet, "customers" ].Position += 1;
您應該像以下代碼示例中所示的那樣,使用 CurrencyManager 對象上的 Count 屬性來確保不會設置無效位置。
if ( this.BindingContext[ DataSet, "customer" ].Position < ( this.BindingContext[ DataSet, "customer" ].Count – 1 ) ) { this.BindingContext[ DataSet, "customers" ].Position += 1; }
CurrencyManager 對象還支持 PositionChanged 事件。您可以創建該事件的處理程序,以便更新您的用戶界面以反映當前綁定位置。下面的代碼示例顯示了一個標簽,以說明當前位置和記錄總數。
this.BindingContext[ DataSet, "customers" ].PositionChanged += new EventHandler( this.BindingPositionChanged );
方法 BindingPositionChanged 的實現方式如下所示。
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 ); }
自定義格式和數據類型轉換
您可以使用 Binding 類的Format 和 Parse 事件為綁定到控件的數據提供自定義格式。通過這些事件,您可以控制在用戶界面中顯示數據的方式以及從用戶界面中獲取數據和分析數據的方式,以便更新基礎數據。還可以使用這些事件來轉換數據類型,以便源數據類型和目標數據類型兼容。
注 如果控件上綁定屬性的數據類型與數據源中數據的數據類型不匹配,則會引發異常。如果您需要綁定不兼容的類型,則應該使用 Binding 對象上的 Format 和 Parse 事件。
當從數據源中讀取數據并將其顯示在控件中時,以及當從控件中讀取數據并使用它來更新數據源時,將發生 Format 事件。當從數據源中讀取數據時,Binding 對象將使用 Format 事件在控件中顯示格式化數據。當從控件中讀取數據并使用它來更新數據源時,Binding 對象將使用 Parse 事件來分析數據。
Format 和 Parse 事件使您可以創建用于顯示數據的自定義格式。例如,如果表中的數據的類型是 Decimal,則您可以通過將 ConvertEventArgs 對象的 Value 屬性設置為 Format 事件中的格式化值,以本地貨幣格式顯示數據。因此,您必須在 Parse 事件中格式化顯示的值。
下面的代碼示例將訂單金額綁定到文本框。Format 和 Parse 事件用于在文本框期望的 string 類型和數據源期望的 decimal 類型之間進行轉換。
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 ); }
使用模型-視圖-控制器模式來實現數據驗證
通過將數據結構綁定到用戶界面元素,用戶可以編輯數據并確保所做更改隨后被寫回到基礎數據結構。通常,您需要檢查用戶對數據所做的更改,以確保輸入的值有效。
上一節中介紹的 Format 和 Parse 事件提供了一種用于截獲用戶對數據所做更改的方法,以便可以檢查數據的有效性。但是,該方法要求與自定義格式代碼一起實現數據驗證邏輯(通常是在窗體級別)。如果在事件處理程序中同時實現這兩種職責,則會使您的代碼難以理解和維護。
更為雅致的辦法是對代碼進行設計,以使其使用模型-視圖-控制器 (MVC) 模式。該模式提供了在通過數據綁定編輯和更改數據時涉及到的各種職責的自然分隔。您應該在負責以特定格式呈現數據的窗體內實現自定義格式,然后將驗證規則與數據本身相關聯,以便在多個窗體中重新使用這些規則。
在 MVC 模式中,數據本身被封裝在模型對象中。視圖對象是數據所綁定到的 Windows 窗體控件。對該模型所做的所有更改都由一個中間控制器對象處理,該對象負責提供對數據的訪問,并且負責控制通過視圖對象對數據所做的任何更改。控制器對象提供了一個用于驗證對數據所做更改的自然位置,所有用戶界面驗證邏輯都應該在這里實現。
圖 2.5 描繪了 MVC 模式中的三個對象之間的結構關系。

圖 2.5 模型-視圖-控制器模式中的對象
以這種方式使用控制器對象具有許多優點。您可以配置一個普通的控制器以提供自定義驗證規則,這些規則可以在運行時根據某些上下文信息(例如,用戶的角色)進行配置。或者,您還可以提供許多個控制器對象,每個控制器對象都實現特定的驗證規則,然后在運行時選擇適當的對象。無論采用哪種方法,因為所有驗證邏輯都被封裝在控制器對象中,所以視圖和模型對象都不需要更改。
除了分隔數據、驗證邏輯和用戶界面控件以外,MVC 模型還為您提供了一種在基礎數據更改時自動更新用戶界面的簡單方法。控制器對象負責在發生通過其他某些編程手段對數據進行更改時通知用戶界面。Windows 窗體數據綁定偵聽由綁定到控件的對象生成的事件,以便用戶界面可以自動響應對基礎數據所做的更改。
要實現用戶界面的自動更新,您應該確保控制器為每個可能更改的屬性實現一個更改通知事件。事件應該遵循命名約定<property>Changed,其中 <property> 是屬性的名稱。例如,如果控制器支持 Name 屬性,則它還應該支持 NameChanged 事件。如果名稱屬性的值更改,則應該激發該事件,以便 Windows 窗體數據綁定可以處理它并更新用戶界面。
下面的代碼示例定義了一個 Customer 對象,該對象實現了 Name 屬性。CustomerController 對象處理 Customer 對象的驗證邏輯并支持 Name 屬性,而該屬性又表示基礎 Customer 對象上的 Name 屬性。每當該名稱更改時,此控制器都將激發一個事件。
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 數據源成員在聲明時需要進行初始化。在前面的示例中,需要將 customer.Name 成員初始化為空字符串。這是因為在數據綁定發生之前,.NET 框架沒有機會與該對象進行交互并設置默認的空字符串設置。如果未初始化 customer 數據源成員,則在嘗試從未初始化的變量中檢索值時,將導致運行時異常。
在下面的代碼示例中,窗體具有一個 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 屬性),則會激發 NameChanged 事件,并且自動更新文本框以反映新的名稱值。
在基礎數據更改時更新用戶界面
您可以使用 Windows 窗體數據綁定在相應的基礎數據更改時自動更新用戶界面。通過在綁定的對象上實現一個更改通知事件,可以完成該任務。更改通知事件按照以下約定命名。
public event EventHandler Changed;
因此,假設您將某個對象的 Name 屬性綁定到用戶界面,然后該對象的名稱由于其他某種處理而更改,則您可以通過實現綁定對象上的 NameChanged 事件來自動更新用戶界面,以反映新的 Name 值。
小結
在確定如何在智能客戶端處理數據時,涉及到許多不同的注意事項。您需要確定是否緩存以及如何緩存您的數據,并且確定如何處理數據并發問題。您將經常決定使用 ADO.NET 數據集來處理您的數據,并且您還可能將決定利用 Windows 窗體數據綁定功能。
在許多情況下,只讀引用數據和瞬態數據需要進行不同的處理。因為智能客戶通常使用這兩種類型的數據,所以您需要確定在應用程序中處理各個類別數據的最佳方式。