将可空类型序列化为可选的非null元素

6
我有一个 xsd 模式,其中有一个类型为 int 的可选元素(minOccurs=0maxOccurs=1),该元素未定义为 nillable。在数据模型中,我想将其映射到 .NET 类型 Nullable<int> 的字段上,其中 null 值应对应于在 xml 中省略该元素。
然而,使用 XmlSerializer 时,似乎我必须在数据模型中声明一个可空的字段,并使用 [XmlElement IsNullable=true]。如果我设置 IsNullable=false,则会抛出异常 "IsNullable may not be set to 'false' for a Nullable type." IsNullable may not be set to 'false' for a Nullable type. Consider using 'System.Int32' type or removing the IsNullable property from the XmlElement attribute. "" 但是,如果我理解正确,设置 IsNullable=true (或忽略该属性)隐含地将元素设置为 nillable,并从而更改模式。
这是基于模式的设计,因此我不能只是向模式中的元素添加“nillable”。
如何将可空的 .NET 类型映射到非 nillable 的 xml 元素?
(我知道可以通过在数据模型中使用 XxxSpecified 属性来在序列化为 xml 时省略 nil 元素,但据我所知,这种方法仍需要向 xsd 模式中添加 nillable。)
编辑:感谢评论,我现在更好地理解了问题。实际上有两个不同的问题:
1. 如果模式元素是 non-nillable(即使它是可选的),则像 xsd.exe 这样的模式到代码生成器会在生成的模型中创建一个 non-nullable 类型。我可以用任何已知的代码生成器覆盖这一点吗,以便在生成的代码中获得可空类型?
2. XmlSerializer 要求数据模型中的可空类型具有 [XmlElement IsNullable=true],这意味着该模型隐含地向模式添加“nillable”。我能否避免这种情况?

问题出在哪里?你是从代码生成xsd,还是反过来,或者两者都不是? - Anton Tykhyy
我正在从XSD生成代码。我的问题是:我无法弄清楚在C#中是否可以映射可空类型到xml中的非空元素。由于这是基于模式优先的设计,我不能仅仅向模式中的元素添加“nillable”。 - JacquesB
哦,我没有表达清楚我的问题。你是使用自动工具XSD.exe从XSD生成代码,还是手动编写代码?如果是前者,你唯一的解决方案是添加某种预处理或后处理步骤,因为XSD.exe不支持你想要的功能。如果是后者,你的选择就更多了。 - Anton Tykhyy
实际上,我正在使用Xsd2Code生成代码,因此我可以在某种程度上修改代码的生成方式。但是,即使使用手工编写的代码,我也不确定如何解决这个问题,而不引入nillable元素。 - JacquesB
你有没有考虑使用FormatterServices对象和System.Reflection命名空间编写自己的序列化器?我有一个例子,但它并不简单。我花了很多年时间解决这个问题,但那是为了自己。如果你有具体的要求,你需要描述清楚。 - Nathan M
2个回答

6
我前段时间也遇到过这个问题,我的解决方法是引入了一个处理序列化逻辑的额外属性。
  1. Firstly you mark your original property with [XmlIgnore] attribute to exlude it from serialization/deserialization.

    // Mark your original property from serialization/deserialization logic
    [XmlIgnore]
    public int? OriginalProperty { get; set; }
    
  2. Then add the additional property and mark it with [XmlElement("YourElementName")] attribute to handle serialization logic.

    // Move serialization logic to additional string property
    [XmlElement("OriginalPropertyXmlElement")]
    public string OriginalPropertyAsString
    {
        get
        {
            //...
        }
        set
        {
            //...
        }
    }
    
  3. On deserialization it will:

    set
    {
        // Check the field existence in xml
        if (string.IsNullOrEmpty(value))
        {
            // Set the original property to null
            this.OriginalProperty = default(int?);
        }
        else
        {
            // Get value from xml field
            this.OriginalProperty = int.Parse(value);
        }
    }
    
  4. On serialization:

    get
    {
        if (this.OriginalProperty.HasValue)
        {
            // Serialize it
            return this.OriginalProperty.ToString();
        }
        else
        {
            // Don't serialize it
            return null;
        }
    }
    
  5. The example could look like:

    [XmlRoot("scene")]
    public class Scene
    {
        [XmlIgnore]
        public int? ParentId { get; set; }
    
        [XmlElement("parent_id")]
        public string ParentIdAsString
        {
            get
            {
                return this.ParentId.HasValue ? this.ParentId.ToString() : null;
            }
    
            set
            {
                this.ParentId = !string.IsNullOrEmpty(value) ? int.Parse(value) : default(int?);
            }
        }
    }
    

这很简单,你不需要编写自己的序列化程序,也不需要通过hack xsd等操作来实现。


3

我不确定XxxSpecified的作用,但您可以使用ShouldSerializeXxx方法。无论属性类型是否可为空,这些方法都能正常工作。以下代码应该可以实现:

public int? Property { get ; set ; }

// this member is used for XML serialization
public bool ShouldSerializeProperty () { return Property.HasValue ; }

关于从XSD架构中生成代码,如果你使用的是微软的xsd.exe工具,最好的方法似乎是使用例如Mono.Cecil这样的后处理程序来修改感兴趣属性的类型以及插入任何额外的序列化相关成员,如ShouldSerializeXxx。在“修正”了nillable元素声明的模式上运行xsd.exe的XSLT预处理步骤可以实现第一个目标,但并不包括第二个目标。xsd.exe不足以满足你的需求。你也可以尝试将这个功能添加到xsd2code中,因为它是开源的。

如果我理解正确的话,这相当于在属性上有一个[XmlElement IsNullable=true]属性,从而允许在生成和消耗的xml中有nil元素,这反过来意味着我偏离了xsd模式。但由于我可以避免输出nil元素,这可能是一个可接受的解决方案。 - JacquesB
不,它们并不等价。[XmlElement(IsNullable=true)]对于类型已经是Nullable<>的属性没有影响,因为它已经被属性的类型所隐含。在这种情况下,你甚至不能设置IsNullable=falseXmlSerializer会抛出异常。你是正确的,这个声明允许序列化器消耗一个空的XML元素,但如果XML符合你的XSD模式,就不应该有任何问题,所以我不明白问题在哪里。 - Anton Tykhyy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接