[英]Data Contract Serializer Forward Compatibility of properties which are referenced
我正在嘗試支持數據合同序列化程序的前向兼容性。 我遇到的問題是:
如果您有一個對象,該對象另存為對屬性的引用,而該屬性是在已知類型的更高版本中添加的,則它將成為例外。 請注意,這兩種類型在這兩個版本中都是已知的。 唯一新的是其中一個對象內部的屬性。
它有兩個不同的項目:V1,這是一個已經部署的舊版本。 V2是V1的較新版本。 V2正在保存其數據,並且V1需要能夠加載V2保存的數據以支持前向兼容性。
共有三種自定義類型:人員:具有兩個對象引用,並且將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>
我試圖使用數據協定解析器,動態地在序列化器內部添加已知類型和數據協定代理來解決問題,但是它們都不起作用。 原因是當序列化程序對FriendPerson進行反序列化並且在此之前未調用替代項或解析器內部的重寫方法時,拋出異常。
注意我們需要保留對象引用,而刪除它不是一種選擇。
問題是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; }
}
通常,不應在數據合同的第一個版本中使用“ Order”參數。 對於任何較新的版本,應在任何新的DataMemberAttribute上指定“ Order”參數,並將指定的數字與版本號一起增加。 在單個數據合約中(例如在此V3中)具有多個相同的“ 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; }
[DataMember(Order = 3)]
public string Remarks { get; set; }
[DataMember(Order = 3)]
public bool? IsMarried { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
PS:我的答案可能來晚了,但可能對其他人還是有幫助的...
我與MSDN事件支持部門進行了交流,經過2個月的反復考察,他們回答:
我們已經聘請了產品小組,正式的說法是IExtensibleDataObject中存在錯誤(當循環引用為ON時)。
我希望他們將此添加到他們的文檔中,希望對其他人的未來發展有所幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.