Artech

Develop every application as an art using the most suitable technologies!

常用链接

统计

积分与排名

网上邻居

我的博文系列

最新评论

[原创]谈谈WCF中的Data Contract(4):WCF Data Contract Versioning

软件工程是一门独特的工程艺术,需要解决的是不断改变的需求变化。而对于WCF,对于SOA,由于涉及的是对多个系统之间的交互问题,如何有效地解决不断改变的需求所带来的问题就显得更为重要:Service端版本的变化能否保持现有Consumer的正常调用,Consumer端的改变不至于影响对Service 的正常调用。对于Data Contract来说就是要解决这样的问题:Service端或者ClientData Type的改变不会影响Service的正常调用。

在系统开发过程中,通过对Data Type添加额外的字段进而对其进行扩展,是一个种很常见的场景。本部分就作中介绍Data Contract的这种变化,Service或者ClientData Contract在本地添加一个新的Data Member会造成怎样的影响,WCF可以采用怎样的机制来解决这种单方面Data Contract版本的改变。

我们同样通过Dome来说话。在这个Demo中,我使用上面介绍的Order Processing的场景,下面是整个Solution的结构(需要说明的是,本片文章提供的Code片断和Source Code都是基于VS 2008的)。

1.   Service: Artech.DataContractVersioning.Service

Data Contract

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace Artech.DataContractVersioning.Service
{
    [DataContract(Namespace
="http://artech.datacontractversioning")]
    
public class Order
    
{
        [DataMember(Order 
= 0)]
        
public Guid OrderID
        
{get;set;}

        [DataMember(Order 
= 1)]
        
public DateTime OrderDate
        
getset; }

        [DataMember(Order 
= 2)]
        
public Guid SupplierID
        
getset; }
    }

}

Service Contract Service Implementation: Process方法简单地将Order对象返回到客户端,当Client接受到Service返回的Order对象后,可以检测和由它传递给ServiceOrder对象有什么不同。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Artech.DataContractVersioning.Service
{
    [ServiceContract]
    
public interface IOrderManager
    
{
        [OperationContract]
        Order Process(Order order);
    }

}


namespace Artech.DataContractVersioning.Service
{
    
public class OrderManagerService:IOrderManager
    
{
        
IOrderManager Members
    }

}

2.   Client端:

Data Contract

    [DataContract(Name="Order",Namespace="http://artech.datacontractversioning")]
    
public class CustomOrder
    
{
        [DataMember(Order 
= 0, Name="OrderID")]
        
public Guid OrderNo
        
getset; }

        [DataMember(Order 
= 2, Name = "SupplierID")]
        
public Guid SupplierNo
        
getset; }

        [DataMember(Order 
= 1)]
        
public DateTime OrderDate
        
getset; }        
    }

}

Program:先创建一个Order对象,向Console打印出Order的信息,随后以此作为参数调用Service,最后将返回的Order对象的信息打印出来,看看两者之间的有何区别。

namespace Artech.DataContractVersioning.Client
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            ChannelFactory
<IOrderManager> channelFactory = new ChannelFactory<IOrderManager>("orderManager.http");
            IOrderManager orderManager 
= channelFactory.CreateChannel();

            
try
            
{
                CustomOrder order 
= new CustomOrder { OrderNo = Guid.NewGuid(), SupplierNo = Guid.NewGuid(), OrderDate = DateTime.Today, ShippingAddress="Room E101, Airport Rd #328, Suzhou Jiangsu Province" };
                Console.WriteLine(
"The original order: \n{0}", order.ToString());
                order 
= orderManager.Process(order);
                Console.WriteLine(
"\n\nThe order processed by service: \n{0}", order.ToString());
            }

            
finally
            
{
                (orderManager 
as IDisposable).Dispose();
            }


            Console.Read();
        }

    }

}

