数据合同序列化程序转发引用的属性的兼容性

时间:2015-08-05 19:00:44

标签: c# xml-serialization datacontractserializer datacontract forward-compatibility

我正在尝试支持数据合同序列化程序的前向兼容性。 我遇到问题的案例:

如果您有一个对象被保存为对已知类型内的更高版本中添加的属性的引用,则它将成为异常。请注意,这两种类型都是已知的。唯一新的是其中一个对象内的属性。

我在samplessamples添加了对问题的简单模拟:

它有两个不同的项目: V1是已部署的旧版本。 V2是V1的较新版本。 V2正在保存其数据,V1需要能够加载V2保存的数据以支持向前兼容性。

有三种自定义类型: People:有两个对象引用,Person和AnotherPerson正在保存在其中。

在V1和V2中:

[DataContract(Name = "People", Namespace = "Tests.FCTests")]
[KnownType(typeof(Person))]
[KnownType(typeof(AnotherPerson))]
public class People : IExtensibleDataObject
{
    [DataMember]
    public object Person { get; set; }

    [DataMember]
    public object AnotherPerson { get; set; }

    public ExtensionDataObject ExtensionData { get; set; }
}

人:有名字。

在V1和V2中:

[DataContract(Name = "Person", Namespace = "Tests.FCTests")]
public class Person : IExtensibleDataObject
{
    [DataMember]
    public string Name { get; set; }

    public ExtensionDataObject ExtensionData { get; set; }

}

AnotherPerson:有一个名字,在V2中加入了对Person(FriendPerson)的引用。

在V1中:

[DataContract(Name = "AnotherPerson", Namespace = "Tests.FCTests")]
public class AnotherPerson : IExtensibleDataObject
{
    [DataMember]
    public string Name { get; set; }

    public ExtensionDataObject ExtensionData { get; set; }
}

在V2:

[DataContract(Name = "AnotherPerson", Namespace = "Tests.FCTests")]
public class AnotherPerson : IExtensibleDataObject
{
    [DataMember]
    public string Name { get; set; }

    /* This is added in this version */
    [DataMember]
    public Person FriendPerson { get; set; }

    public ExtensionDataObject ExtensionData { get; set; }
}

版本2正在保存数据:

    static void Main(string[] args)
    {
        DataContractSerializer serializer = new DataContractSerializer(typeof(People), null, int.MaxValue, false, true, null, null);

        var people = new People();
        var person = new Person() { Name = "Person" };
        var anotherPerson = new AnotherPerson() { Name = "AnotherPerson", FriendPerson = person };

        people.Person = person;
        people.AnotherPerson = anotherPerson;

        using (var writer = new XmlTextWriter("../../../../SavedFiles/Version2Saved.xml", null) { Formatting = Formatting.Indented })
        {
            serializer.WriteObject(writer, people);
            writer.Flush();
        }

        Console.WriteLine("Save Successfull.");
        Console.ReadKey();
    }

版本1正在加载相同的数据:

    static void Main(string[] args)
    {
        DataContractSerializer serializer = new DataContractSerializer(typeof(People), null, int.MaxValue, false, true, null, null);

        People loadedPeople;

        using (var reader = new XmlTextReader("../../../../SavedFiles/Version2Saved.xml"))
        {
            loadedPeople = (People)serializer.ReadObject(reader);
        }

        Console.WriteLine("Load Successful.");

        Console.ReadKey();
    }

保存的数据:

<People xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="Tests.FCTests">
  <AnotherPerson z:Id="2" i:type="AnotherPerson">
    <FriendPerson z:Id="3">
      <Name z:Id="4">Person</Name>
    </FriendPerson>
    <Name z:Id="5">AnotherPerson</Name>
  </AnotherPerson>
  <Person z:Ref="3" i:nil="true" />
</People>

当V1尝试加载数据时,抛出此异常:

{System.Runtime.Serialization.SerializationException: Element Person from namespace Tests.FCTests cannot have child contents to be deserialized as an object. Please use XmlNode[] to deserialize this pattern of XML. ---> System.Xml.XmlException: 'Element' is an invalid XmlNodeType.
   at System.Xml.XmlReader.ReadEndElement()
   at System.Runtime.Serialization.XmlReaderDelegator.ReadEndElement()
   at System.Runtime.Serialization.ObjectDataContract.ReadXmlValue(XmlReaderDelegator reader, XmlObjectSerializerReadContext context)
   --- End of inner exception stack trace ---
   at System.Runtime.Serialization.ObjectDataContract.ReadXmlValue(XmlReaderDelegator reader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadDataContractValue(DataContract dataContract, XmlReaderDelegator reader)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract& dataContract)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator xmlReader, Type declaredType, String name, String ns)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.DeserializeFromExtensionData(IDataNode dataNode, Type type, String name, String ns)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.GetExistingObject(String id, Type type, String name, String ns)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.TryHandleNullOrRef(XmlReaderDelegator reader, Type declaredType, String name, String ns, Object& retObj)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract& dataContract)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator xmlReader, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, String name, String ns)
   at ReadPeopleFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
   at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadDataContractValue(DataContract dataContract, XmlReaderDelegator reader)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract& dataContract)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator xmlReader, Type declaredType, DataContract dataContract, String name, String ns)
   at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.DataContractSerializer.ReadObject(XmlReader reader)
   at Version1.Program.Main(String[] args) in C:\Users\Administrator\Desktop\Unknown types Test\Version1\Version1\Program.cs:line 17}

