2009-06-19 2 views
13

Я пытаюсь сериализовать некоторые объекты с помощью XmlSerializer и наследования, но у меня возникают некоторые проблемы с упорядочением результата.. Сериализация .NET.

Ниже приведен пример похож на то, что у меня есть установка: ~

public class SerializableBase 
{ 
    [XmlElement(Order = 1)] 
    public bool Property1 { get; set;} 

    [XmlElement(Order = 3)] 
    public bool Property3 { get; set;} 
} 

[XmlRoot("Object")] 
public class SerializableObject1 : SerializableBase 
{ 
} 

[XmlRoot("Object")] 
public class SerializableObject2 : SerializableBase 
{ 
    [XmlElement(Order = 2)] 
    public bool Property2 { get; set;} 
} 

Результат я хочу, заключается в следующем: ~

<Object> 
    <Property1></Property1> 
    <Property2></Property2> 
    <Property3></Property3> 
</Object> 

Однако я получаю результат: ~

<Object> 
    <Property1></Property1> 
    <Property3></Property3> 
    <Property2></Property2> 
</Object> 

Кто-нибудь знает, если это возможно или какой-либо другой альтернативы?

Благодаря

+0

У меня была проблема, подобная этому, когда мне нужно, чтобы свойство в производном классе появлялось последним в сообщении SOAP, моим решением было добавить свойство как внутреннее в базовый класс, а затем скрыть его с помощью «нового» ключевого слова в производном классе. См. Мой ответ [здесь] (http://stackoverflow.com/questions/22174311/wcf-serialization-order-issue/22177272#22177272). Надеюсь, поможет. –

ответ

3

Похоже, что класс XmlSerializer сериализует базовый тип, а затем полученные тип в таком порядке, и только уважая свойство Order в пределах каждого класса в отдельности. Несмотря на то, что заказ не совсем то, что вы хотите, он все равно должен Deserialize правильно. Если вам действительно нужен такой порядок, вам нужно будет написать собственный XML-сериализатор. Я бы предостерег от этого, что .NET XmlSerializer делает для вас много специальной обработки. Можете ли вы описать, почему вам нужны вещи в указанном вами порядке?

3

EDIT: Этот подход не работает. Я покинул этот пост, чтобы люди могли избежать этого мышления.

Сериализатор действует рекурсивно. Для этого есть преимущество; при десериализации процесс десериализации может читать базовый класс, а затем производный класс. Это означает, что свойство на производном классе не задано перед свойствами на базе, что может привести к проблемам.

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

1) сделать базовый класс property1 и Property3 виртуальной. 2) переопределить их тривиальными свойствами в производном классе. Например

public class SerializableBase 
{ 
    [XmlElement(Order = 1)] 
    public virtual bool Property1 { get; set;} 

    [XmlElement(Order = 3)] 
    public virtual bool Property3 { get; set;} 
} 

[XmlRoot("Object")] 
public class SerializableObject1 : SerializableBase 
{ 
} 

[XmlRoot("Object")] 
public class SerializableObject2 : SerializableBase 
{ 
    [XmlElement(Order = 1)] 
    public override bool Property1 
    { 
     get { return base.Property1; } 
     set { base.Property1 = value; } 
    } 

    [XmlElement(Order = 2)] 
    public bool Property2 { get; set;} 

    [XmlElement(Order = 3)] 
    public override bool Property3 
    { 
     get { return base.Property3; } 
     set { base.Property3 = value; } 
    } 

} 

Это ставит конкретную implementtion собственности на наиболее производного класса, и порядок должны быть соблюдены.

+0

Я пробовал это - он действительно не работал –

+0

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

+0

все еще была хорошая идея :) –

15

Технически, с чистой точки зрения xml, я бы сказал, что это, вероятно, плохо, что нужно делать.

.NET скрывает большую часть сложности, такой как XmlSerialization - в этом случае он скрывает схему, которой должен соответствовать ваш serialized xml.

Введенная схема будет использовать элементы последовательности для описания базового типа и типов расширений. Для этого требуется строгий порядок - даже если десериализатор менее строг и принимает элементы порядка.

В xml-схемах при определении типов расширений дополнительные элементы из дочернего класса должны наступить после элементов из базового класса.

вы по существу иметь схему, которая выглядит примерно так (XML-у тегов для ясности)

