2009-06-19 10 views
13

Próbuję serializować niektóre obiekty przy użyciu XmlSerializer i dziedziczenia, ale mam problemy z zamówieniem wyniku..NET Serializacja Kolejność

Poniżej jest przykład podobny do tego, co mam setup: ~

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

Wynik Chcę się następująco: ~

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

Jednak ja otrzymuję wynik: ~

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

Czy ktoś wie, czy jest to możliwe lub jakiejkolwiek alternatywy?

Dzięki

+0

Miałem problem podobny do tego, w którym potrzebowałem właściwości w klasie pochodnej, aby pojawił się jako ostatni w komunikacie SOAP, moim rozwiązaniem było dodanie właściwości jako wewnętrznej w klasie bazowej, a następnie ukrycie jej za pomocą słowa kluczowego "new" w klasie pochodnej. Zobacz moją odpowiedź [tutaj] (http://stackoverflow.com/questions/22174311/wcf-serialization-order-issue/22177272#22177272). Mam nadzieję, że to pomoże. –

Odpowiedz

3

Wygląda klasy XmlSerializer serializuje typ bazowy, a następnie pochodzi typy w tej kolejności i poszanowaniu własności Order tylko w obrębie każdej klasy osobno. Mimo że zamówienie nie jest całkowicie tym, czego potrzebujesz, powinno ono nadal poprawnie Deserializować. Jeśli naprawdę musisz mieć takie zamówienie, musisz napisać niestandardowy serializator XML. Ostrzegam przed tym, że program .NET XmlSerializer wykonuje dla ciebie wiele specjalnych czynności. Czy możesz opisać, dlaczego potrzebujesz rzeczy w podanej kolejności?

3

EDYTOWANIE: To podejście nie działa. Opuściłem stanowisko, aby ludzie mogli uniknąć tego sposobu myślenia.

Serializator działa rekurencyjnie. Jest to korzystne; w przypadku deserializacji proces deserializacji może odczytać klasę podstawową, a następnie klasę pochodną. Oznacza to, że właściwość klasy pochodnej nie jest ustawiona przed właściwościami w bazie, co może prowadzić do problemów.

Jeśli to naprawdę ważne (i nie jestem pewien, dlaczego ważne jest, aby dostać je w kolejności), to możesz spróbować -

1) sprawiają, że klasa bazowa Property1 i Property3 wirtualny. 2) nadpisuj je za pomocą banalnych właściwości w klasie pochodnej. Np.

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

} 

Stawia to konkretną implementację nieruchomości na najbardziej pochodnej klasy, a kolejność powinna być przestrzegana.

+0

Próbowałem tego - tak naprawdę nie działało –

+0

Ach, przykro mi - myślałem, że zadziałało, patrząc na klasę, która zawierała konkretną implementację, ale wyraźnie nie. Zaktualizuję wpis, aby wskazać, że to podejście nie działa. –

+0

to nadal był dobry pomysł :) –

15

Z technicznego punktu widzenia, z czystej perspektywy xml, powiedziałbym, że prawdopodobnie nie jest to złe.

.NET ukrywa wiele złożoności takich rzeczy jak XmlSerialization - w tym przypadku ukrywa schemat, do którego powinien dostosować się twój serializowany xml.

Przyjęty schemat użyje elementów sekwencji do opisania typu bazowego i typów rozszerzeń. Wymaga to ścisłego porządkowania - nawet jeśli Deserializator jest mniej rygorystyczny i akceptuje elementy nieczynne.

W schematach xml, podczas definiowania typów rozszerzeń, dodatkowe elementy z klasy potomnej muszą pochodzić od po elementów z klasy bazowej.

byś mają zasadniczo schematu, który wygląda mniej więcej tak (znaczniki XML-y usuniętymi dla jasności)

base 
    sequence 
    prop1 
    prop3 

derived1 extends base 
    sequence 
    <empty> 

derived2 extends base 
    sequence 
    prop2 

nie ma sposobu, aby trzymać zastępczy pomiędzy PROP1 i prop3 wskazać gdzie Właściwości z pochodny xml może iść.

Na koniec występuje niezgodność między formatem danych a obiektem biznesowym. Prawdopodobnie najlepszą alternatywą jest zdefiniowanie obiektu do radzenia sobie z serializacją xml.

Na przykład

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

ten rozdziela swój kod serializacji xml z modelu obiektowego. Skopiuj wszystkie wartości z SerializableObject1 lub SerializableObject2 do SerializableObjectForPersistance, a następnie serializuj je.

Zasadniczo, jeśli chcesz mieć taką szczególną kontrolę nad formatowanym serializowanym xml, który nie pasuje do oczekiwanego schematu serializacji xml, musisz oddzielić projekt obiektu biznesowego (struktura dziedziczenia w tym przypadku) i odpowiedzialność do serializacji tego obiektu biznesowego.

+0

+1, schemat jest ważnym, ale łatwo pomijanym punktem. – shambulator

+1

Podkreślasz, dlaczego zamawianie nie zachowuje się "zgodnie z oczekiwaniami", ponieważ myślimy w kategoriach schematów OOP, a nie XML. W moim konkretnym przypadku luźne sprzężenie nie jest odpowiednim projektem (znowu jest to niszowe - zawsze dąży do luźnego sprzężenia!), A jeśli tak jest, zawsze możesz spróbować agregacji, gdzie "dziecko" obiekt _ zawiera obiekt "macierzysty". Nadal można osiągnąć hermetyzację i ponowne wykorzystanie, ale można również określić dla dziecka dokładną kolejność elementów. – fourpastmidnight