内部例外:

{System.Xml.XmlException: 'Element' is an invalid XmlNodeType.
   at System.Xml.XmlReader.ReadEndElement()
   at System.Runtime.Serialization.XmlReaderDelegator.ReadEndElement()
   at System.Runtime.Serialization.ObjectDataContract.ReadXmlValue(XmlReaderDelegator reader, XmlObjectSerializerReadContext context)}

我怀疑错误是因为对象引用了一个在Extension Object内反序列化的类型,并且没有任何类型。 原因是,如果在People中添加Person的新实例,而不是在AnotherPerson(FriendPerson)中引用相同的实例。

var anotherPerson = new AnotherPerson() { Name = "AnotherPerson", FriendPerson = new Person() };

然后保存的文件变为以下内容,一切正常:

<People xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="Tests.FCTests">
  <AnotherPerson z:Id="2" i:type="AnotherPerson">
    <FriendPerson z:Id="3">
      <Name i:nil="true" />
    </FriendPerson>
    <Name z:Id="4">AnotherPerson</Name>
  </AnotherPerson>
  <Person z:Id="5" i:type="Person">
    <Name z:Id="6">Person</Name>
  </Person>
</People>

我尝试使用数据合约解析器,动态地在序列化程序中添加已知类型和数据合同代理来解决问题但是,它们都没有工作。原因是当序列化程序反序列化FriendPerson并且在此之前没有调用代理或解析器内部的重写方法时抛出异常。

注意我们需要保留对象引用并删除它不是一种选择。

2 个答案:

答案 0 :(得分:2)

问题是Person数据合约V2中字段的顺序。新字段需要附加在序列化文档的末尾,以便向前兼容:

<People xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="Tests.FCTests">
  <AnotherPerson z:Id="2" i:type="AnotherPerson">
    <FriendPerson z:Id="3">
      <Name z:Id="4">Person</Name>
    </FriendPerson>
    <Name z:Id="5">AnotherPerson</Name>
  </AnotherPerson>
  <Person z:Ref="3" i:nil="true" />
</People>

注意&#34; FriendPerson&#34;上面的XML中的标记出现在&#34; Name&#34;标签在&#34; AnotherPerson&#34;分割。如果您的对象已按如下方式序列化,它将起作用:

<People xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="Tests.FCTests">
  <AnotherPerson z:Id="2" i:type="AnotherPerson">
    <Name z:Id="5">AnotherPerson</Name>
    <FriendPerson z:Id="3">
      <Name z:Id="4">Person</Name>
    </FriendPerson>
  </AnotherPerson>
  <Person z:Ref="3" i:nil="true" />
</People>

要实现此目的,请指定&#34;订单&#34; &#34; FriendPerson&#34;的DataMemberAttribute上的参数V2&#34; AnotherPerson&#34;课程如下:

[DataContract(Name = "AnotherPerson", Namespace = "Tests.FCTests")]
public class AnotherPerson : IExtensibleDataObject
{
    [DataMember]
    public string Name { get; set; }

    /* This is added in this version */
    [DataMember(Order = 2)]
    public Person FriendPerson { get; set; }

    public ExtensionDataObject ExtensionData { get; set; }
}

作为一般规则,您不应使用&#34;订单&#34; datacontract第一版中的参数。对于任何较新的版本,您应指定&#34;订单&#34;任何新DataMemberAttribute上的参数,并将指定的数字与版本号一起递增。拥有几个相同的&#34; Order&#34;是完全合法的。单个datacontract中的参数值,例如本V3:

[DataContract(Name = "AnotherPerson", Namespace = "Tests.FCTests")]
public class AnotherPerson : IExtensibleDataObject
{
    [DataMember]
    public string Name { get; set; }

    /* This is added in this version */
    [DataMember(Order = 2)]
    public Person FriendPerson { get; set; }

    [DataMember(Order = 3)]
    public string Remarks { get; set; }

    [DataMember(Order = 3)]
    public bool? IsMarried { get; set; }

    public ExtensionDataObject ExtensionData { get; set; }
}

P.S。:我的回答可能会迟到,但可能对其他人有帮助......

答案 1 :(得分:0)

我正在与MSDN事件支持进行沟通,经过两个月的回访,他们回答说:

  

我们订婚了产品组,官方的话是有错误的   在IExtensibleDataObject中(当循环引用为ON时)。

我希望他们在文档中的某处添加它,我希望这有助于其他人的未来发展。