【问题标题】:Unable to deserialize polymorphic list from xml无法从 xml 反序列化多态列表
【发布时间】:2017-02-02 13:17:10
【问题描述】:

我使用 XmlSerializer 对由 Xsd2Code 从具有扩展基本元素的元素的 xsd 文件生成的类从 xml 文件反序列化。

这是一个简化的例子:

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

生成的代码:

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

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>

以及反序列化代码:

var serializer = new XmlSerializer(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);
}

我在反序列化行收到异常 The specified type is abstract: name='Vehicle', namespace='', at &lt;Vehicle xmlns=''&gt;.

如果我从 XSD 中的 Vehicle 元素中删除抽象属性,我会得到一个空引用异常,因为 garage.Vehicles[0] 无法转换为 Car

我希望能够反序列化,然后转换为 CarTruck。我怎样才能做到这一点?

【问题讨论】:

    标签: c# xsd xmlserializer xsd2code


    【解决方案1】:

    基本问题是您的 XML 与您的 XSD 不匹配。如果您尝试使用 .Net 验证 XML(示例 fiddle),您将看到以下错误:

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

    这些错误的含义如下:

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

      这里的问题是您的 XSD 指定 &lt;Vehicles&gt; 列表没有容器元素。相反,它应该只是一组重复的元素,如下所示:

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

      对应的c#类是:

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

      要指定带有名为&lt;Vehicle&gt; 的内部元素的外部包装器元素,您的XSD 必须有一个额外的中间元素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>
      

      在您的问题中,您写了 这是一个简化的示例。编写问题时是否有可能手动省略 XSD 中的额外嵌套级别?

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

      您正在尝试通过使用XmlIncludeAttribute 指定可能的子类型来序列化多态列表。

      执行此操作时,XML 中的每个多态元素都必须使用 w3c 标准属性 xsi:type{http://www.w3.org/2001/XMLSchema-instance}type 的缩写)声明其实际类型,如 Xsi:type Attribute Binding Support 中所述。只有这样XmlSerializer 才能知道将每个元素反序列化为的正确具体类型。因此,您的最终 XML 应如下所示:

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

      或者,如果您希望为您的车辆集合添加一个外包装元素:

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

      后面的 XML 可以成功反序列化为您当前的 Garage 类而不会出错,如 here 所示。

    顺便说一句,调试此类问题的一种简单方法是在内存中创建Garage 类的实例,填充其车辆列表,并将其序列化。这样做后,您会发现与您尝试反序列化的 XML 不一致。

    【讨论】:

    • 感谢您的回复。实际上,在添加这个简化示例时,我忽略了一个嵌套问题,真正的代码在 xsd 和 xml 中都有正确的嵌套级别。我最终选择了您建议的相同 xsi:type 解决方案。这对我来说很好,因为我的 xml 包含一个简短的“车辆”集合,但我想知道,一个巨大的集合会做什么?可能是维护的噩梦。
    猜你喜欢
    • 2011-05-03
    • 1970-01-01
    • 1970-01-01
    • 2016-07-25
    • 1970-01-01
    • 2014-08-13
    • 2013-09-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多