WCF技术剖析之十四:泛型数据契约和集合数据契约(上篇)

[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]在.NET Framework 2.0中,泛型第一次被引入。我们可以定义泛型接口、泛型类型、泛型委托和泛型方法。序列化依赖于真实具体的类型,而泛型则刻意模糊了具体类型概念。而集合代表一组对象的组合,集合具有可迭代(Enumerable)的特性,可以通过某个迭代规则遍历集合中的每一个元素。由于范型类型和集合类型在序列化和反序列化上具有一些特殊的行为和规则,在这篇文章中,我将会对此进行详细介绍。上篇先来说所泛型数据契约。

一、泛型与数据契约

面向对象通过继承实现了代码的重用,而泛型则实现了“算法的重用”。我们定义一种算法,比如排序、搜索、交换、比较或者转换等等,为了实现尽可能的重用,我们并不限定该算法操作对象的具体类型,而通过一个泛型类型来表示。在真正创建范型对象或者调用该方法的时候,才指定其具体的类型。

就实现来说,泛型是CLR和编程语言(或者是基于编程语言的编译器)共同实现的一种特殊机制;就泛型的概念来说,这是面向对象的范畴。而我们现在介绍的数据契约,则属于面向服务的概念。两者具有一些冲突 ,比如面常服务没有继承、重载的概念一样,面向服务同样也无法理解泛型。

但是基于WCF的编程语言是C#、VB.NET这样的完全面向对象的编程语言,而WCF服务却是基于面向服务的。所以,从某种意义上讲,WCF的一个重大的作用就是弥合面向对象编程(OOP)和面向服务架构(SOA)之间的差异。我们现在就来看看WCF做了些什么使我们能够以泛型类型的形式来定义数据契约。

二、泛型数据契约的默认序列化规则

我们首先通过一个简单的例子看看DataContractSerializer是如何序列化一个范型对象的。为此我定义一个泛型类型Bill<BillHeader, BillDetail>,代表一个一般意义上的单据,BillHeader和BillDetail代表单据报头的明细的类型。两个属性Header和Details表示单据报头和明细列表。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Namespace="http://www.artech.com/")]
   4:     public class Bill<BillHeader, BillDetail>
   5:     {
   6:         [DataMember(Order = 1)]
   7:         public BillHeader Header
   8:         { get; set; }
   9:  
  10:         [DataMember(Order = 2)]
  11:         public BillDetail[] Details
  12:         { get; set; }
  13:     }
  14: }

然后我们定义用于描述订单单据的报头和明细的类型:OrderBillHeader和OrderBillDetail。OrderBillHeader描述定单的总体信息,OrderBillDetail实际上表示订单中每一个产品的ID、单价和数量。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Namespace="http://www.artech.com/")]
   4:     public class OrderBillHeader
   5:     {
   6:         [DataMember]
   7:         public Guid OrderID
   8:         { get; set; }
   9:  
  10:         [DataMember]
  11:         public DateTime Date
  12:         { get; set; }
  13:  
  14:         [DataMember]
  15:         public string Customer
  16:         { get; set; }
  17:     }
  18:  
  19:     [DataContract(Namespace="http://www.artech.com/")]
  20:     public class OrderBillDetail
  21:     {
  22:         [DataMember]
  23:         public Guid ProductID
  24:         { get; set; }
  25:  
  26:         [DataMember]
  27:         public int Quantity
  28:         { get; set; }
  29:  
  30:         [DataMember]
  31:         public double UnitPrice
  32:         { get; set; }
  33:     }
  34: }

通过下面一个方法创建泛型类型Bill<BillHeader, BillDetail>对象,泛型类型指定为上面定义的OrderBillHeader和OrderBillDetail。

   1: private static Bill<OrderBillHeader, OrderBillDetail> CreateOrderBill()
   2: {
   3:     OrderBillHeader header = new OrderBillHeader
   4:     {
   5:         OrderID     = Guid.NewGuid(),
   6:         Date         = DateTime.Today,
   7:         Customer     = "Foo"
   8:     };
   9:  
  10:     IList<OrderBillDetail> details = new List<OrderBillDetail>();
  11:     OrderBillDetail detail = new OrderBillDetail
  12:     {
  13:         ProductID     = Guid.NewGuid(),
  14:         Quantity     = 20,
  15:         UnitPrice     = 888
  16:     };
  17:     details.Add(detail);
  18:     detail = new OrderBillDetail
  19:     {
  20:         ProductID     = Guid.NewGuid(),
  21:         Quantity     = 10,
  22:         UnitPrice     = 9999
  23:     };
  24:     details.Add(detail);
  25:  
  26:  
  27:     Bill<OrderBillHeader, OrderBillDetail> orderBill = new Bill<OrderBillHeader, OrderBillDetail>()
  28:     {
  29:         Header     = header,
  30:         Details     = details.ToArray<OrderBillDetail>()
  31:     };
  32:     return orderBill;
  33: }

借助在《WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》定义的Serialize<T>辅助方法,我们对创建Bill<OrderBillHeader, OrderBillDetail>对象进行序列化。最终对象将被序列化成如下的XML。

   1: Bill<OrderBillHeader, OrderBillDetail> orderBill = CreateOrderBill();
   2: Serialize<Bill<OrderBillHeader, OrderBillDetail>>(orderBill, @"orderbill.xml");
   1: <BillOfOrderBillHeaderOrderBillDetail6Of3LqKh xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com/Artech.DataContractSerializerDemos">
   2:     <Header>
   3:         <Customer>NCS</Customer>
   4:         <Date>2008-12-04T00:00:00+08:00</Date>
   5:         <OrderID>15a62aae-c955-4bc0-acb6-e171fb9fe085</OrderID>
   6:     </Header>
   7:     <Details>
   8:         <OrderBillDetail>
   9:             <ProductID>f7679949-938a-40a0-a32a-5dde5c85e55f</ProductID>
  10:             <Quantity>20</Quantity>
  11:             <UnitPrice>888</UnitPrice>
  12:         </OrderBillDetail>
  13:         <OrderBillDetail>
  14:             <ProductID>bbd750ff-8b0c-48f5-ab1f-5ad7e51bd420</ProductID>
  15:             <Quantity>10</Quantity>
  16:             <UnitPrice>9999</UnitPrice>
  17:         </OrderBillDetail>
  18:     </Details>
  19: </BillOfOrderBillHeaderOrderBillDetail6Of3LqKh>

