WCF使用資料集(DataSet)、資料表(DataTable)、集合(Collection)傳遞資料 (轉)

weixin_34279184發表於2011-08-09
資料集(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 並依次更新原始資料的步驟包括:

  1. 通過 DataAdapter 使用資料來源中的資料生成和填充 DataSet 中的每個 DataTable。
  2. 通過新增、更新或刪除 DataRow 物件更改單個 DataTable 物件中的資料。
  3. 呼叫 GetChanges 方法以建立只反映對資料進行的更改的第二個 DataSet。
  4. 呼叫 DataAdapter 的 Update 方法,並將第二個 DataSet 作為引數傳遞。
  5. 呼叫 Merge 方法將第二個 DataSet 中的更改合併到第一個中。
  6. 針對 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。

http://blog.csdn.net/book_frank_xl/article/details/4735914

相關文章