使用C#中的属性对可为空的元素进行XML序列化

时间:2016-04-29 11:03:47

标签: c# xml serialization xsd

我们使用属性nilReason来表示XML元素为空的原因。例子:

<dateOfDeath nilReason="noValue" xsi:nil="true"/>
<dateOfDeath nilReason="valueUnknown" xsi:nil="true"/>

在第一个例子中,这个人还活着,因为没有死亡日期。在第二个例子中,我们不知道死亡日期是什么。

此元素的XSD定义如下:

<xs:element name="dateOfDeath" type="DateOfDeath" nillable="true"/>
<xs:complexType name="DateOfDeath">
    <xs:simpleContent>
        <xs:extension base="xs:date">
            <xs:attribute name="nilReason" type="NilReason"/>
        </xs:extension>
    </xs:simpleContent>
</xs:complexType>
<xs:simpleType name="NilReason">
    <xs:restriction base="xs:string">
        <xs:enumeration value="noValue"/>
        <xs:enumeration value="valueUnknown"/>
    </xs:restriction>
</xs:simpleType>

当我使用.net框架提供的XSD.exe工具生成C#类时遇到问题。如何编写生成以下XML的代码?

<dateOfDeath nilReason="noValue" xsi:nil="true"/>

这是我能写的最佳近似代码:

DateOfDeath dateOfDeath = new DateOfDeath();
dateOfDeath.nilReason = NilReason.noValue;
dateOfDeath.nilReasonSpecified = true;
XmlSerializer serializer = new XmlSerializer(typeof(DateOfDeath));
StreamWriter writer = new StreamWriter("dateofdeath.xml");
serializer.Serialize(writer, dateOfDeath);
writer.Close();

然而,遗憾的是,此代码产生以下结果:

<dateOfDeath nilReason="noValue">0001-01-01</dateOfDeath>

这不是我想要的,因为它会生成一个虚拟日期值。看来这是序列化器的一个缺点。解决这个问题的唯一方法似乎是应用一个删除虚拟值的函数并插入xsi:nil =&#34; true&#34;序列化后的属性。然后还需要一个删除xsi的函数:nil =&#34; true&#34;反序列化之前的属性。否则,在反序列化过程中将丢弃nilReason属性的信息。

2 个答案:

答案 0 :(得分:2)

问题是该属性是与它的值并排生成的,在同一个DateOfDeath类中(为了简洁我省略了一些代码):

public partial class DateOfDeath
{
    private NilReason nilReasonField;
    private bool nilReasonFieldSpecified;
    private System.DateTime valueField;

    [System.Xml.Serialization.XmlAttributeAttribute()]
    public NilReason nilReason
    {
        get/set...
    }

    [System.Xml.Serialization.XmlIgnoreAttribute()]
    public bool nilReasonSpecified
    {
        get/set...
    }