XML整体的结构正是我们希望的,关键是根节点名称,也就是数据契约的名称,“BillOfOrderBillHeaderOrderBillDetail6Of3LqKh”,会让有些人难以理解。我们仔细分析一下数据契约的名称,会发现它的组成结构是这样的:{类型名称(Bill)}+ Of + {第一个范型参数的类型(OrderBillHeader)} + {第二个范型参数的类型(OrderBillDetail)}+ {哈希值(6Of3LqKh)}。

这里说泛型参数的类型,实际上是不对的,应该说OrderBillHeader和OrderBillDetail的泛型类型对应的数据契约的名称。在下面的代码中。通过 DataContractAttribute特性修改了数据契约的名称(OrderHeader和OrderDetail),最终的数据契约的名称将会变成:BillOfOrderHeaderOrderDetail6Of3LqKh。可以看出描述泛型数据契约的部分内容相应地改变了。可能仔细的读者已经发现了,哈希值部分却没有发生变化,依然是“6Of3LqKh”,这是因为这是泛型类型(含命名空间)的哈希值,而不是数据契约名称的哈希值。所以我们可以将默认的基于泛型类型的命名规则表示成:[类型名称][范型数据契约名称1][ 范型数据契约名称2][…][含命名空间的范型类型哈希值]。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Name="OrderHeader")]
   4:     public class OrderBillHeader
   5:     {
   6:         //省略成员
   7:     }
   8:  
   9:     [DataContract(Name = "OrderDetail")]
  10:     public class OrderBillDetail
  11:     {
  12:         //省略成员
  13:     }
  14: }

WCF之所以要采用这样的数据契约命名方式,是为了解决命名冲突,保证数据契约名称的唯一性。我们说了,面向服务下的数据契约完全没有泛型的概念,对它来说所有的类型都是“实实在在”的具体类型。对于泛型类型Bill<BillHeader,BillDetail>,不同的BillHeader和BillDetail组合代表不同的数据契约,所以最终的数据契约的名称需要由自身类型和泛型契约名称派生出来。由于在定义数据契约的时候,不同的CLR类型可以指定相同的数据契约名称,所以加上一个基于所有范型类型(含命名空间)的哈希值可以确保数据契约的唯一性。

WCF在进行元数据发布的时候,会自动按照这样的命名机制创建数据契约,并以XSD的形式发布出来。所以当客户端导入元数据生成客户端代码的时候,生成的等效数据契约的类型名称就是这个经过拼接的名称。下面是Bill<OrderBillHeader, OrderBillDetail>导入的形式。

   1: public partial class BillOfOrderBillHeaderOrderBillDetail6Of3LqKh : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged
   2: {
   3:     //省略成员    
   4: }

三、如何显式指定契约名称

如果你能够确保命名不会发生冲突,你可以通过DataContractAttribute特性的Name属性对数据契约的名称进行显式设置。比如在下面的代码中,将契约名称限定为“OrderBill”。不过这样设置就意味着你假定泛型类型只能表示基于订单的单据了,这相当于失去了泛型的意义。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Name="OrderBill")]
   4:     public class Bill<BillHeader, BillDetail>
   5:     {
   6:         //省略成员
   7:     }
   8: }

所以我们可以采用一种动态的设置方式,为数据契约的名称指定一个模板,使用表示泛型数据契约名称和泛型类型哈希值的占位符。其中{0}、{1}表示的是范型数据契约的名称,数字表示相应的范型参数出现的次序,而哈希值则通过{#}表示。所以下面两种范型数据契约是完全等效的。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract]
   4:     public class Bill<BillHeader, BillDetail>
   5:     {
   6:         //省略成员
   7:     }
   8: }
   9: namespace Artech.DataContractSerializerDemos
  10: {
  11:     DataContract(Name="BillOf{0}{1}{#}")]
  12:     public class Bill<BillHeader, BillDetail>
  13:     {
  14:         //省略成员
  15:     }
  16: }

注:部分内容节选自《WCF技术剖析(卷1)》第五章:序列化与数据契约(Serialization and Data Contract)

WCF技术剖析系列:

WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构
WCF技术剖析之二:再谈IIS与ASP.NET管道
WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘
WCF技术剖析之五:利用ASP.NET兼容模式创建支持会话(Session)的WCF服务
WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效
WCF技术剖析之七:如何实现WCF与EnterLib PIAB、Unity之间的集成
WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制
WCF技术剖析之九:服务代理不能得到及时关闭会有什么后果?
WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理

WCF技术剖析之十一:异步操作在WCF中的应用(上篇)
WCF技术剖析之十一:异步操作在WCF中的应用(下篇)
WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)
WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
WCF技术剖析之十四:泛型数据契约和集合数据契约(上篇)
WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇)
WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用
WCF技术剖析之十六:数据契约的等效性和版本控制


作者:Artech
出处:http://artech.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2009-07-13 17:38  Artech  阅读(9948)  评论(10编辑  收藏  举报