base 
    sequence 
    prop1 
    prop3 

derived1 extends base 
    sequence 
    <empty> 

derived2 extends base 
    sequence 
    prop2 

Там нет никакого способа, чтобы вставить заполнитель между prop1 и prop3, чтобы указать, где свойства из производных xml может пойти.

В итоге у вас есть несоответствие между вашим форматом данных и бизнес-объектом. Вероятно, ваша лучшая альтернатива - это определить объект для работы с вашей сериализацией xml.

Например

[XmlRoot("Object") 
public class SerializableObjectForPersistance 
{ 
    [XmlElement(Order = 1)] 
    public bool Property1 { get; set; } 

    [XmlElement(Order = 2, IsNullable=true)] 
    public bool Property2 { get; set; } 

    [XmlElement(Order = 3)] 
    public bool Property3 { get; set; } 
} 

Это отделяет ваш XML-код сериализации из объектной модели. Скопируйте все значения из SerializableObject1 или SerializableObject2 в SerializableObjectForPersistance, а затем выполните сериализацию.

По существу, если вы хотите, чтобы такой специфический контроль над форматом вашего сериализованного xml, который не совсем преувеличивает с каркасом сериализации ожиданий xml, вам необходимо отделить дизайн вашего бизнес-объекта (структура наследования в этом случае) и ответственность для сериализации этого бизнес-объекта.

+0

+1, схема является важной, но легко упускаемой точкой. – shambulator

+1

Вы очень хорошо понимаете, почему упорядочение не ведет себя «как ожидалось», потому что мы думаем с точки зрения ООП, а не XML-схем. В моем частном случае ослабленная связь не является подходящей конструкцией (опять же, это ниша - всегда стремиться к свободному соединению!), И если это ваша ситуация, вы всегда можете попробовать агрегацию, где «дочерний» объект _ содержит_ «родительский» объект. Вы все еще достигаете инкапсуляции и повторного использования, но вы также можете указать для ребенка точное упорядочение элементов. – fourpastmidnight

+0

@Nader Я ответил на ваш ответ в ответе на этот вопрос. Хотя мое решение работает, ваш явно превосходит. Я ошибался, когда говорить о том, что в моем дизайне не было подходящего сцепления. Если у меня когда-нибудь будет возможность рефакторинга, я буду использовать ваши рекомендации! – fourpastmidnight

0

Как сказал Надер, возможно, подумайте о создании более несвязанного дизайна. Однако, в моем случае, ослабление связи было неприемлемым. Вот моя иерархия классов и как я предлагаю решить проблему без использования пользовательской сериализации или DTO.

В моем проекте я создаю целую кучу объектов для представления частей XML-документа, который будет передаваться через веб-службу. Очень много штук. Не все отправляются с каждым запросом (фактически, в этом примере я моделирую ответ, но понятия одинаковы). Эти части используются так же, как и строительные блоки для сборки запроса (или разобрать ответ в этом случае). Итак, вот пример использования агрегирования/инкапсуляции для выполнения желаемого упорядочения, несмотря на иерархию наследования.

[Serializable] 
public abstract class ElementBase 
{ 
    // This constructor sets up the default namespace for all of my objects. Every 
    // Xml Element class will inherit from this class. 
    internal ElementBase() 
    { 
     this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] { 
      new XmlQualifiedName(string.Empty, "urn:my-default-namespace:XSD:1") 
     }); 
    } 

    [XmlNamespacesDeclaration] 
    public XmlSerializerNamespaces Namespaces { get { return this._namespaces; } } 
    private XmlSerializationNamespaces _namespaces; 
} 


[Serializable] 
public abstract class ServiceBase : ElementBase 
{ 
    private ServiceBase() { } 

    public ServiceBase(Guid requestId, Guid? asyncRequestId = null, Identifier name = null) 
    { 
     this._requestId = requestId; 
     this._asyncRequestId = asyncRequestId; 
     this._name = name; 
    } 

    public Guid RequestId 
    { 
     get { return this._requestId; } 
     set { this._requestId = value; } 
    } 
    private Guid _requestId; 

    public Guid? AsyncRequestId 
    { 
     get { return this._asyncRequestId; } 
     set { this._asyncRequestId = value; } 
    } 
    private Guid? _asyncRequestId; 