    [System.Xml.Serialization.XmlText(DataType = "date")]
    public System.DateTime Value
    {
        get/set...
    }
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.81.0")]
[System.SerializableAttribute()]
public enum NilReason
{
    noValue,
    valueUnknown,
}

因此,为了序列化一个nil元素,你必须将父元素设置为null:

DateOfDeath dod = null;
serializer.Serialize(stream, dod);

产生类似的东西:

<dateOfDeath xmlns:xsi="..." xmlns:xsd="..." xsi:nil="true" />

当然,这会导致您无法设置属性:

DateOfDeath dod = null;
dod.nilReason = noValue; // does not work with nullpointer

然而,该值呈现为xml元素的文本,如:

<dateOfDeath xmlns:xsi="..." xmlns:xsd="...">[value]</dateOfDeath>

其中[value]当然是您日期的文字表示。所以,即使你可以将值设置为null - 你不能因为你不能将复杂类型(例如Nullable&lt; DateTime&gt;)渲染为XmlText - 你仍然无法将父(&lt; DateOfDeath&gt;)元素设置为无论如何。

所以也许最接近你想要的是使值可以为空并将其呈现为XmlElement(注意添加的问号):

private System.DateTime? valueField;

[System.Xml.Serialization.XmlElement(DataType = "date", IsNullable = true)]
public System.DateTime? Value { get/set ...}

将其设为null

DateOfDeath dod = new DateOfDeath();
dod.nilReason = NilReason.noValue;
dod.nilReasonSpecified = true;
dod.Value = null;

XmlSerializer serializer = new XmlSerializer(typeof(DateOfDeath));
serializer.Serialize(stream, dod);

给你:

<?xml version="1.0" encoding="utf-8"?>
<dateOfDeath xmlns:xsi="..." xmlns:xsd="..." nilReason="noValue">
  <Value xsi:nil="true" />
</dateOfDeath>

这显然不是你想要的,但除非有一种神奇的方法将外部类成员作为属性附加到空指针或反过来,使用你的类的另一个成员作为nil值指示器,没有用给定的工具链实现这一目标的机会。

答案 1 :(得分:0)

接下来的两个功能解决了这个问题。第一个(addNilAttributes)将属性xsi:nil =“true”添加到包含属性nilReason的元素,并使元素为空。必须在序列化后应用此功能。

    static public XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";

    static public XElement addNilAttributes(XElement root)
    {       
        IEnumerable<XElement> noValueElements =
            from el in root.Descendants()
            where (string)el.Attribute("nilReason") != null
            select el;

        foreach (XElement el in noValueElements)
        {
            el.Add(new XAttribute(xsi + "nil", "true"));
            el.ReplaceNodes(null); // make element empty
        }

        IEnumerable<XElement> nilElements =
            from el in root.Descendants()
            where (string)el.Attribute("nilReason") == null && (string)el.Attribute(xsi + "nil") != null
            select el;

        nilElements.Remove();
        return root;
    }

例如,<dateOfDeath nilReason="noValue">0001-01-01</dateOfDeath>将被翻译为<dateOfDeath nilReason="noValue" xsi:nil="true"/>。但是<dateOfDeath xsi:nil="true"/>将被删除,因为如果元素为空,你总是必须指定nilReason。

第二个函数(removeNilAttributes)在反序列化之前删除xsi:nil属性。否则,在反序列化过程中,nilReason属性的值将丢失。

    static public XElement removeNilAttributes(XElement root)
    {
        root.DescendantsAndSelf().Attributes(xsi + "nil").Remove();
        return root;
    }

例如,在反序列化之前,<dateOfDeath nilReason="noValue" xsi:nil="true"/>将转换为<dateOfDeath nilReason="noValue"/>

以下示例代码如何应用这两个函数:

        DateOfDeath dateOfDeath = new DateOfDeath();
        dateOfDeath.nilReason = NilReasonType.noValue;
        dateOfDeath.nilReasonSpecified = true;

        XmlSerializer serializer = new XmlSerializer(typeof(DateOfDeath));

        StringWriter writer = new StringWriter();   
        serializer.Serialize(writer, dateOfDeath);
        String str = writer.ToString();
        Console.WriteLine(str);          
        writer.Close();

        XElement root = XElement.Parse(str);

        root = addNilAttributes(root);
        Console.WriteLine(root.ToString());

        root = removeNilAttributes(root);
        Console.WriteLine(root.ToString());

        StringReader reader = new StringReader(root.ToString());        
        DateOfDeath dateOfDeath2 = new DateOfDeath();
        dateOfDeath2 = (DateOfDeath)serializer.Deserialize(reader);

输出:

<dateOfDeath xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=" tp://www.w3.org/2001/XMLSchema" nilReason="noValue">0001-01-01</dateOfDeath>

<dateOfDeath xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=" tp://www.w3.org/2001/XMLSchema" nilReason="noValue" xsi:nil="true"/>

<dateOfDeath xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=" tp://www.w3.org/2001/XMLSchema" nilReason="noValue"/>