通过上面的分析,我们可以知道,尽管就CLR Type的定义来讲,Service端的OrderClient端的CustomOrder具有很大的差异,但是通过WCF Datacontract Attribute的适配,他们是相互匹配的。

现在我们在Client端为Custom添加一个新的成员,ShippingAddress,通过重写ToString方法:

 

namespace Artech.DataContractVersioning.Client
{
    [DataContract(Name
="Order",Namespace="http://artech.datacontractversioning")]
    
public class CustomOrder
    
{
        [DataMember(Order 
= 0, Name="OrderID")]
        
public Guid OrderNo
        
getset; }

        [DataMember(Order 
= 2, Name = "SupplierID")]
        
public Guid SupplierNo
        
getset; }

        [DataMember(Order 
= 1)]
        
public DateTime OrderDate
        
getset; }

        [DataMember(Order 
= 3)]
        
public string ShippingAddress
        
getset; }
        
        
public override string ToString()
        
{
            
return string.Format("Order No.\t: {0}\nSupplier No.\t: {1}\nOrder Date:\t: {2}\nShipping Address: {3}"this.OrderNo, this.SupplierNo, this.OrderDate, this.ShippingAddress);
        }

    }

}

我们来看看Client端程序运行的输出结果:

通过上面的结果,我们发现Shipping Address的信息在经过Service处理后丢失了。原因很简单,Service端的Data Contract根本就没有ShippingAddress成员,所有在反序列化生成Order对象的时候将会忽略ShippingAddress的信息。

其实这是一个不太合理的状况,对于Client来说,我指定了对象的某个对象的某个成员的值,结果Service处理返回后,却无缘无故(对于Client来说是无缘无故)丢失了。其实这种情况还出来在另一种场景之中:Client先调用Service AService B再将相同的对象作为参数调用Service C,现在假设ClientService BData ContractCustomOrderService AData Contract是少一个ShippingAddressOrder,那么经过Service A反序列化的对象将会是缺少Shipping AddressOrder对象,然后这个Order对象又由Service A传导Service B,虽然Service B能过识别Shipping Address成员,但是现在却没有改成员的值了,这显然是有问题的。我们把这样的问题称为Round trip问题,我们必须解决这样一个问题。

其实在WCF中解决这样一个问题的方案简单而直接,那就是在Data Contract中定义一个额外的成员来存储没有在成员列表中定义的信息。我们可以让Data ContractData Type实现System.Runtime.Serialization.IExtensibleDataObject Interface来解决Round trip的版本问题。Interface的定义如下,他仅仅有一个Property成员:ExtensionData

namespace System.Runtime.Serialization
{
    
// Summary:
    
//     Provides a data structure to store extra data encountered by the System.Runtime.Serialization.XmlObjectSerializer
    
//     during deserialization of a type marked with the System.Runtime.Serialization.DataContractAttribute
    
//     attribute.
    public interface IExtensibleDataObject
    
{
        
// Summary:
        
//     Gets or sets the structure that contains extra data.
        
//
        
// Returns:
        
//     An System.Runtime.Serialization.ExtensionDataObject that contains data that
        
//     is not recognized as belonging to the data contract.
        ExtensionDataObject ExtensionData getset; }
    }

}

现在我们来重新定义ServiceOrder Data Contract

namespace Artech.DataContractVersioning.Service
{
    [DataContract(Namespace
="http://artech.datacontractversioning")]
    
public class Order:IExtensibleDataObject
    
{
        [DataMember(Order 
= 0)]
        
public Guid OrderID
        
{get;set;}

        [DataMember(Order 
= 1)]
        
public DateTime OrderDate
        
getset; }

        [DataMember(Order 
= 2)]
        
public Guid SupplierID
        
getset; }


        
public ExtensionDataObject ExtensionData
        
{
            
get;
            
set;
        }

    }

}

我们再来运行一下client端程序,我们发现现在没有数据丢失了:

