2016-09-24 1 views
0

Je désérialise à partir d'un fichier xml en utilisant XmlSerializer sur des classes générées par Xsd2Code à partir d'un fichier xsd avec des éléments étendant un élément de base.Impossible de désérialiser la liste polymorphe à partir de xml

Voici un exemple simplifié:

<?xml version="1.0" encoding="utf-8"?> 
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> 
<xs:complexType name="Vehicle" abstract="true"> 
    <xs:sequence> 
    <xs:element name="Manufacturer" type="xs:string" nillable="false" /> 
    </xs:sequence> 
</xs:complexType> 
<xs:complexType name="Car"> 
    <xs:complexContent> 
    <xs:extension base="Vehicle"> 
     <xs:sequence> 
     <xs:element name="Configuration" type="xs:string" nillable="false" /> 
     </xs:sequence> 
    </xs:extension> 
    </xs:complexContent> 
</xs:complexType> 
<xs:complexType name="Truck"> 
    <xs:complexContent> 
    <xs:extension base="Vehicle"> 
     <xs:sequence> 
     <xs:element name="Load" type="xs:int" nillable="false" /> 
     </xs:sequence> 
    </xs:extension> 
    </xs:complexContent> 
</xs:complexType> 
<xs:element name="Garage"> 
    <xs:complexType> 
    <xs:sequence> 
     <xs:element name="Vehicles" type="Vehicle" minOccurs="0" maxOccurs="unbounded" nillable="false" /> 
    </xs:sequence> 
    </xs:complexType> 
</xs:element> 
</xs:schema> 

Code généré:

public partial class Garage 
{ 
    public Garage() 
    { 
     Vehicles = new List<Vehicle>(); 
    } 

    public List<Vehicle> Vehicles { get; set; } 
}  
[System.Xml.Serialization.XmlIncludeAttribute(typeof(Truck))] 
[System.Xml.Serialization.XmlIncludeAttribute(typeof(Car))] 
public partial class Vehicle 
{ 
    public string Manufacturer { get; set; } 
}  
public partial class Truck : Vehicle 
{ 
    public int Load { get; set; } 
}  
public partial class Car : Vehicle 
{ 
    public string Configuration { get; set; } 
} 

Le XML:

<?xml version="1.0" encoding="utf-8" ?> 
<Garage> 
    <Vehicles> 
    <Vehicle> 
     <Manufacturer>Honda</Manufacturer> 
     <Configuration>Sedan</Configuration> 
    </Vehicle> 
    <Vehicle> 
     <Manufacturer>Volvo</Manufacturer> 
     <Load>40</Load> 
    </Vehicle> 
    </Vehicles> 
</Garage> 

Et le code désérialisation:

var sérialiseur = new XmlSeria lizer (typeof (Garage));

using (var reader = File.OpenText("Settings.xml")) 
{ 
    var garage = (Garage)serializer.Deserialize(reader); 
    var car = garage.Vehicles[0] as Car; 
    Console.WriteLine(car.Configuration); 
} 

je reçois une exception The specified type is abstract: name='Vehicle', namespace='', at <Vehicle xmlns=''>. sur la ligne de désérialisation.

Si je supprime l'attribut abstrait de l'élément Vehicle dans XSD, j'obtiens une exception de référence nulle car garage.Vehicles[0] ne peut pas être converti en Car. Je veux être en mesure de désérialiser puis de lancer Car et Truck. Comment puis-je faire ce travail?

Répondre

1

Le problème de base est que votre XML ne correspond pas à votre XSD. Si vous essayez de valider le XML en utilisant .Net (exemple fiddle), vous verrez les erreurs suivantes:

The element 'Vehicles' is abstract or its type is abstract. 
The element 'Vehicles' has invalid child element 'Vehicle'. List of possible elements expected: 'Manufacturer'. 

