数据集(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工具查看,部分代码如下:
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;
}
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;
}
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;
}
}
// 因此本例开头使用 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 >
< 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();
}
return base .Channel.GetDataByTable();
}
public System.Data.DataSet GetDataByDataSet() {
return base .Channel.GetDataByDataSet();
}
但是我们定义的集合操作在反序列化为客户端操作以后以及发生了变化,客户单使用数组代替了我们的集合List.代码如下:
public
User[] GetDataByCollection() {
return base .Channel.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 ]; // 绑定数据源到控件
}
}
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 ]; // 绑定数据源到控件
}
}
点击按钮,分别测试调用服务操作返回数据是否成功,运行结果如图:
我们来看看使用这些类型进行数据传递的优点:
(1)在WCF中,还可以使用DataTable和DataSet的类型或者继承之数据集或者数据表。 对于WCF的客户端与服务而言,可以通过开发工具Visual Studio工具使用DataSet、DataTable以及它们的类型安全的派生对象进行数据的传输。
(2)在服务契约中使用数据表或者数据集还存在一个缺陷,那就是它可能暴露内部数据库表的数据结构。
(3)WCF为集合类型提供了专属的封送机制,客户端发序列化的本地操作使用与之对应的数组。.NET为集合类封装了丰富特性和操作,这也是我们使用的主要原因。
它们同样也有缺点,这个是我们必须注意的:
(1)如果全部是基于.net平台进行数据交换,比较方便,但是异构平台来说,这种方式过于繁琐。而且,这些数据访问类型都是特定的.NET类型。在序列化时,它们生成的数据契约样式也过于复杂,很难与其它平台进行交互;
(2)WCF主要的目标是面向服务,平台无关。但客户端必须知道ADO.NET关于此类的定义信息,,这些显然违背了面向服务的编程原则。
(3)使用序列化机制而不是WCF面向服务的数据契约特性,将来对数据库样式的修改会影响到客户端。虽然在应用程序内部可以传递数据表,但如果是跨越应用程序或公有的服务边界发送数据表。使用数组返回数据,代替DataTable和DataSet。
以上就是本节的全部内容,下面上传本节的示例代码供大家参考,
/Files/frank_xl/WCFServiceDataSetFrankXuLei.rar。(大家有好的博客写作的经验,请赐教~我出现好几次写的文章保存失败,丢失的情况了。live writer怎么样?)
参考资料:
1.《DataSet》,http://baike.baidu.com/view/624618.html;
2.《DataSet类》,http://msdn.microsoft.com/zh-cn/library/system.data.dataset(VS.80).aspx
3.《集合和数据结构》,http://msdn.microsoft.com/zh-cn/library/7y3x785f(VS.80).aspx
4.《Programming in WCF Service》
4.《Programming in WCF Service》
本文转自 frankxulei 51CTO博客,原文链接:http://blog.51cto.com/frankxulei/320412,如需转载请自行联系原作者