Data Contract Serializer Forward Совместимость свойств, на которые ссылаются

Я пытаюсь поддерживать прямую совместимость для сериализатора контрактов данных. Случай, с которым у меня проблемы:

Если у вас есть объект, который сохраняется как ссылка на свойство, добавленное в более поздней версии внутри известного типа, это станет исключением. Обратите внимание, что оба эти типа известны в обеих версиях. Единственное, что является новым - это свойство внутри одного из объектов.

Я приложил простое моделирование проблемы на образцах и образцах:

У него есть два разных проекта: 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)}

Я подозреваю, что ошибка в том, что объект ссылается на тип, который десериализуется внутри объекта расширения и не имеет какого-либо типа. Причина в том, что если вы добавляете новый экземпляр Person внутри People и не ссылаетесь на тот же экземпляр внутри 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 ответа

Решение

Я общался со службой поддержки MSDN, и через 2 месяца после этого они ответили:

Мы задействовали группу продуктов, и официальное слово - ошибка в IExtensibleDataObject (когда циклические ссылки включены).

Я надеюсь, что они добавят это где-нибудь в свою документацию, и я надеюсь, что это поможет будущим разработкам для других.

Проблема заключается в порядке полей в V2 Data-контракта 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 z:Id="4">Person</Name>
    </FriendPerson>
    <Name z:Id="5">AnotherPerson</Name>
  </AnotherPerson>
  <Person z:Ref="3" i:nil="true" />
</People>

Обратите внимание, как тег "FriendPerson" в приведенном выше XML-коде отображается над тегом "Name" в сегменте "AnotherPerson". Это сработало бы, если бы ваш объект был сериализован следующим образом:

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

Для этого укажите параметр "Порядок" в атрибуте DataMemberAttribute свойства "FriendPerson" класса "AnotherPerson" V2 следующим образом:

[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 и увеличить указанное число вместе с номером версии. Вполне допустимо иметь несколько одинаковых значений параметра "Порядок" в одном датаконтракте, например, в этом 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; }
}

PS: мой ответ может прийти поздно, но все же может быть полезным для других...

Другие вопросы по тегам