Ces erreurs ont la signification suivante:

  • The element 'Vehicles' has invalid child element 'Vehicle'. List of possible elements expected: 'Manufacturer'.

    Le problème ici est que votre XSD spécifie que la liste <Vehicles> n'a pas d'élément conteneur. Au lieu de cela, il devrait juste être un jeu répétitif d'éléments comme ceci:

    <Garage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
        <Vehicles> 
        <Manufacturer>Honda</Manufacturer> 
        <Configuration>Sedan</Configuration> 
        </Vehicles> 
        <Vehicles> 
        <Manufacturer>Volvo</Manufacturer> 
        <Load>40</Load> 
        </Vehicles> 
    </Garage> 
    

    pour laquelle la classe C# correspondant est:

    public partial class Garage 
    { 
        public Garage() 
        { 
         Vehicles = new List<Vehicle>(); 
        } 
    
        [XmlElement] 
        public List<Vehicle> Vehicles { get; set; } 
    } 
    

    Pour spécifier un élément d'enveloppe extérieure avec des éléments internes nommés <Vehicle>, votre XSD devrait avoir un élément intermédiaire supplémentaire ArrayOfVehicle:

    <?xml version="1.0" encoding="utf-8"?> 
    <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> 
    <xs:complexType name="Vehicle" abstract="true"> 
        <xs:sequence> 
        <xs:element name="Manufacturer" type="xs:string" nillable="false" /> 
        </xs:sequence> 
    </xs:complexType> 
    <xs:complexType name="Car"> 
        <xs:complexContent> 
        <xs:extension base="Vehicle"> 
         <xs:sequence> 
         <xs:element name="Configuration" type="xs:string" nillable="false" /> 
         </xs:sequence> 
        </xs:extension> 
        </xs:complexContent> 
    </xs:complexType> 
    <xs:complexType name="Truck"> 
        <xs:complexContent> 
        <xs:extension base="Vehicle"> 
         <xs:sequence> 
         <xs:element name="Load" type="xs:int" nillable="false" /> 
         </xs:sequence> 
        </xs:extension> 
        </xs:complexContent> 
    </xs:complexType> 
    <!-- BEGIN CHANGES HERE --> 
    <xs:complexType name="ArrayOfVehicle"> 
    <xs:sequence> 
        <xs:element minOccurs="0" maxOccurs="unbounded" name="Vehicle" nillable="true" type="Vehicle" /> 
    </xs:sequence> 
    </xs:complexType> 
    <xs:element name="Garage"> 
        <xs:complexType> 
        <xs:sequence> 
         <xs:element minOccurs="0" maxOccurs="1" name="Vehicles" type="ArrayOfVehicle" /> 
        </xs:sequence> 
        </xs:complexType> 
    </xs:element> 
    <!-- END CHANGES HERE --> 
    </xs:schema> 
    

    Dans votre question, vous avez écrit Voici un simplifi exemple. Y a-t-il une chance que le niveau supplémentaire d'imbrication dans le XSD ait été omis manuellement lors de l'écriture de la question?

  • The element 'Vehicles' is abstract or its type is abstract.

    Vous essayez de sérialiser une liste polymorphes en spécifiant les sous-types possibles en utilisant XmlIncludeAttribute.

    En faisant cela, il est nécessaire pour chaque élément polymorphes dans le XML pour affirmer son type réel en utilisant l'attribut standard w3c xsi:type (abréviation de {http://www.w3.org/2001/XMLSchema-instance}type), comme cela est expliqué dans Xsi:type Attribute Binding Support. Ce n'est qu'ensuite que l'on connaîtra le type concret correct auquel désérialiser chaque élément. Ainsi votre XML final devrait ressembler à:

    <Garage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
        <Vehicles xsi:type="Car"> 
        <Manufacturer>Honda</Manufacturer> 
        <Configuration>Sedan</Configuration> 
        </Vehicles> 
        <Vehicles xsi:type="Truck"> 
        <Manufacturer>Volvo</Manufacturer> 
        <Load>40</Load> 
        </Vehicles> 
    </Garage> 
    

    Ou, si vous préférez avoir un élément d'enveloppe extérieure pour votre collection de véhicules:

    <Garage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
        <Vehicles> 
        <Vehicle xsi:type="Car"> 
         <Manufacturer>Honda</Manufacturer> 
         <Configuration>Sedan</Configuration> 
        </Vehicle> 
        <Vehicle xsi:type="Truck"> 
         <Manufacturer>Volvo</Manufacturer> 
         <Load>40</Load> 
        </Vehicle> 
        </Vehicles> 
    </Garage> 
    

    Ce dernier XML peut être désérialisée avec succès dans votre classe Garage actuelle sans erreurs, comme indiqué here.

Soit dit en passant, un moyen facile de déboguer ce genre de problème est de créer une instance de votre classe Garage en mémoire, remplir la liste des véhicules et sérialiser. Cela étant fait, vous auriez vu des incohérences avec le XML que vous essayiez de désérialiser.

+0

Merci pour la réponse. En effet, il y a un problème d'imbrication que j'ai négligé lors de l'ajout de cet exemple simplifié, le vrai code a le bon niveau d'imbrication dans xsd et xml. J'ai finalement opté pour la même solution xsi: type que vous avez suggérée. Cela fonctionne très bien pour moi car mon xml contient une petite collection de "Véhicules", mais je me demande, que ferions-nous pour une énorme collection? Pourrait être un cauchemar d'entretien. – Dondey