+0

@Nader Odniosłem się do twojej odpowiedzi w mojej odpowiedzi na to pytanie. Podczas gdy moje rozwiązanie działa, twoja jest wyraźnie lepsza. Byłem niewłaściwy, mówiąc, że luźne sprzężenie nie było właściwe w moim projekcie. Jeśli kiedykolwiek będę miał szansę na refaktor, korzystam z twoich rekomendacji! – fourpastmidnight

0

Tak jak powiedział Nader, może pomyśleć o stworzeniu bardziej luźnego projektu. Jednak w moim przypadku sprzężenie luźne nie było odpowiednie. Oto moja hierarchia klasowa i jak proponuję rozwiązać problem bez korzystania z niestandardowej serializacji lub DTO.

W moim projekcie tworzę cały garść obiektów do reprezentowania fragmentów dokumentu XML, który zostanie przesłany za pośrednictwem usługi internetowej. Istnieje bardzo duża liczba sztuk. Nie wszystkie są wysyłane przy każdym żądaniu (właściwie w tym przykładzie modeluję odpowiedź, ale koncepcje są takie same). Fragmenty te są używane podobnie jak bloki konstrukcyjne do składania żądania (lub w tym przypadku do dezasemblacji odpowiedzi). Oto przykład zastosowania agregacji/enkapsulacji do osiągnięcia pożądanego porządku mimo hierarchii dziedziczenia.

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

Tak więc, choć muszę utworzyć właściwości dla wszystkich zawartych zajęciach, mogę przekazać dowolną niestandardową logikę może mam wewnątrz zamkniętego klasa (-y) ustawiaczy nieruchomość/pobierające po prostu za pomocą właściwości zawartego klasa kiedy właściwości klasy liści są dostępne. Ponieważ nie ma dziedziczenia, mogę ozdobić wszystkie właściwości klasy liści atrybutem XmlElementAttribute i użyć dowolnej kolejności, którą widzę.


UPDATE:

wróciłem do ponownego ten artykuł, ponieważ mój projekt decyzji o użyciu klasy dziedziczenie wrócił ugryźć mnie ponownie. Chociaż moje powyższe rozwiązanie działa, używam go, naprawdę uważam, że rozwiązanie Nadera jest najlepsze i powinno być rozważone przed rozwiązaniem, które przedstawiłem. Właściwie to już go + 1'emu! Bardzo podoba mi się jego odpowiedź, a jeśli kiedykolwiek będę miał okazję, by refaktoryzować mój obecny projekt, zdecydowanie oddzielę obiekt biznesowy od logiki serializacji w przypadku przedmiotów, które w przeciwnym razie w znacznym stopniu skorzystałyby z dziedziczenia, aby uprościć kod i ułatwić mu dla innych do używania i rozumienia.

Dzięki za wysłanie odpowiedzi Nader, jak myślę, wielu uzna ją za bardzo pouczającą i przydatną.

2

Ten post jest dość stary, ale ostatnio miałem podobny problem w WCF i znalazłem rozwiązanie podobne do Steve'a Coopera powyżej, ale takie, które działa, i prawdopodobnie będzie działać również dla Serializacji XML.

Jeśli usuniesz atrybuty XmlElement z klasy bazowej i dodasz kopię każdej właściwości o innej nazwie do klas pochodnych, które uzyskają dostęp do wartości bazowej za pomocą polecenia get/set, kopie mogą zostać przekształcone do postaci szeregowej z odpowiednią nazwą przypisane przy użyciu XmlElementAttribute, i miejmy nadzieję, następnie serializacji w domyślnej kolejności:

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

Dodałem też interfejs do dodawania do klas pochodnych, tak że kopie mogą być obowiązkowe:

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

To nie jest es sential, ale oznacza, że ​​mogę sprawdzić wszystko jest realizowane w czasie kompilacji, zamiast sprawdzać wynikowy XML. Pierwotnie stworzyłem te abstrakcyjne właściwości SerializableBase, ale te następnie serializują najpierw (z klasą podstawową), co teraz rozumiem jest logiczne.

Nazywa się to w zwykły sposób, zmieniając jedną linię powyżej:

public class SerializableObject : SerializableBase, ISerializableObjectEnsureProperties 

Przetestowałem to tylko w WCF i przenieśli koncepcję do serializacji XML bez kompilowania, więc jeśli to nie pracuję, przepraszam, ale spodziewam się, że będzie się zachowywał w taki sam sposób - jestem pewien, że ktoś da mi znać, jeśli nie ...

2

Wiem, że to pytanie wygasło; jednak tutaj jest rozwiązanie dla tego problemu:

Nazwa metody powinna zawsze zaczynać się od ShouldSerialize, a następnie kończyć się nazwą właściwości. Następnie wystarczy zwrócić wartość typu boolean na podstawie dowolnego warunku, którego chcesz, albo do serializowania wartości, czy nie.

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

Wynik podczas korzystania SerializableObject2: ~

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

Wynik podczas korzystania SerializableObject1: ~

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

Nadzieja to pomaga wiele innych!

+0

To faktycznie działa. Jednak zasadniczo dodałeś Property2 do klas, do których nie należy (nawet jeśli nie pojawią się w xml). – Ian1971