这就是实现了IExtensibleDataObject Interface的效果。就其本质,很简单,对于实现了该InterfaceData contract,将通过一个ExtensionDataObject 类型的对象来保存和获取那些没有在Data Contract定义的成员。为了一窥OrderExtensionData属性中保存的内容,我们在Service进行Debug,在QuickWatch中看看它是不是真的保存了不能识别的ShippingAddress

[原创]谈谈WCF中的Data Contract(1):Data Contract Overview
[原创]谈谈WCF中的Data Contract(2):WCF Data Contract对Generic的支持
[原创]谈谈WCF中的Data Contract(3):WCF Data Contract对Collection & Dictionary的支持
[原创]谈谈WCF中的Data Contract(4):WCF Data Contract Versioning

posted on 2007-11-27 21:06 Artech 阅读(3510) 评论(29)  编辑 收藏 网摘 所属分类: J. WCF

评论

#1楼 2007-11-27 22:04 Jeffrey Zhao      

汗一记,咋个一下子发三篇阿,呵呵   回复  引用  查看    

#2楼 2007-11-27 23:06 Anytao      

又发力了,好久未见踪影了,找时间再看啦:-)   回复  引用  查看    

#3楼 2007-11-28 00:21 volnet(可以叫我大V)      

感谢提供~
  回复  引用  查看    

#4楼[楼主] 2007-11-28 08:58 Artech      

@Jeffrey Zhao
很久没有写了,弥补一下^_^
  回复  引用  查看    

#5楼[楼主] 2007-11-28 09:00 Artech      

@Anytao
刚“回”到一个“新”的环境,很多东西要去熟悉,要去学习,自己的时间越来越少了。
  回复  引用  查看    

#6楼[楼主] 2007-11-28 09:00 Artech      

@volnet(可以叫我大V)
^_^
  回复  引用  查看    

#7楼 2007-11-28 11:13 婚纱[未注册用户]

辛苦了,又到了一招.   回复  引用    

#8楼 2007-11-28 11:15 婚纱[未注册用户]

学到了.   回复  引用    

#9楼 2007-11-29 10:14 Enzo      

@Jeffrey Zhao
lz是厚积续发啊 呵呵 up
  回复  引用  查看    

#10楼 2007-12-01 10:29 Ray Zhang[未注册用户]

不错!   回复  引用    

#11楼[楼主] 2007-12-03 09:17 Artech      

@Ray Zhang
Ray, 谢谢捧场^_^
  回复  引用  查看    

#12楼 2008-02-01 16:59 Colin1981[未注册用户]

不错,在学习,基础有点差,看起来有点费劲!
楼主能不原代码共享一下.
  回复  引用    

#13楼[楼主] 2008-02-01 20:10 Artech      

@Colin1981
现在找不到了,写文章的时候临时写的
  回复  引用  查看    

#14楼 2008-03-04 21:05 Tony.Zhu[未注册用户]

CustomOrder order = new CustomOrder { OrderNo = Guid.NewGuid(), SupplierNo = Guid.NewGuid(), OrderDate = DateTime.Today, ShippingAddress="Room E101, Airport Rd #328, Suzhou Jiangsu Province" };
Console.WriteLine("The original order: \n{0}", order.ToString());
order = orderManager.Process(order);
Console.WriteLine("\n\nThe order processed by service: \n{0}", order.ToString());

LZ, orderManager.Process的参数是Order的实例, 而你的代码传入的是CustomOrder的实例, 通过DataContract和DataMember就可以编译通过嘛? 我试了一下不行哦. 是不是还有些细节没有贴出来呀? 请指教.
  回复  引用    

#15楼 2008-03-04 22:08 Tony.Zhu[未注册用户]

@Artech:
我发现问题了. 我是按你的文章顺利来一步步学习的, "通过上面的结果,我们发现Shipping Address的信息在经过Service处理后丢失了。", 你的这个结果始终出不来. 问题编译不过, 现在看来是因为此时Order还没实现IExtensionDataObject接口, 所以编译不过...
  回复  引用    

