【问题标题】:Data Contract Serializer Forward Compatibility of properties which are referencedData Contract Serializer 被引用的属性的前向兼容性
【发布时间】:2015-08-05 19:00:44
【问题描述】:

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

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

我在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)}

我怀疑这个错误是因为对象引用了一个在扩展对象内部被反序列化的类型并且没有任何类型。 原因是,如果您在 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>

我尝试使用 Data Contract Resolver、在序列化程序中动态添加已知类型以及 Data Contract Surrogate 来解决此问题,但它们都不起作用。原因是当序列化程序反序列化 FriendPerson 并且在此之前没有调用代理或解析器内的覆盖方法时引发异常。

注意我们需要保留对象引用并且不能删除它。

【问题讨论】:

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


    【解决方案1】:

    问题在于 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>
    

    请注意上述 XML 中的“FriendPerson”标记如何出现在“AnotherPerson”段中的“Name”标记上方。如果您的对象已按如下方式序列化,它将起作用:

    <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>
    

    为此,请在 V2 "AnotherPerson" 类的 "FriendPerson" 属性的 DataMemberAttribute 上指定 "Order" 参数,如下所示:

    [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; }
    }
    

    作为一般规则,您不应在数据合同的第一个版本中使用“订单”参数。对于任何较新的版本,您应该在任何新的 DataMemberAttribute 上指定“Order”参数,并将指定的数字与版本号一起递增。在单个数据合同中拥有多个相同的“订单”参数值是完全合法的,例如在这个 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.:我的回答可能来晚了,但可能仍然对其他人有所帮助......

    【讨论】:

    • 这将解决这个特定的问题,但它实际上不是一个通用的解决方案。如果在 V2 Person 中还添加了对 AnotherPerson 的引用怎么办?然后,使用 order 无法解决问题。
    【解决方案2】:

    我正在与 MSDN 事件支持人员沟通,经过 2 个月的来来回回,他们回答:

    我们已经聘请了产品组,官方的说法是有一个错误 在 IExtensibleDataObject 中(当循环引用为 ON 时)。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-11
      • 1970-01-01
      • 2013-12-17
      • 1970-01-01
      • 2011-11-17
      相关资源
      最近更新 更多