在系统开发过程中通过对Data Type添加额外的字段进而对其进行扩展是一个种很常见的场景本部分就作中介绍Data Contract的这种变化Service或者Client的Data Contract在本地添加一个新的Data Member会造成怎样的影响WCF可以采用怎样的机制来解决这种单方面Data Contract版本的改变 我们同样通过Dome来说话在这个Demo中我使用上面介绍的Order Processing的场景下面是整个Solution的结构(需要说明的是本片文章提供的Code片断和Source Code都是基于VS 的) Service端ArtechDataContractVersioningService Data Contract using System; using SystemCollectionsGeneric; using SystemLinq; using SystemText; using SystemRuntimeSerialization; namespace ArtechDataContractVersioningService { [DataContract(Namespace=)] public class Order { [DataMember(Order = )] public Guid OrderID {get;set;} [DataMember(Order = )] public DateTime OrderDate { get; set; } [DataMember(Order = )] public Guid SupplierID { get; set; } } } Service Contract 和Service Implementation: Process方法简单地将Order对象返回到客户端当Client接受到Service返回的Order对象后可以检测和由它传递给Service的Order对象有什么不同 using System; using SystemCollectionsGeneric; using SystemLinq; using SystemText; using SystemServiceModel; namespace ArtechDataContractVersioningService { [ServiceContract] public interface IOrderManager { [OperationContract] Order Process(Order order); } } namespace ArtechDataContractVersioningService { public class OrderManagerService:IOrderManager { IOrderManager Members#region IOrderManager Members public Order Process(Order order) { return order; } #endregion } } Client端 Data Contract [DataContract(Name=OrderNamespace=)] public class CustomOrder { [DataMember(Order = Name=OrderID)] public Guid OrderNo { get; set; } [DataMember(Order = Name = SupplierID)] public Guid SupplierNo { get; set; } [DataMember(Order = )] public DateTime OrderDate { get; set; } } } Program先创建一个Order对象向Console打印出Order的信息随后以此作为参数调用Service最后将返回的Order对象的信息打印出来看看两者之间的有何区别 namespace ArtechDataContractVersioningClient { class Program { static void Main(string[] args) { ChannelFactory channelFactory = new ChannelFactory(orderManagerhttp);IOrderManager orderManager = channelFactoryCreateChannel(); try { CustomOrder order = new CustomOrder { OrderNo = GuidNewGuid() SupplierNo = GuidNewGuid() OrderDate = DateTimeToday ShippingAddress=Room E Airport Rd # Suzhou Jiangsu Province }; ConsoleWriteLine(The original order: \n{} orderToString()); order = orderManagerProcess(order); ConsoleWriteLine(\n\nThe order processed by service: \n{} orderToString()); } finally { (orderManager as IDisposable)Dispose(); } ConsoleRead(); } } } 通过上面的分析我们可以知道尽管就CLR Type的定义来讲Service端的Order和Client端的CustomOrder具有很大的差异但是通过WCF Datacontract Attribute的适配他们是相互匹配的 现在我们在Client端为Custom添加一个新的成员ShippingAddress通过重写ToString方法 namespace ArtechDataContractVersioningClient { [DataContract(Name=OrderNamespace=)] public class CustomOrder { [DataMember(Order = Name=OrderID)] public Guid OrderNo { get; set; } [DataMember(Order = Name = SupplierID)] public Guid SupplierNo { get; set; } [DataMember(Order = )] public DateTime OrderDate { get; set; } [DataMember(Order = )] public string ShippingAddress { get; set; } public override string ToString() { return stringFormat(Order No\t: {}\nSupplier No\t: {}\nOrder Date:\t: {}\nShipping Address: {} thisOrderNo thisSupplierNo thisOrderDate thisShippingAddress); } } } 我们来看看Client端程序运行的输出结果 )thiswidth=;>图通过上面的结果我们发现Shipping Address的信息在经过Service处理后丢失了原因很简单Service端的Data Contract根本就没有ShippingAddress成员所有在反序列化生成Order对象的时候将会忽略ShippingAddress的信息 其实这是一个不太合理的状况对于Client来说我指定了对象的某个对象的某个成员的值结果Service处理返回后却无缘无故(对于Client来说是无缘无故)丢失了其实这种情况还出来在另一种场景之中Client先调用Service AService B再将相同的对象作为参数调用Service C现在假设Client和Service B的Data Contract是CustomOrderService A的Data Contract是少一个ShippingAddress的Order那么经过Service A反序列化的对象将会是缺少Shipping Address的Order对象然后这个Order对象又由Service A传导Service B虽然Service B能过识别Shipping Address成员但是现在却没有改成员的值了这显然是有问题的我们把这样的问题称为Round trip问题我们必须解决这样一个问题 其实在WCF中解决这样一个问题的方案简单而直接那就是在Data Contract中定义一个额外的成员来存储没有在成员列表中定义的信息我们可以让Data Contract的Data Type实现SystemRuntimeSerializationIExtensibleDataObject Interface来解决Round trip的版本问题Interface的定义如下他仅仅有一个Property成员ExtensionData cellSpacing= borderColorLight=black borderColorDark=#ffffff cellPadding= width= align=center>ee> namespace SystemRuntimeSerialization { // Summary: // Provides a data structure to store extra data encountered by the SystemRuntime SerializationXmlObjectSerializer // during deserialization of a type marked with the SystemRuntimeSerialization DataContractAttribute // attribute public interface IExtensibleDataObject { // Summary: // Gets or sets the structure that contains extra data // // Returns: // An SystemRuntimeSerializationExtensionDataObject that contains data that // is not recognized as belonging to the data contract ExtensionDataObject ExtensionData { get; set; } } } 现在我们来重新定义Service的Order Data Contract namespace ArtechDataContractVersioningService { [DataContract(Namespace=)] public class Order:IExtensibleDataObject { [DataMember(Order = )] public Guid OrderID {get;set;} [DataMember(Order = )] public DateTime OrderDate { get; set; } [DataMember(Order = )] public Guid SupplierID { get; set; } public ExtensionDataObject ExtensionData { get; set; } } } 我们再来运行一下client端程序我们发现现在没有数据丢失了 cellSpacing= borderColorLight=# borderColorDark=#ffffff bgColor=#ddddd align=center>image onmousewheel=javascript:return big(this) src=http://imgeducitycn/img_///jpg onload=javascript:if(thiswidth>)thiswidth=;>图这就是实现了IExtensibleDataObject Interface的效果就其本质很简单对于实现了该Interface的Data contract将通过一个ExtensionDataObject 类型的对象来保存和获取那些没有在Data Contract定义的成员为了一窥Order的ExtensionData属性中保存的内容我们在Service进行Debug在QuickWatch中看看它是不是真的保存了不能识别的ShippingAddress cellSpacing= borderColorLight=# borderColorDark=#ffffff bgColor=#ddddd align=center>image onmousewheel=javascript:return big(this) src=http://imgeducitycn/img_///jpg onload=javascript:if(thiswidth>)thiswidth=;>图 |