0

我使用 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 <Vehicle xmlns=''>.我在反序列化行上遇到异常。

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

我希望能够反序列化然后转换为Carand Truck。我怎样才能使这项工作?

4

1 回答 1

2

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

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 指定<Vehicles>列表没有容器元素。相反,它应该只是一组重复的元素,如下所示:

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

    要指定具有名为的内部元素的外部包装器元素<Vehicle>,您的 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 属性绑定支持中所述。只有这样才能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所示。

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

于 2016-10-03T21:03:58.147 回答