#16楼 2008-04-16 13:30 苏州婚纱[未注册用户]

拜读了!回头打印一份继续学习   回复  引用    

#17楼[楼主] 2008-04-16 14:51 Artech      

@苏州婚纱
谢谢捧场
  回复  引用  查看    

#18楼 2008-06-12 00:59 BAsil      

有个问题请教,
如果有1个wcf服务端,10个wcf客户端,我想在同一时刻10个客户端同时调用wcf服务端,或者依次执行,怎么处理阿?
  回复  引用  查看    

#19楼[楼主] 2008-06-12 09:58 Artech      

@BAsil
You can refer to : http://msdn.microsoft.com/en-us/library/ms731193.aspx" target="_new">Sessions, Instancing, and Concurrency
  回复  引用  查看    

#20楼 2008-06-23 23:18 kingly[未注册用户]

牛啊   回复  引用    

#21楼 2009-01-04 18:58 sapphireren      

楼主,在static void Main(string[] args)中:
CustomOrder order = new CustomOrder { OrderNo = Guid.NewGuid(), SupplierNo = Guid.NewGuid(), ... };
order = orderManager.Process(order);
order是CustomOrder类型,而orderManager.Process方法接受的参数是Order类型。
  回复  引用  查看    

#22楼 2009-01-04 18:59 sapphireren      

楼主
在static void Main(string[] args)中:
CustomOrder order = new CustomOrder { OrderNo = Guid.NewGuid(), SupplierNo = Guid.NewGuid(), ... };
order = orderManager.Process(order);
order是CustomOrder类型,而orderManager.Process方法接受的参数是Order类型 ????????
  回复  引用  查看    

#23楼[楼主] 2009-01-05 08:19 Artech      

--引用--------------------------------------------------
sapphireren: 楼主
在static void Main(string[] args)中:
CustomOrder order = new CustomOrder { OrderNo = Guid.NewGuid(), SupplierNo = Guid.NewGuid(), ... };
order = orderManager.Process(order);
order是CustomOrder类型,而orderManager.Process方法接受的参数是Order类型 ????????
--------------------------------------------------------
????
  回复  引用  查看    

#24楼 2009-01-05 10:59 sapphireren      

先谢谢楼主这么快的回复!
order = orderManager.Process(order);
order是CustomOrder类型,而orderManager.Process方法接受的参数是Order类型,编译不能通过。
可能这个问题这个问题有点低级,呵呵
请问,CustomOrder类 和 Order类是不是都是手写的?还是CustomOrder类是根据Order类生成的?
我这里依照楼主的例子做下来,出现编译时错误:
Argument '1': cannot convert from 'Artech.DataContractVersioning.CustomOrder' to Artech.DataContractVersioning.Order'

再次感谢楼主!
  回复  引用  查看    

#25楼[楼主] 2009-01-05 12:42 Artech      

--引用--------------------------------------------------
sapphireren: 先谢谢楼主这么快的回复!
order = orderManager.Process(order);
order是CustomOrder类型,而orderManager.Process方法接受的参数是Order类型,编译不能通过。
可能这个问题这个问题有点低级,呵呵
请问,CustomOrder类 和 Order类是不是都是手写的?还是CustomOrder类是根据Order类生成的?
我这里依照楼主的例子做下来,出现编译时错误:
Argument '1': cannot convert from 'Artech.DataContractVersioning.CustomOrder' to Artech.DataContractVersioning.Order'

再次感谢楼主!
--------------------------------------------------------
都是自定义的!
  回复  引用  查看    

#26楼 2009-07-01 15:48 碧海山城      

我也遇到了和上面一样的问题,说CustomOrder无法转换为Order。。。没什么头绪,博主可否帮忙解答一下!   回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 974671




相关文章:

相关链接: