XmlCodeExporter和可空类型

时间:2017-02-17 10:12:15

标签: c# .net xsd code-generation

System.Xml.Serialization.XmlCodeExporter从XSD架构生成代码(代码CodeDom形式)。但它有一些怪癖。例如,一个可选元素:

<xs:element name="Something" type="xs:decimal" minOccurs="0" maxOccurs="1"/>

我希望这会生成类型为Nullable<decimal>的相应代码成员,但它实际上会创建一个decimal类型的成员,然后是一个单独的SomethingSpecified字段,应该单独切换表示空值。这可能是因为库是在引入可空类型之前的,但它导致了非常不方便的代码。

是否可以调整此代码生成,或者是否存在可在此情况下生成更好代码的替代工具?

编辑:我知道我可以修改架构并添加nillable='true',但我不想更改架构以解决代码生成的限制。

1 个答案:

答案 0 :(得分:13)

Mike Hadlow撰写的文章 Writing your own XSD.exe 提供了创建自己的xsd.exe版本的基本框架。它有以下步骤:

  1. 使用XmlSchema.Read()XmlSchemaImporter导入架构。

  2. 使用XmlCodeExporter生成.Net类型和属性以进行创建。

  3. 根据需要调整生成的类型和属性

    在这里,您需要删除生成的xxxSpecified属性并宣传其相应的&#34;真实&#34;属性可以为空。

  4. 使用CSharpCodeProvider生成最终代码。

  5. 使用此框架,并通过实验确定XmlCodeExporter使用调试器实际生成的类型,我创建了以下CustomXsdCodeGenerator

    public class CustomXsdCodeGenerator : CustomXsdCodeGeneratorBase
    {
        readonly bool promoteToNullable;
    
        public CustomXsdCodeGenerator(string Namespace, bool promoteToNullable) : base(Namespace)
        {
            this.promoteToNullable = promoteToNullable;
        }
    
        protected override void ModifyGeneratedCodeTypeDeclaration(CodeTypeDeclaration codeType, CodeNamespace codeNamespace)
        {
            RemoveSpecifiedProperties(codeNamespace, promoteToNullable);
            base.ModifyGeneratedCodeTypeDeclaration(codeType, codeNamespace);
        }
    
        private static void RemoveSpecifiedProperties(CodeNamespace codeNamespace, bool promoteToNullable)
        {
            foreach (CodeTypeDeclaration codeType in codeNamespace.Types)
            {
                RemoveSpecifiedProperties(codeType, codeNamespace, promoteToNullable);
            }
        }
    
        private static void RemoveSpecifiedProperties(CodeTypeDeclaration codeType, CodeNamespace codeNamespace, bool promoteToNullable)
        {
            var toRemove = new List<CodeTypeMember>();
    
            foreach (var property in codeType.Members.OfType<CodeMemberProperty>())
            {
                CodeMemberField backingField;
                CodeMemberProperty specifiedProperty;
                if (!property.TryGetBackingFieldAndSpecifiedProperty(codeType, out backingField, out specifiedProperty))
                    continue;
                var specifiedField = specifiedProperty.GetBackingField(codeType);
                if (specifiedField == null)
                    continue;
                toRemove.Add(specifiedProperty);
                toRemove.Add(specifiedField);
    
                if (promoteToNullable)
                {
                    // Do not do this for attributes
                    if (property.CustomAttributes.Cast<CodeAttributeDeclaration>().Any(a => a.AttributeType.BaseType == typeof(System.Xml.Serialization.XmlAttributeAttribute).FullName))
                        continue;
                    var typeRef = property.Type;
                    if (typeRef.ArrayRank > 0)
                        // An array - not a reference type.
                        continue;
    
                    // OK, two possibilities here:
                    // 1) The property might reference some system type such as DateTime or decimal
                    // 2) The property might reference some type being defined such as an enum or struct.
    
                    var type = Type.GetType(typeRef.BaseType);
                    if (type != null)
                    {
                        if (!type.IsClass)
                        {
                            if (type == typeof(Nullable<>))
                                // Already nullable
                                continue;
                            else if (!type.IsGenericTypeDefinition && (type.IsValueType || type.IsEnum) && Nullable.GetUnderlyingType(type) == null)
                            {
                                var nullableType = typeof(Nullable<>).MakeGenericType(type);
                                var newRefType = new CodeTypeReference(nullableType);
                                property.Type = newRefType;
                                backingField.Type = newRefType;
                            }
                        }
                    }
                    else
                    {
                        var generatedType = codeNamespace.FindCodeType(typeRef);
                        if (generatedType != null)
                        {
                            if (generatedType.IsStruct || generatedType.IsEnum)
                            {
                                var newRefType = new CodeTypeReference(typeof(Nullable<>).FullName, typeRef);
                                property.Type = newRefType;
                                backingField.Type = newRefType;
                            }
                        }
                    }
                }
            }
            foreach (var member in toRemove)
            {
                codeType.Members.Remove(member);
            }
        }
    }
    
    public static class CodeNamespaceExtensions
    {
        public static CodeTypeDeclaration FindCodeType(this CodeNamespace codeNamespace, CodeTypeReference reference)
        {
            if (codeNamespace == null)
                throw new ArgumentNullException();
            if (reference == null)
                return null;
            CodeTypeDeclaration foundType = null;
            foreach (CodeTypeDeclaration codeType in codeNamespace.Types)
            {
                if (codeType.Name == reference.BaseType)
                {
                    if (foundType == null)
                        foundType = codeType;
                    else if (foundType != codeType)
                    {
                        foundType = null;
                        break;
                    }
                }
            }
            return foundType;
        }
    }
    
    public static class CodeMemberPropertyExtensions
    {
        public static bool TryGetBackingFieldAndSpecifiedProperty(this CodeMemberProperty property, CodeTypeDeclaration codeType,
            out CodeMemberField backingField, out CodeMemberProperty specifiedProperty)
        {
            if (property == null)
            {
                backingField = null;
                specifiedProperty = null;
                return false;
            }
    
            if ((backingField = property.GetBackingField(codeType)) == null)
            {
                specifiedProperty = null;
                return false;
            }
    
            specifiedProperty = null;
            var specifiedName = property.Name + "Specified";
            foreach (var p in codeType.Members.OfType<CodeMemberProperty>())
            {
                if (p.Name == specifiedName)
                {
                    // Make sure the property is marked as XmlIgnore (there might be a legitimate, serializable property
                    // named xxxSpecified).
                    if (!p.CustomAttributes.Cast<CodeAttributeDeclaration>().Any(a => a.AttributeType.BaseType == typeof(System.Xml.Serialization.XmlIgnoreAttribute).FullName))
                        continue;
                    if (specifiedProperty == null)
                        specifiedProperty = p;
                    else if (specifiedProperty != p)
                    {
                        specifiedProperty = null;
                        break;
                    }
                }
            }
            if (specifiedProperty == null)
                return false;
            if (specifiedProperty.GetBackingField(codeType) == null)
                return false;
            return true;
        }
    
        public static CodeMemberField GetBackingField(this CodeMemberProperty property, CodeTypeDeclaration codeType)
        {
            if (property == null)
                return null;
    
            CodeMemberField returnedField = null;
            foreach (var statement in property.GetStatements.OfType<CodeMethodReturnStatement>())
            {
                var expression = statement.Expression as CodeFieldReferenceExpression;
                if (expression == null)
                    return null;
                if (!(expression.TargetObject is CodeThisReferenceExpression))
                    return null;
                var fieldName = expression.FieldName;
                foreach (var field in codeType.Members.OfType<CodeMemberField>())
                {
                    if (field.Name == fieldName)
                    {
                        if (returnedField == null)
                            returnedField = field;
                        else if (returnedField != field)
                            return null;
                    }
                }
            }
    
            return returnedField;
        }
    }
    
    public abstract class CustomXsdCodeGeneratorBase
    {
        // This base class adapted from http://mikehadlow.blogspot.com/2007/01/writing-your-own-xsdexe.html
    
        readonly string Namespace;
    
        public CustomXsdCodeGeneratorBase(string Namespace)
        {
            this.Namespace = Namespace;
        }
    
        public void XsdToClassTest(IEnumerable<string> xsds, TextWriter codeWriter)
        {
            XsdToClassTest(xsds.Select(xsd => (Func<TextReader>)(() => new StringReader(xsd))), codeWriter);
        }
    
        public void XsdToClassTest(IEnumerable<Func<TextReader>> xsds, TextWriter codeWriter)
        {
            var schemas = new XmlSchemas();
    
            foreach (var getReader in xsds)
            {
                using (var reader = getReader())
                {
                    var xsd = XmlSchema.Read(reader, null);
                    schemas.Add(xsd);
                }
            }
    
            schemas.Compile(null, true);
            var schemaImporter = new XmlSchemaImporter(schemas);
    
            var maps = new List<XmlTypeMapping>();
            foreach (XmlSchema xsd in schemas)
            {
                foreach (XmlSchemaType schemaType in xsd.SchemaTypes.Values)
                {
                    maps.Add(schemaImporter.ImportSchemaType(schemaType.QualifiedName));
                }
                foreach (XmlSchemaElement schemaElement in xsd.Elements.Values)
                {
                    maps.Add(schemaImporter.ImportTypeMapping(schemaElement.QualifiedName));
                }
            }
    
            // create the codedom
            var codeNamespace = new CodeNamespace(this.Namespace);
            var codeExporter = new XmlCodeExporter(codeNamespace);
            foreach (XmlTypeMapping map in maps)
            {
                codeExporter.ExportTypeMapping(map);
            }
    
            ModifyGeneratedNamespace(codeNamespace);
    
            // Check for invalid characters in identifiers
            CodeGenerator.ValidateIdentifiers(codeNamespace);
    
            // output the C# code
            var codeProvider = new CSharpCodeProvider();
            codeProvider.GenerateCodeFromNamespace(codeNamespace, codeWriter, new CodeGeneratorOptions());
        }
    
        protected virtual void ModifyGeneratedNamespace(CodeNamespace codeNamespace)
        {
            foreach (CodeTypeDeclaration codeType in codeNamespace.Types)
            {
                ModifyGeneratedCodeTypeDeclaration(codeType, codeNamespace);
            }
        }
    
        protected virtual void ModifyGeneratedCodeTypeDeclaration(CodeTypeDeclaration codeType, CodeNamespace codeNamespace)
        {
        }
    }
    

    为了测试它,我创建了以下类型:

    namespace SampleClasses
    {
        public class SimleSampleClass
        {
            [XmlElement]
            public decimal Something { get; set; }
    
            [XmlIgnore]
            public bool SomethingSpecified { get; set; }
        }
    
        [XmlRoot("RootClass")]
        public class RootClass
        {
            [XmlArray]
            [XmlArrayItem("SampleClass")]
            public List<SampleClass> SampleClasses { get; set; }
        }
    
        [XmlRoot("SampleClass")]
        public class SampleClass
        {
            [XmlAttribute]
            public long Id { get; set; }
    
            public decimal Something { get; set; }
    
            [XmlIgnore]
            public bool SomethingSpecified { get; set; }
    
            public SomeEnum SomeEnum { get; set; }
    
            [XmlIgnore]
            public bool SomeEnumSpecified { get; set; }
    
            public string SomeString { get; set; }
    
            [XmlIgnore]
            public bool SomeStringSpecified { get; set; }
    
            public decimal? SomeNullable { get; set; }
    
            [XmlIgnore]
            public bool SomeNullableSpecified { get; set; }
    
            public DateTime SomeDateTime { get; set; }
    
            [XmlIgnore]
            public bool SomeDateTimeSpecified { get; set; }
    
            // https://stackoverflow.com/questions/3280362/most-elegant-xml-serialization-of-color-structure
    
            [XmlElement(Type = typeof(XmlColor))]
            public Color MyColor { get; set; }
    
            [XmlIgnore]
            public bool MyColorSpecified { get; set; }
        }
    
        public enum SomeEnum
        {
            DefaultValue,
            FirstValue,
            SecondValue,
            ThirdValue,
        }
    
        // https://stackoverflow.com/questions/3280362/most-elegant-xml-serialization-of-color-structure
        public struct XmlColor
        {
            private Color? color_;
    
            private Color Color
            {
                get
                {
                    return color_ ?? Color.Black;
                }
                set
                {
                    color_ = value;
                }
            }
    
            public XmlColor(Color c) { color_ = c; }
    
            public Color ToColor()
            {
                return Color;
            }
    
            public void FromColor(Color c)
            {
                Color = c;
            }
    
            public static implicit operator Color(XmlColor x)
            {
                return x.ToColor();
            }
    
            public static implicit operator XmlColor(Color c)
            {
                return new XmlColor(c);
            }
    
            [XmlAttribute]
            public string Web
            {
                get { return ColorTranslator.ToHtml(Color); }
                set
                {
                    try
                    {
                        if (Alpha == 0xFF) // preserve named color value if possible
                            Color = ColorTranslator.FromHtml(value);
                        else
                            Color = Color.FromArgb(Alpha, ColorTranslator.FromHtml(value));
                    }
                    catch (Exception)
                    {
                        Color = Color.Black;
                    }
                }
            }
    
            [XmlAttribute]
            public byte Alpha
            {
                get { return Color.A; }
                set
                {
                    if (value != Color.A) // avoid hammering named color if no alpha change
                        Color = Color.FromArgb(value, Color);
                }
            }
    
            public bool ShouldSerializeAlpha() { return Alpha < 0xFF; }
        }
    }
    

    使用通用xsd.exe我从中生成了以下架构:

    <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="SimleSampleClass" nillable="true" type="SimleSampleClass" />
      <xs:complexType name="SimleSampleClass">
        <xs:sequence>
          <xs:element minOccurs="0" maxOccurs="1" name="Something" type="xs:decimal" />
        </xs:sequence>
      </xs:complexType>
      <xs:element name="RootClass" nillable="true" type="RootClass" />
      <xs:complexType name="RootClass">
        <xs:sequence>
          <xs:element minOccurs="0" maxOccurs="1" name="SampleClasses" type="ArrayOfSampleClass" />
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="ArrayOfSampleClass">
        <xs:sequence>
          <xs:element minOccurs="0" maxOccurs="unbounded" name="SampleClass" nillable="true" type="SampleClass" />
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="SampleClass">
        <xs:sequence>
          <xs:element minOccurs="0" maxOccurs="1" name="Something" type="xs:decimal" />
          <xs:element minOccurs="0" maxOccurs="1" name="SomeEnum" type="SomeEnum" />
          <xs:element minOccurs="0" maxOccurs="1" name="SomeString" type="xs:string" />
          <xs:element minOccurs="0" maxOccurs="1" name="SomeNullable" nillable="true" type="xs:decimal" />
          <xs:element minOccurs="0" maxOccurs="1" name="SomeDateTime" type="xs:dateTime" />
          <xs:element minOccurs="0" maxOccurs="1" name="MyColor" type="XmlColor" />
        </xs:sequence>
        <xs:attribute name="Id" type="xs:long" use="required" />
      </xs:complexType>
      <xs:simpleType name="SomeEnum">
        <xs:restriction base="xs:string">
          <xs:enumeration value="DefaultValue" />
          <xs:enumeration value="FirstValue" />
          <xs:enumeration value="SecondValue" />
          <xs:enumeration value="ThirdValue" />
        </xs:restriction>
      </xs:simpleType>
      <xs:complexType name="XmlColor">
        <xs:attribute name="Web" type="xs:string" />
        <xs:attribute name="Alpha" type="xs:unsignedByte" />
      </xs:complexType>
      <xs:element name="SampleClass" nillable="true" type="SampleClass" />
      <xs:element name="SomeEnum" type="SomeEnum" />
      <xs:element name="XmlColor" type="XmlColor" />
    </xs:schema>
    

    并且,使用此架构,我使用CustomXsdCodeGeneratorpromoteToNullable = true使用Namespace = "Question42295155"重新生成以下c#类:

    namespace Question42295155 {
    
    
        /// <remarks/>
        [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
        [System.SerializableAttribute()]
        [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
        public partial class SimleSampleClass {
    
            private System.Nullable<decimal> somethingField;
    
            /// <remarks/>
            public System.Nullable<decimal> Something {
                get {
                    return this.somethingField;
                }
                set {
                    this.somethingField = value;
                }
            }
        }
    
        /// <remarks/>
        [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
        [System.SerializableAttribute()]
        [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
        public partial class SampleClass {
    
            private System.Nullable<decimal> somethingField;
    
            private System.Nullable<SomeEnum> someEnumField;
    
            private string someStringField;
    
            private System.Nullable<decimal> someNullableField;
    
            private System.Nullable<System.DateTime> someDateTimeField;
    
            private XmlColor myColorField;
    
            private long idField;
    
            /// <remarks/>
            public System.Nullable<decimal> Something {
                get {
                    return this.somethingField;
                }
                set {
                    this.somethingField = value;
                }
            }
    
            /// <remarks/>
            public System.Nullable<SomeEnum> SomeEnum {
                get {
                    return this.someEnumField;
                }
                set {
                    this.someEnumField = value;
                }
            }
    
            /// <remarks/>
            public string SomeString {
                get {
                    return this.someStringField;
                }
                set {
                    this.someStringField = value;
                }
            }
    
            /// <remarks/>
            [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
            public System.Nullable<decimal> SomeNullable {
                get {
                    return this.someNullableField;
                }
                set {
                    this.someNullableField = value;
                }
            }
    
            /// <remarks/>
            public System.Nullable<System.DateTime> SomeDateTime {
                get {
                    return this.someDateTimeField;
                }
                set {
                    this.someDateTimeField = value;
                }
            }
    
            /// <remarks/>
            public XmlColor MyColor {
                get {
                    return this.myColorField;
                }
                set {
                    this.myColorField = value;
                }
            }
    
            /// <remarks/>
            [System.Xml.Serialization.XmlAttributeAttribute()]
            public long Id {
                get {
                    return this.idField;
                }
                set {
                    this.idField = value;
                }
            }
        }
    
        /// <remarks/>
        [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
        [System.SerializableAttribute()]
        [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
        public enum SomeEnum {
    
            /// <remarks/>
            DefaultValue,
    
            /// <remarks/>
            FirstValue,
    
            /// <remarks/>
            SecondValue,
    
            /// <remarks/>
            ThirdValue,
        }
    
        /// <remarks/>
        [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
        [System.SerializableAttribute()]
        [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
        public partial class XmlColor {
    
            private string webField;
    
            private byte alphaField;
    
            /// <remarks/>
            [System.Xml.Serialization.XmlAttributeAttribute()]
            public string Web {
                get {
                    return this.webField;
                }
                set {
                    this.webField = value;
                }
            }
    
            /// <remarks/>
            [System.Xml.Serialization.XmlAttributeAttribute()]
            public byte Alpha {
                get {
                    return this.alphaField;
                }
                set {
                    this.alphaField = value;
                }
            }
        }
    
        /// <remarks/>
        [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
        [System.SerializableAttribute()]
        [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
        public partial class RootClass {
    
            private SampleClass[] sampleClassesField;
    
            /// <remarks/>
            public SampleClass[] SampleClasses {
                get {
                    return this.sampleClassesField;
                }
                set {
                    this.sampleClassesField = value;
                }
            }
        }
    
        /// <remarks/>
        [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
        [System.SerializableAttribute()]
        [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
        public partial class ArrayOfSampleClass {
    
            private SampleClass[] sampleClassField;
    
            /// <remarks/>
            [System.Xml.Serialization.XmlElementAttribute("SampleClass", IsNullable=true)]
            public SampleClass[] SampleClass {
                get {
                    return this.sampleClassField;
                }
                set {
                    this.sampleClassField = value;
                }
            }
        }
    }
    

    请注意:

    • 名称Specified中没有任何属性。

    • 属性SomethingSomeEnumSomeDateTime已变为可空。

    • 已经可以public decimal? SomeNullable { get; set; }public System.Nullable<decimal> SomeNullable次往返System.Nullable<System.Nullable<decimal>>,而不是因为成为一些可怕的双重可空RootClass而失败。

    然后,我从最初的<RootClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SampleClasses> <SampleClass Id="10101"> <Something>2.718</Something> <SomeEnum>ThirdValue</SomeEnum> <SomeString>hello</SomeString> <SomeNullable>3.14</SomeNullable> <SomeDateTime>2017-02-28T00:00:00-05:00</SomeDateTime> <MyColor Web="Maroon" /> </SampleClass> </SampleClasses> </RootClass> 生成了以下XML:

    Question42295155.RootClass

    并且能够成功地将其反序列化为生成的类 var n = 2; column number you want to hide $('#myTable tr td:nth-child('+n+')').hide() 而不会丢失数据。

    注意 - 此代码经过轻微测试。如果您想提供示例模式,我可以使用示例模式重新测试。

    有关详细信息,请参阅Code Generation in the .NET Framework Using XML Schema