使用數據集(DataSet)、數據表(DataTable)、集合(Collection)傳遞數據
數據集(DataSet)、數據表(DataTable)、集合(Collection)概念是.NET FrameWork裡提供數據類型,在應用程序編程過程中會經常使用其來作為數據的載體,屬於ADO.NET的一部分。今天我們WCF分布式開發步步為贏第8節的內容:使用數據集(DataSet)、數據表(DataTable)、集合(Collection)傳遞數據。本節內容除了介紹幾個類型概念外的,同樣會詳細給出代碼的實現過程。此外我們會分析這幾種數據類型的優勢和缺點,以及在面向對象的服務開發過程中如何解決這些問題。
(昨天博客園發布文章出錯,沒辦法只有現在重新發了,可惜我花了很久排版,沒保存成功,結果還是要重新組織。大家有好的方法可以介紹一下~)
【1】數據集(DataSet)、數據表(DataTable):
我們首先來介紹這兩個類型的相關概念,然後在介紹其在WCF應用程序開發中的使用方式。
【1.1】基本概念:
數據集(DataSet)、數據表(DataTable),相信大家都不回陌生,只要做過ADO.NET進行數據庫編程的開發人員來說,都會使用到這兩個類。DataSet 是 ADO.NET 結構的主要組件,它是從數據源中檢索到的數據在內存中的緩存。DataSet 由一組 DataTable 對象組成,您可使這些對象與 DataRelation 對象互相關聯。您還可通過使用 UniqueConstraint 和 ForeignKeyConstraint 對象在 DataSet 中實施數據完整性。有關使用 DataSet 對象的詳細信息,請參見 在 ADO.NET 中使用 DataSet。
盡管 DataTable 對象中包含數據,但是 DataRelationCollection 允許您遍覽表的層次結構。這些表包含在通過 Tables 屬性訪問的 DataTableCollection 中。當訪問 DataTable 對象時,請注意它們是按條件區分大小寫的。例如,如果一個 DataTable 被命名為“mydatatable”,另一個被命名為“Mydatatable”,則用於搜索其中一個表的字符串被認為是區分大小寫的。但是,如果“mydatatable”存在而“Mydatatable”不存在,則認為該搜索字符串不區分大小寫。有關使用 DataTable 對象的更多信息,請參見 創建 DataTable。
DataSet 可將數據和架構作為 XML 文檔進行讀寫。數據和架構可通過 HTTP 傳輸,並在支持 XML 的任何平台上被任何應用程序使用。可使用 WriteXmlSchema 方法將架構保存為 XML 架構,並且可以使用 WriteXml 方法保存架構和數據。若要讀取既包含架構也包含數據的 XML 文檔,請使用 ReadXml 方法。
在典型的多層實現中,用於創建和刷新 DataSet 並依次更新原始數據的步驟包括:
通過 DataAdapter 使用數據源中的數據生成和填充 DataSet 中的每個 DataTable。
通過添加、更新或刪除 DataRow 對象更改單個 DataTable 對象中的數據。
調用 GetChanges 方法以創建只反映對數據進行的更改的第二個 DataSet。
調用 DataAdapter 的 Update 方法,並將第二個 DataSet 作為參數傳遞。
調用 Merge 方法將第二個 DataSet 中的更改合並到第一個中。
針對 DataSet 調用 AcceptChanges。或者,調用 RejectChanges 以取消更改。
【1.2】
DataSet 和 DataTable 對象從 MarshalByValueComponent 繼承而來,並支持用於遠程處理的 ISerializable 接口。這些是僅有的可以遠程處理的 ADO.NET 對象。我們先來看一下DataSet的定義,使用Reflector工具查看,部分代碼如下:
[Serializable, ToolboxItem("Microsoft.VSDesigner.Data.VS.DataSetToolboxItem, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), DefaultProperty("DataSetName"), XmlSchemaProvider("GetDataSetSchema"), ResDescription("DataSetDescr"), XmlRoot("DataSet"), Designer("Microsoft.VSDesigner.Data.VS.DataSetDesigner, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
public class DataSet : MarshalByValueComponent, IListSource, IXmlSerializable, ISupportInitializeNotification, ISupportInitialize, ISerializable
{
// Fields
private bool _caseSensitive;
private CultureInfo _culture;
private bool _cultureUserSet;
private string _datasetPrefix;
private object _defaultViewManagerLock;
private readonly int _objectID;
private static int _objectTypeCount;
private SerializationFormat _remotingFormat;
private string dataSetName;
private DataViewManager defaultViewManager;
private bool enforceConstraints;
internal PropertyCollection extendedProperties;
private bool fBoundToDocument;
internal bool fEnableCascading;
internal bool fInitInProgress;
}
DataTable的部分實現代碼如下:
[Serializable, Editor("Microsoft.VSDesigner.Data.Design.DataTableEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), DefaultProperty("TableName"), DesignTimeVisible(false), ToolboxItem(false), XmlSchemaProvider("GetDataTableSchema"), DefaultEvent("RowChanging")]
public class DataTable : MarshalByValueComponent, IListSource, ISupportInitializeNotification, ISupportInitialize, ISerializable, IXmlSerializable
{
// Fields
private bool _caseSensitive;
private bool _caseSensitiveUserSet;
internal DataColumn _colUnique;
private CompareOptions _compareFlags;
private CompareInfo _compareInfo;
}
在WCF分布式應用程序開發過程中,可以使用來作為數據契約類型,在服務和客戶端傳遞。兩者在定義之初都包含了[Serializable]聲明,因此兩者的對象都是可以序列化的。可以使用在客戶端和服務端傳遞數據。
【【2】集合(Collection):
集合也是我們編程開發中經常使用的類型。
【2.1】基本概念:
.NET Framework 提供了用於數據存儲和檢索的專用類。這些類提供對堆棧、隊列、列表和哈希表的支持。大多數集合類實現相同的接口,可繼承這些接口來創建適應更為專業的數據存儲需要的新集合類。針對 .NET Framework 的 2.0 版和更高版本的應用程序應當使用 System.Collections.Generic 命名空間中的泛型集合類,與對應的非泛型類相比,這些類提供了更高的類型安全性和效率。
集合類具有以下特點:
集合類定義為 System.Collections 或 System.Collections.Generic 命名空間的一部分。大多數集合類都派生自 ICollection、IComparer、IEnumerable、IList、IDictionary 和 IDictionaryEnumerator 接口以及它們的等效泛型接口。使用泛型集合類可以提供更高的類型安全性,在某些情況下還可以提供更好的性能,尤其是在存儲值類型時,這些優勢會體現得更明顯。有關更多信息,請參見泛型的優點。
如果將緊密相關的數據組合到一個集合中,則能夠更有效地處理這些緊密相關的數據。代替編寫不同的代碼來處理每一單獨的對象,您可以使用相同的調用代碼來處理一個集合的所有元素。
若要管理集合,可使用 Array 類和 System.Collections 類添加、移除和修改該集合中的個別元素或某一范圍內的元素。甚至可以將整個集合復制到另一個集合中。某些 Collections 類具有排序功能並且大多數都有索引。自動處理內存管理,集合的容量會根據需要擴展。當訪問集合成員時同步提供線程安全。某些 Collections 類可以生成包裝,這些包裝令集合是只讀的或固定大小的。任何 Collections 類都可以生成自己的枚舉數,該枚舉數簡化了對元素的循環訪問。
在 .NET Framework 2.0 版中,泛型集合類提供了新功能,並且使得創建強類型集合變得容易。請參見 System.Collections.Generic 和 System.Collections.ObjectModel 命名空間。
【2.2】集合數據契約:
集合有如此強大的特性,這也是我們使用的一個重要原因。
【3】示例代碼分析:
下面我們來介紹一下使用Dataset、 Datatable和集合類來傳遞數據的程序開發過程。依次介紹服務契約、宿主、客戶端的開發配置過程,另外服務端設計了一個數據庫,添加了部分演示數據,目的是方便Demo。
【3.1】服務契約:
服務契約定義了3個操作契約,分別是使用Dataset、Datatable、List來傳遞數據,WCF服務類實現了接口定義的操作契約,分別返回不同的數據結構類型。具體代碼如下:
//ServiceContract 屬性以及 Indigo 使用的所有其他屬性均在 System.ServiceModel 命名空間中定義, //因此本例開頭使用 using 語句來引用該命名空間。 //為了掩飾WCF服務的操作重載 namespace WCFService { //1.服務契約,操作契約重載 [ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")] interface IWCFService { //操作契約,數據表 [OperationContract] System.Data.DataTable GetDataByTable(); //操作契約,數據集 [OperationContract] System.Data.DataSet GetDataByDataSet(); //操作契約,數據集合 [OperationContract] List<User> GetDataByCollection(); } //2.服務類,集成接口。實現契約 public class WCFService : IWCFService { //實現接口定義的方法,DataTable傳遞數據 public System.Data.DataTable GetDataByTable() { //這裡可以定義數據持久化操作,訪問數據庫等 System.Data.DataSet dataSet = new System.Data.DataSet(); System.Data.DataTable dataTable = null; SqlConnection sqlConnection = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\Database\\DatabaseWCF.mdf;Integrated Security=True;User Instance=True"); try { System.Data.SqlClient.SqlDataAdapter sqlDataAdapter = new System.Data.SqlClient.SqlDataAdapter("SELECT id, name, english_name FROM TableWCF", sqlConnection); sqlDataAdapter.Fill(dataSet, "TableWCF"); if (dataSet != null && dataSet.Tables.Count > 0) { dataTable = dataSet.Tables[0]; } } catch (Exception e) { } finally { sqlConnection.Close(); } Console.WriteLine("Calling WCF Service,Transfer data using DataTable"); return dataTable; } //實現接口定義的方法,DataSet傳遞數據 public System.Data.DataSet GetDataByDataSet() { //這裡可以定義數據持久化操作,訪問數據庫等 System.Data.DataSet dataSet = new System.Data.DataSet(); SqlConnection sqlConnection = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\Database\\DatabaseWCF.mdf;Integrated Security=True;User Instance=True"); try { System.Data.SqlClient.SqlDataAdapter sqlDataAdapter = new System.Data.SqlClient.SqlDataAdapter("SELECT id, name, english_name FROM TableWCF", sqlConnection); sqlDataAdapter.Fill(dataSet, "TableWCF"); } catch (Exception e) { } finally { sqlConnection.Close(); } Console.WriteLine("Calling WCF Service,Transfer data using dataSet"); return dataSet; } //實現接口定義的方法,Collection傳遞數據 public List<User> GetDataByCollection() { //這裡可以定義數據持久化操作,訪問數據庫等 List<User> list = new List<User>(); for (int i = 0; i < 10; i++) { User user = new User(); user.age = 20+i; user.name = "Frank Xu Lei:" + i.ToString(); } Console.WriteLine("Calling WCF Service,Transfer data using Collection"); return list; } } //3數據契約 [DataContract] public class User { [DataMember] public string name; [DataMember] public int age; } }
【3.2】托管宿主:
托管宿主的配置過程與前幾節宿主類似,這裡不在詳述,配置文件裡契約和MEX原數據節點一定要配置,具體代碼如下:
<services>
<service behaviorConfiguration="WCFService.WCFServiceBehavior" name="WCFService.WCFService">
<endpoint
address="http://localhost:9003/WCFService"
binding="wsHttpBinding"
contract="WCFService.IWCFService">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:9003/"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WCFService.WCFServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
【3.4】客戶端:
宿主配置完畢,編譯運行宿主程序,我們在客戶端添加服務的引用,輸入正確的元數據交換地址,查詢服務,可以看到如下的操作查詢結果,如圖:
我們可以看到客戶端反序列化的本地類的信息。DataTable和DataSet使用的依然是.NET 類庫的類型。對應的代理服務操作如下:
public System.Data.DataTable GetDataByTable() {
return base.Channel.GetDataByTable();
}
public System.Data.DataSet GetDataByDataSet() {
return base.Channel.GetDataByDataSet();
}
但是我們定義的集合操作在反序列化為客戶端操作以後以及發生了變化,客戶單使用數組代替了我們的集合List.代碼如下:
public User[] GetDataByCollection() {
return base.Channel.GetDataByCollection();
}
WCF為集合類型提供了專屬的封送機制,客戶端發序列化的本地操作使用與之對應的數組。
【4】運行結果:
這裡客戶端使用了WinForm界面,借助DataGridView控件來顯示數據,方便DEMO。分別綁定事件方法,通過客戶端服務代理,調用WCF操作服務,獲取數據。代碼如下:
//Get data using DataTable By WCF proxy
private void buttonDataTable_Click(object sender, EventArgs e)
{
WCFServiceClient wcfServiceProxy =
new WCFServiceClient("WSHttpBinding_IWCFService");
//調用服務,獲取數據表dataTable,
System.Data.DataTable dataTable = wcfServiceProxy.GetDataByTable();
if (dataTable != null)
{
dataGridViewWCFDataTable.DataSource = dataTable;//綁定數據源到控件
}
}
//Get data using DataSet By WCF proxy
private void buttonDataSet_Click(object sender, EventArgs e)
{
WCFServiceClient wcfServiceProxy =
new WCFServiceClient("WSHttpBinding_IWCFService");
//調用服務,獲取數據集dataSet,
System.Data.DataSet dataSet = wcfServiceProxy.GetDataByDataSet();
if (dataSet != null && dataSet.Tables.Count > 0)
{
dataGridViewWCFDataSet.DataSource = dataSet.Tables[0];//綁定數據源到控件
}
}
點擊按鈕,分別測試調用服務操作返回數據是否成功,運行結果如圖:
【5】總結:
我們來看看使用這些類型進行數據傳遞的優點:
(1)在WCF中,還可以使用DataTable和DataSet的類型或者繼承之數據集或者數據表。對於WCF的客戶端與服務而言,可以通過開發工具Visual Studio工具使用DataSet、DataTable以及它們的類型安全的派生對象進行數據的傳輸。
(2)在服務契約中使用數據表或者數據集還存在一個缺陷,那就是它可能暴露內部數據庫表的數據結構。
(3)WCF為集合類型提供了專屬的封送機制,客戶端發序列化的本地操作使用與之對應的數組。.NET為集合類封裝了豐富特性和操作,這也是我們使用的主要原因。
它們同樣也有缺點,這個是我們必須注意的:
(1)如果全部是基於.net平台進行數據交換,比較方便,但是異構平台來說,這種方式過於繁瑣。而且,這些數據訪問類型都是特定的.NET類型。在序列化時,它們生成的數據契約樣式也過於復雜,很難與其它平台進行交互;
(2)WCF主要的目標是面向服務,平台無關。但客戶端必須知道ADO.NET關於此類的定義信息,,這些顯然違背了面向服務的編程原則。
(3)使用序列化機制而不是WCF面向服務的數據契約特性,將來對數據庫樣式的修改會影響到客戶端。雖然在應用程序內部可以傳遞數據表,但如果是跨越應用程序或公有的服務邊界發送數據表。使用數組返回數據,代替DataTable和DataSet。
本文配套源碼