    public bool AsyncRequestIdSpecified 
    { 
     get { return this._asyncRequestId == null && this._asyncRequestId.HasValue; } 
     set { /* XmlSerializer requires both a getter and a setter.*/ ; } 
    } 

    public Identifier Name 
    { 
     get { return this._name; } 
     set { this._name; } 
    } 
    private Identifier _name; 
} 


[Serializable] 
public abstract class ServiceResponseBase : ServiceBase 
{ 
    private ServiceBase _serviceBase; 

    private ServiceResponseBase() { } 

    public ServiceResponseBase(Guid requestId, Guid? asyncRequestId = null, Identifier name = null, Status status = null) 
    { 
     this._serviceBase = new ServiceBase(requestId, asyncRequestId, name); 
     this._status = status; 
    } 

    public Guid RequestId 
    { 
     get { return this._serviceBase.RequestId; } 
     set { this._serviceBase.RequestId = value; } 
    } 

    public Guid? AsyncRequestId 
    { 
     get { return this._serviceBase.AsyncRequestId; } 
     set { this._serviceBase.AsyncRequestId = value; } 
    } 

    public bool AsynceRequestIdSpecified 
    { 
     get { return this._serviceBase.AsyncRequestIdSpecified; } 
     set { ; } 
    } 

    public Identifier Name 
    { 
     get { return this._serviceBase.Name; } 
     set { this._serviceBase.Name = value; } 
    } 

    public Status Status 
    { 
     get { return this._status; } 
     set { this._status = value; } 
    } 
} 

[Serializable] 
[XmlRoot(Namespace = "urn:my-default-namespace:XSD:1")] 
public class BankServiceResponse : ServiceResponseBase 
{ 
    // Determines if the class is being deserialized. 
    private bool _isDeserializing; 

    private ServiceResponseBase _serviceResponseBase; 

    // Constructor used by XmlSerializer. 
    // This is special because I require a non-null List<T> of items later on. 
    private BankServiceResponse() 
    { 
     this._isDeserializing = true; 
     this._serviceResponseBase = new ServiceResponseBase(); 
    } 

    // Constructor used for unit testing 
    internal BankServiceResponse(bool isDeserializing = false) 
    { 
     this._isDeserializing = isDeserializing; 
     this._serviceResponseBase = new ServiceResponseBase(); 
    } 

    public BankServiceResponse(Guid requestId, List<BankResponse> responses, Guid? asyncRequestId = null, Identifier name = null, Status status = null) 
    { 
     if (responses == null || responses.Count == 0) 
      throw new ArgumentNullException("The list cannot be null or empty", "responses"); 

     this._serviceResponseBase = new ServiceResponseBase(requestId, asyncRequestId, name, status); 
     this._responses = responses; 
    } 

    [XmlElement(Order = 1)] 
    public Status Status 
    { 
     get { return this._serviceResponseBase.Status; } 
     set { this._serviceResponseBase.Status = value; } 
    } 

    [XmlElement(Order = 2)] 
    public Guid RequestId 
    { 
     get { return this._serviceResponseBase.RequestId; } 
     set { this._serviceResponseBase.RequestId = value; } 
    } 

    [XmlElement(Order = 3)] 
    public Guid? AsyncRequestId 
    { 
     get { return this._serviceResponseBase.AsyncRequestId; } 
     set { this._serviceResponseBase.AsyncRequestId = value; } 
    } 

    [XmlIgnore] 
    public bool AsyncRequestIdSpecified 
    { 
     get { return this._serviceResponseBase.AsyncRequestIdSpecified; } 
     set { ; } // Must have this for XmlSerializer. 
    } 

    [XmlElement(Order = 4)] 
    public Identifer Name 
    { 
     get { return this._serviceResponseBase.Name; } 
     set { this._serviceResponseBase.Name; } 
    } 

    [XmlElement(Order = 5)] 
    public List<BankResponse> Responses 
    { 
     get { return this._responses; } 
     set 
     { 
      if (this._isDeserializing && this._responses != null && this._responses.Count > 0) 
       this._isDeserializing = false; 

      if (!this._isDeserializing && (value == null || value.Count == 0)) 
       throw new ArgumentNullException("List cannot be null or empty.", "value"); 

      this._responses = value; 
     } 
    } 
    private List<BankResponse> _responses; 
} 

Таким образом, в то время как я должен создавать свойства для всех содержащихся классов, я могу делегировать любой пользовательской логики я мог бы иметь в Содержащийся класс (ы) собственности сеттеры/добытчиками, просто используя свойства Содержащийся класса, когда доступ к свойствам класса листа. Поскольку наследования нет, я могу украсить все свойства листового класса атрибутом XmlElementAttribute и использовать любое упорядочение, которое я считаю нужным.


UPDATE:

Я вернулся, чтобы вновь эту статью, потому что мои дизайнерские решения об использовании наследования класса вернулся, чтобы укусить меня снова. Хотя мое решение выше работает, я использую его, я действительно думаю, что решение Nader является лучшим и должно рассматриваться до решения, которое я представил. На самом деле, я + 1 его сегодня! Мне очень нравится его ответ, и если у меня когда-нибудь будет возможность реорганизовать мой текущий проект, я определенно буду отделять бизнес-объект от логики сериализации для объектов, которые в противном случае выиграли бы от наследования, чтобы упростить код и упростить его для других использовать и понимать.

Спасибо за сообщение вашего ответа. Надер, как я думаю, многие найдут его очень поучительным и полезным.

2

Это сообщение довольно устарело, но у меня была аналогичная проблема в WCF в последнее время, и нашел решение, похожее на вышеприведенное Стив Купер, но тот, который действительно работает и, по-видимому, будет работать и для XML-сериализации.

Если вы удалите атрибуты XmlElement из базового класса и добавьте копию каждого свойства с другим именем в производные классы, которые получают доступ к базовому значению с помощью get/set, копии могут быть сериализованы с соответствующим именем назначены с использованием XmlElementAttribute, и мы надеемся, затем сериализации в порядке по умолчанию:

public class SerializableBase 
{ 
    public bool Property1 { get; set;} 
    public bool Property3 { get; set;} 
} 

[XmlRoot("Object")] 
public class SerializableObject : SerializableBase 
{ 
    [XmlElement("Property1")] 
    public bool copyOfProperty1 
    { 
    get { return base.Property1; } 
    set { base.Property1 = value; } 
    } 

    [XmlElement] 
    public bool Property2 { get; set;} 

    [XmlElement("Property3")] 
    public bool copyOfProperty3 
    { 
    get { return base.Property3; } 
    set { base.Property3 = value; } 
    } 
} 

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

interface ISerializableObjectEnsureProperties 
{ 
    bool copyOfProperty1 { get; set; } 
    bool copyOfProperty2 { get; set; } 
} 

Это не так но означает, что я могу проверить, что все реализовано во время компиляции, а не проверка результирующего XML. Я изначально сделал эти абстрактные свойства SerializableBase, но затем они сериализуются сначала (с базовым классом), которые я теперь понимаю, логичен.

Это называется обычным способом, изменив одну строку выше:

public class SerializableObject : SerializableBase, ISerializableObjectEnsureProperties 

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

2

Я знаю, что этот вопрос истёс; однако, вот решение этой проблемы:

Имя метода всегда должно начинаться с ShouldSerialize, а затем заканчиваться именем свойства. Затем вам просто нужно вернуть логическое значение на основе любого условного вы хотите, чтобы сериализовать значение или нет.

public class SerializableBase 
{ 
    public bool Property1 { get; set;} 
    public bool Property2 { get; set;} 
    public bool Property3 { get; set;} 

    public virtual bool ShouldSerializeProperty2 { get { return false; } } 
} 

[XmlRoot("Object")] 
public class SerializableObject1 : SerializableBase 
{   
} 

[XmlRoot("Object")] 
public class SerializableObject2 : SerializableBase 
{ 
    public override bool ShouldSerializeProperty2 { get { return true; } } 
} 

Результат при использовании SerializableObject2: ~

<Object> 
    <Property1></Property1> 
    <Property2></Property2> 
    <Property3></Property3> 
</Object> 

Результата при использовании SerializableObject1: ~

<Object> 
    <Property1></Property1> 
    <Property3></Property3> 
</Object> 

Надеется, что это помогает многим другимам!

+0

Это действительно работает. Тем не менее, вы по существу добавили Property2 в классы, к которым он не принадлежит (хотя он может не отображаться в xml). – Ian1971

Смежные вопросы