XSD 工具在 .NET8 中 – 第 6 部分 – XmlSchemaClassGenerator - 高级
.NET8 环境中可用的 XSD 工具实用指南。
1 在 .NET8 中进行 XML 和 XSD 相关工作
我最近在 .NET8 环境中进行了一些与 XML 和 XSD 处理相关的工作,并创建了几个 概念验证 应用程序来评估可用的工具。这些文章是我原型工作的结果。
1.1 使用/测试的工具列表
以下是使用/测试的工具
- Visual Studio 2022
- XSD.EXE(微软许可,VS2022 的一部分)
- XmlSchemaClassGenerator (开源/免费软件)
- LinqToXsdCore (开源/免费软件)
- Liquid XML Objects (商业许可)
1.2 本系列文章
由于技术原因,我将把这篇文章组织成几篇文章
- .NET8 中的 XSD 工具 – 第 1 部分 – VS2022
- .NET8 中的 XSD 工具 – 第 2 部分 – C# 验证
- .NET8 中的 XSD 工具 – 第 3 部分 – XsdExe – 简单
- .NET8 中的 XSD 工具 – 第 4 部分 – XsdExe - 高级
- .NET8 中的 XSD 工具 – 第 5 部分 – XmlSchemaClassGenerator – 简单
- .NET8 中的 XSD 工具 – 第 6 部分 – XmlSchemaClassGenerator – 高级
- .NET8 中的 XSD 工具 – 第 7 部分 – LinqToXsdCore – 简单
- .NET8 中的 XSD 工具 – 第 8 部分 – LinqToXsdCore – 高级
- .NET8 中的 XSD 工具 – 第 9 部分 – LiquidXMLObjects – 简单
- .NET8 中的 XSD 工具 – 第 10 部分 – LiquidXMLObjects – 高级
2 更多关于 XML 和 XSD 规则的理论
这里有更多关于 XML 和 XSD 规则的理论。
2.1 可选的 Xml-Element 和 Xml-Attribute
可选:XML 中不需要存在。
对于 XSD Schema 元素
可选:minOccurs="0" 属性 ->要将 Schema 元素设置为可选,请包含 minOccurs="0" 属性。
2.2 可选和非必需的 Xml-Element 和 Xml-Attribute 之间的区别
注意区别
- 可选:XML 中不需要存在。
- 非必需:不需要有值。
您可以进行任何组合
- 可选 + 非必需
- 可选 + 必需
- 非可选 + 非必需
- 非可选 + 必需
对于 XSD Schema 元素
- 可选:minOccurs="0" 属性 ->要将 Schema 元素设置为可选,请包含 minOccurs="0" 属性。
- 必需:nillable="true" 属性 ->要将 Schema 元素设置为非必需,请包含 nillable="true" 属性。
字符串数据类型默认是非必需的,但您可以强制它们成为必需的。
其他数据类型,例如布尔型、整数型、日期型、时间型等,默认都是必需的。要使这些数据类型中的一个非必需,您必须在 Schema 中为该元素设置 nillable 属性等于 true。
3 XML 和 XSD 的示例
以下是我为测试目的创建的一些示例 XML 和 XSD。
3.1 高级案例
请注意,此示例 XML/XSD 包含一个 可选且非必需的 Xml-Element。请阅读其中的注释以获取更多详细信息。
<?xml version="1.0" encoding="utf-8"?>
<!--BigCompany.xsd+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-->
<xs:schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
attributeFormDefault="unqualified"
elementFormDefault="qualified"
targetNamespace="https://markpelf.com/BigCompany.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="BigCompany">
<xs:complexType>
<xs:sequence>
<xs:element name="CompanyName" type="xs:string" />
<xs:element maxOccurs="unbounded" name="Employee">
<xs:complexType>
<xs:sequence>
<!--Name_String_NO is String NotOptional-->
<xs:element name="Name_String_NO" type="xs:string" />
<!--City_String_O is String Optional-->
<xs:element minOccurs="0" name="City_String_O" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element maxOccurs="unbounded" name="InfoData">
<xs:complexType>
<xs:sequence>
<!--Data1_Int_NO_R is Int NotOptional+Required-->
<xs:element name="Data1_Int_NO_R" type="xs:int" minOccurs="1" nillable="false" />
<!--Data2_Int_NO_NR is Int NotOptional+NotRequired-->
<xs:element name="Data2_Int_NO_NR" type="xs:int" minOccurs="1" nillable="true" />
<!--Data3_Int_O_R is Int Optional+Required-->
<xs:element name="Data3_Int_O_R" type="xs:int" minOccurs="0" nillable="false" />
<!--Data4_Int_O_NR is Int Optional+NotRequired-->
<xs:element name="Data4_Int_O_NR" type="xs:int" minOccurs="0" nillable="true" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
<?xml version="1.0" encoding="utf-8"?>
<!--BigCompanyMMM.xml+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-->
<BigCompany xmlns="https://markpelf.com/BigCompany.xsd">
<CompanyName>BigCompanyMMM</CompanyName>
<Employee>
<Name_String_NO>Mark</Name_String_NO>
<City_String_O>Belgrade</City_String_O>
</Employee>
<Employee>
<Name_String_NO>John</Name_String_NO>
</Employee>
<InfoData>
<Data1_Int_NO_R>555</Data1_Int_NO_R>
<Data2_Int_NO_NR>16</Data2_Int_NO_NR>
<Data3_Int_O_R>333</Data3_Int_O_R>
<Data4_Int_O_NR>17</Data4_Int_O_NR>
</InfoData>
<InfoData>
<Data1_Int_NO_R>123</Data1_Int_NO_R>
<Data2_Int_NO_NR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
<Data3_Int_O_R>15</Data3_Int_O_R>
<Data4_Int_O_NR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
</InfoData>
<InfoData>
<Data1_Int_NO_R>777</Data1_Int_NO_R>
<Data2_Int_NO_NR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
</InfoData>
</BigCompany>
4 使用 XmlSchemaClassGenerator 工具创建 C# 类
本文档重点介绍如何使用 XmlSchemaClassGenerator 工具从 XSD 文件生成 C# 类。
以下是该工具的基本信息。
Tool name============================
XmlSchemaClassGenerator
License============================
Open Source/Freeware
Where to get it============================
https://github.com/mganss/XmlSchemaClassGenerator?tab=readme-ov-file
Version============================
Windows PowerShell > ./XmlSchemaClassGenerator.Console.exe
Usage: xscgen [OPTIONS]+ xsdFile...
Generate C# classes from XML Schema files.
Version 2.1.1162.0
Help============================
Windows PowerShell > ./XmlSchemaClassGenerator.Console.exe
Usage: xscgen [OPTIONS]+ xsdFile...
Generate C# classes from XML Schema files.
Version 2.1.1162.0
xsdFiles may contain globs, e.g. "content\{schema,xsd}\**\*.xsd", and URLs.
Append - to option to disable it, e.g. --interface-.
Options:
-h, --help show this message and exit
-n, --namespace=VALUE map an XML namespace to a C# namespace
Separate XML namespace and C# namespace by '='.
A single value (no '=') is taken as the C#
namespace the empty XML namespace is mapped to.
One option must be given for each namespace to
be mapped.
A file name may be given by appending a pipe
sign (|) followed by a file name (like schema.
xsd) to the XML namespace.
If no mapping is found for an XML namespace, a
name is generated automatically (may fail).
--nf, --namespaceFile=VALUE
file containing mappings from XML namespaces to C#
namespaces
The line format is one mapping per line: XML
namespace = C# namespace [optional file name].
Lines starting with # and empty lines are
ignored.
--tns, --typeNameSubstitute=VALUE
substitute a generated type/member name
Separate type/member name and substitute name by
'='.
Prefix type/member name with an appropriate kind
ID as documented at: https://t.ly/HHEI.
Prefix with 'A:' to substitute any type/member.
--tnsf, --typeNameSubstituteFile=VALUE
file containing generated type/member name
substitute mappings
The line format is one mapping per line:
prefixed type/member name = substitute name.
Lines starting with # and empty lines are
ignored.
-o, --output=FOLDER the FOLDER to write the resulting .cs files to
-d, --datetime-offset map xs:datetime and derived types to System.
DateTimeOffset instead of System.DateTime
-i, --integer=TYPE map xs:integer and derived types to TYPE instead
of automatic approximation
TYPE can be i[nt], l[ong], or d[ecimal]
--fb, --fallback, --use-integer-type-as-fallback
use integer type specified via -i only if no type
can be deduced
-e, --edb, --enable-data-binding
enable INotifyPropertyChanged data binding
-r, --order emit order for all class members stored as XML
element
-c, --pcl PCL compatible output
-p, --prefix=PREFIX the PREFIX to prepend to auto-generated namespace
names
-v, --verbose print generated file names on stdout
-0, --nullable generate nullable adapter properties for optional
elements/attributes w/o default values
-f, --ef generate Entity Framework Code First compatible
classes
-t, --interface generate interfaces for groups and attribute
groups (default is enabled)
-a, --pascal use Pascal case for class and property names (
default is enabled)
--av, --assemblyVisible
use the internal visibility modifier (default is
false)
-u, --enableUpaCheck should XmlSchemaSet check for Unique Particle
Attribution (UPA) (default is enabled)
--ct, --collectionType=VALUE
collection type to use (default is System.
Collections.ObjectModel.Collection`1)
--cit, --collectionImplementationType=VALUE
the default collection type implementation to use (
default is null)
--csm, --collectionSettersMode=Private, Public, PublicWithoutConstructorInitialization, Init, InitWithoutConstructorInitialization
generate a private, public, or init-only setter
with or without backing field initialization for
collections
(default is Private; can be: Private, Public,
PublicWithoutConstructorInitialization, Init,
InitWithoutConstructorInitialization)
--ctro, --codeTypeReferenceOptions=GlobalReference, GenericTypeParameter
the default CodeTypeReferenceOptions Flags to use (
default is unset; can be: GlobalReference,
GenericTypeParameter)
--tvpn, --textValuePropertyName=VALUE
the name of the property that holds the text value
of an element (default is Value)
--dst, --debuggerStepThrough
generate DebuggerStepThroughAttribute (default is
enabled)
--dc, --disableComments
do not include comments from xsd
--nu, --noUnderscore do not generate underscore in private member name (
default is false)
--da, --description generate DescriptionAttribute (default is true)
--cc, --complexTypesForCollections
generate complex types for collections (default is
true)
-s, --useShouldSerialize use ShouldSerialize pattern instead of Specified
pattern (default is false)
--sf, --separateFiles generate a separate file for each class (default
is false)
--nh, --namespaceHierarchy
generate a separate folder for namespace hierarchy.
Implies "separateFiles" if true (default is
false)
--sg, --separateSubstitutes
generate a separate property for each element of a
substitution group (default is false)
--dnfin, --doNotForceIsNullable
do not force generator to emit IsNullable = true
in XmlElement annotation for nillable elements
when element is nullable (minOccurs < 1 or
parent element is choice) (default is false)
--cn, --compactTypeNames
use type names without namespace qualifier for
types in the using list (default is false)
--cl, --commentLanguages=VALUE
comment languages to use (default is en; supported
are en, de)
--un, --uniqueTypeNames
generate type names that are unique across
namespaces (default is false)
--gc, --generatedCodeAttribute
add version information to GeneratedCodeAttribute (
default is true)
--nc, --netCore generate .NET Core specific code that might not
work with .NET Framework (default is false)
--nr, --nullableReferenceAttributes
generate attributes for nullable reference types (
default is false)
--ar, --useArrayItemAttribute
use ArrayItemAttribute for sequences with single
elements (default is true)
--es, --enumAsString Use string instead of enum for enumeration
--dmb, --disableMergeRestrictionsWithBase
Disable merging of simple type restrictions with
base type restrictions
--ca, --commandArgs generate a comment with the exact command line
arguments that were used to generate the source
code (default is true)
--uc, --unionCommonType
generate a common type for unions if possible (
default is false)
--ec, --serializeEmptyCollections
serialize empty collections (default is false)
--dtd, --allowDtdParse allows dtd parse (default is false)
--ns, --namingScheme=VALUE
use the specified naming scheme for class and
property names (default is Pascal; can be:
Direct, Pascal, Legacy)
=================================================
Usage Examples===================
Instructions to generate C# class
Windows PowerShell> ./XmlSchemaClassGenerator.Console.exe --namespace=Example2SmallCompany --nullable --netCore --nullableReferenceAttributes --namingScheme=Direct SmallCompany.xsd
Windows PowerShell> ./XmlSchemaClassGenerator.Console.exe --namespace=Example2BigCompany --nullable --netCore --nullableReferenceAttributes --namingScheme=Direct BigCompany.xsd
============================
5 生成的 C# 类
这是上面工具根据上面提供的 XSD BigCompany.xsd 生成的 C# 代码。
这是类的完整代码。
//BigCompany_ver2_2.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
// This code was generated by XmlSchemaClassGenerator version 2.1.1162.0 using the following command:
// XmlSchemaClassGenerator.Console --namespace=Example2BigCompany --nullable --netCore --nullableReferenceAttributes --namingScheme=Direct BigCompany.xsd
namespace Example2BigCompany
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("XmlSchemaClassGenerator", "2.1.1162.0")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute("BigCompany", Namespace="https://markpelf.com/BigCompany.xsd", AnonymousType=true)]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute("BigCompany", Namespace="https://markpelf.com/BigCompany.xsd")]
public partial class BigCompany
{
[System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
[System.Xml.Serialization.XmlElementAttribute("CompanyName")]
public string CompanyName { get; set; }
[System.Xml.Serialization.XmlIgnoreAttribute()]
private System.Collections.ObjectModel.Collection<BigCompanyEmployee> _employee;
[System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
[System.Xml.Serialization.XmlElementAttribute("Employee")]
public System.Collections.ObjectModel.Collection<BigCompanyEmployee> Employee
{
get
{
return _employee;
}
private set
{
_employee = value;
}
}
/// <summary>
/// <para xml:lang="en">Initializes a new instance of the <see cref="BigCompany" /> class.</para>
/// </summary>
public BigCompany()
{
this._employee = new System.Collections.ObjectModel.Collection<BigCompanyEmployee>();
this._infoData = new System.Collections.ObjectModel.Collection<BigCompanyInfoData>();
}
[System.Xml.Serialization.XmlIgnoreAttribute()]
private System.Collections.ObjectModel.Collection<BigCompanyInfoData> _infoData;
[System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
[System.Xml.Serialization.XmlElementAttribute("InfoData")]
public System.Collections.ObjectModel.Collection<BigCompanyInfoData> InfoData
{
get
{
return _infoData;
}
private set
{
_infoData = value;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("XmlSchemaClassGenerator", "2.1.1162.0")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute("BigCompanyEmployee", Namespace="https://markpelf.com/BigCompany.xsd", AnonymousType=true)]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class BigCompanyEmployee
{
[System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
[System.Xml.Serialization.XmlElementAttribute("Name_String_NO")]
public string Name_String_NO { get; set; }
[System.Diagnostics.CodeAnalysis.AllowNullAttribute()]
[System.Diagnostics.CodeAnalysis.MaybeNullAttribute()]
[System.Xml.Serialization.XmlElementAttribute("City_String_O")]
public string City_String_O { get; set; }
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("XmlSchemaClassGenerator", "2.1.1162.0")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute("BigCompanyInfoData", Namespace="https://markpelf.com/BigCompany.xsd", AnonymousType=true)]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class BigCompanyInfoData
{
[System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
[System.Xml.Serialization.XmlElementAttribute("Data1_Int_NO_R")]
public int Data1_Int_NO_R { get; set; }
[System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)]
[System.Xml.Serialization.XmlElementAttribute("Data2_Int_NO_NR", IsNullable=true)]
public System.Nullable<int> Data2_Int_NO_NR { get; set; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Xml.Serialization.XmlElementAttribute("Data3_Int_O_R")]
public int Data3_Int_O_RValue { get; set; }
/// <summary>
/// <para xml:lang="en">Gets or sets a value indicating whether the Data3_Int_O_R property is specified.</para>
/// </summary>
[System.Xml.Serialization.XmlIgnoreAttribute()]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public bool Data3_Int_O_RValueSpecified { get; set; }
[System.Xml.Serialization.XmlIgnoreAttribute()]
public System.Nullable<int> Data3_Int_O_R
{
get
{
if (this.Data3_Int_O_RValueSpecified)
{
return this.Data3_Int_O_RValue;
}
else
{
return null;
}
}
set
{
this.Data3_Int_O_RValue = value.GetValueOrDefault();
this.Data3_Int_O_RValueSpecified = value.HasValue;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Xml.Serialization.XmlElementAttribute("Data4_Int_O_NR", IsNullable=true)]
public System.Nullable<int> Data4_Int_O_NRValue { get; set; }
/// <summary>
/// <para xml:lang="en">Gets or sets a value indicating whether the Data4_Int_O_NR property is specified.</para>
/// </summary>
[System.Xml.Serialization.XmlIgnoreAttribute()]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public bool Data4_Int_O_NRValueSpecified { get; set; }
[System.Xml.Serialization.XmlIgnoreAttribute()]
public System.Nullable<int> Data4_Int_O_NR
{
get
{
if (this.Data4_Int_O_NRValueSpecified)
{
return this.Data4_Int_O_NRValue;
}
else
{
return null;
}
}
set
{
this.Data4_Int_O_NRValue = value.GetValueOrDefault();
this.Data4_Int_O_NRValueSpecified = value.HasValue;
}
}
}
}
这是类的类图。
6 可选 Xml-Elements 的两种 C# API 样式
在生成的 C# 代码中,有两种方法/样式可以标记可选 Xml-Element 的存在。
- 第一种是 bool_flag_style – 使用布尔标志来指示可选 Xml-Element 的存在,flag=false 表示 Xml-Element 不存在。
例如,对于一个可选的 Xml-Element ElemA,如果存在,其值为整数,您将在生成的 C# 代码中得到两个变量“bool ElemA_flag, int ElemA_value”。您需要首先检查标志 ElemA_flag 来判断元素 ElemA 是否存在;如果为 true,然后获取 ElemA_value 的值。如果您不先检查标志 ElemA_flag,而是直接获取 ElemA_value 的值,您可能会得到默认的整数值零 (0),这样您就无法知道这只是一个始终存在的 C# 变量的默认值,但 Xml-Element 并不存在,还是该元素存在并且实际值为零 (0)。 - 第二种是 nullable_type_style – 使用可空类型来指示 Xml-Element 的存在,value=null 表示 Xml-Element 不存在。
例如,对于一个可选的 Xml-Element ElemA,如果存在,其值为整数,您将在生成的 C# 代码中得到一个变量“int? ElemA_nullableValue”。您需要首先检查 ElemA_nullableValue 是否不为 null 来判断元素 ElemA 是否存在;如果不为 null,则表示元素存在,然后获取 ElemA_nullableValue 的整数值。
7 示例 C# 应用程序
这是一个使用上面生成的 C# 类来加载和处理上面提供的 XML BigCompanyMMM.xml 的示例 C# 代码。
public static void ProcessVer2_Process2(
string? filePath,
Microsoft.Extensions.Logging.ILogger? logger)
{
try
{
logger?.LogInformation(
"+++ProcessVer2_Process2-Start++++++++++++++++++");
logger?.LogInformation("filePath:" + filePath);
XmlSerializer ser = new XmlSerializer(typeof(Example2BigCompany.BigCompany));
TextReader textReader = File.OpenText(filePath ?? String.Empty);
Example2BigCompany.BigCompany? xmlObject =
ser.Deserialize(textReader) as Example2BigCompany.BigCompany;
if (xmlObject != null)
{
logger?.LogInformation("CompanyName:" + xmlObject.CompanyName);
foreach (Example2BigCompany.BigCompanyEmployee item in xmlObject.Employee)
{
logger?.LogInformation("------------");
logger?.LogInformation("Name_String_NO:" + item.Name_String_NO);
logger?.LogInformation("City_String_O:" + (item.City_String_O ?? "null"));
}
foreach (Example2BigCompany.BigCompanyInfoData item in xmlObject.InfoData)
{
logger?.LogInformation("------------");
logger?.LogInformation("Data1_Int_NO_R:" + item.Data1_Int_NO_R.ToString());
logger?.LogInformation("Data2_Int_NO_NR:" + (item.Data2_Int_NO_NR?.ToString() ?? "null"));
logger?.LogInformation("Data3_Int_O_RValue:" + item.Data3_Int_O_RValue.ToString());
logger?.LogInformation("Data3_Int_O_RValueSpecified:" + item.Data3_Int_O_RValueSpecified.ToString());
logger?.LogInformation("Data3_Int_O_R:" + (item.Data3_Int_O_R?.ToString() ?? "null"));
logger?.LogInformation("Data4_Int_O_NRValue:" + (item.Data4_Int_O_NRValue?.ToString() ?? "null"));
logger?.LogInformation("Data4_Int_O_NRValueSpecified:" + item.Data4_Int_O_NRValueSpecified.ToString());
logger?.LogInformation("Data4_Int_O_NR:" + (item.Data4_Int_O_NR?.ToString() ?? "null"));
}
}
else
{
logger?.LogError("xmlObject == null");
}
logger?.LogInformation(
"+++ProcessVer2_Process2-End++++++++++++++++++");
}
catch (Exception ex)
{
string methodName =
$"Type: {System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType?.FullName}, " +
$"Method: ProcessVer2_Process2; ";
logger?.LogError(ex, methodName);
}
}
这是执行日志。
+++ProcessVer2_Process2-Start++++++++++++++++++
filePath:C:\TmpXSD\XsdExample_Ver2\Example01\bin\Debug\net8.0\XmlFiles\BigCompanyMMM.xml
CompanyName:BigCompanyMMM
------------
Name_String_NO:Mark
City_String_O:Belgrade
------------
Name_String_NO:John
City_String_O:null
------------
Data1_Int_NO_R:555
Data2_Int_NO_NR:16
Data3_Int_O_RValue:333
Data3_Int_O_RValueSpecified:True
Data3_Int_O_R:333
Data4_Int_O_NRValue:17
Data4_Int_O_NRValueSpecified:True
Data4_Int_O_NR:17
------------
Data1_Int_NO_R:123
Data2_Int_NO_NR:null
Data3_Int_O_RValue:15
Data3_Int_O_RValueSpecified:True
Data3_Int_O_R:15
Data4_Int_O_NRValue:null
Data4_Int_O_NRValueSpecified:True
Data4_Int_O_NR:null
------------
Data1_Int_NO_R:777
Data2_Int_NO_NR:null
Data3_Int_O_RValue:0
Data3_Int_O_RValueSpecified:False
Data3_Int_O_R:null
Data4_Int_O_NRValue:null
Data4_Int_O_NRValueSpecified:False
Data4_Int_O_NR:null
+++ProcessVer2_Process2-End++++++++++++++++++
8 分析
这里发生的事情并不容易理解,但看起来这个工具正在尝试同时使用“bool_flag_style”和“nullable_type_style”这两种方法/样式来标记生成 C# 代码中可选 Xml-Element 的存在。用户可以选择他们喜欢的 API 方法/样式。如果使用 API 样式/方法“bool_flag_style”,它还会使用一个可空方法来指示“nill”值。如果使用 API 样式/方法“nullable_type_style”,则无法指示“nill”值。
- Data1_Int_NO_R - 是 int 类型,并且始终有值。
- Data2_Int_NO_NR - 是 int? 类型,其含义是:
1) null – 存在但“nill”(即使元素存在但值为“nill”,我们这里也得到 null)。
2) int – 存在并有值。 - Xml-Element Data3_Int_O_R 由 3 个 C# 变量表示:
Data3_Int_O_RValueSpecified – 是一个布尔类型(“bool_flag_style” API)。
Data3_Int_O_RValue – 是 int 类型(“bool_flag_style” API)。
Data3_Int_O_R – 是 int? 类型(“nullable_type_style” API)。- 使用“bool_flag_style” API:
1) Data3_Int_O_RValueSpecified – 用于指示元素是否存在或不存在的标志。
2) Data3_Int_O_RValue – 如果上述标志为 true,则这是元素的 int 值。 - 使用“nullable_type_style” API:
3) Data3_Int_O_R - – 是 int? 类型,其含义是:
a) null - 表示它不存在。
b) int – 存在并有值。
- 使用“bool_flag_style” API:
- Xml-Element Data4_Int_O_NR 由 3 个 C# 变量表示:
Data4_Int_O_NRValueSpecified – 是一个布尔类型(“bool_flag_style” API)。
Data4_Int_O_NRValue – 是 int? 类型(“bool_flag_style” API)。
Data4_Int_O_NR – 是 int? 类型(“nullable_type_style” API)。- 使用“bool_flag_style” API:
1) Data4_Int_O_NRValueSpecified – 用于指示元素是否存在或不存在的标志。
2) Data4_Int_O_NRValue – 如果上述标志为 true,则这是元素的值。如果值为 null,则表示该元素是“nill”,或者如果它不为 null,则表示该元素的 int 值。这里“null”表示值为“nill”,这可能会令人困惑,但事实就是如此。 - 使用“nullable_type_style” API:
3) Data4_Int_O_NR - 是 int? 类型,其含义是:
a) null – 表示它要么不存在,要么存在但“nill”。我们无法知道这两种情况中的哪一种发生了。严格来说,这是此方法/样式在生成代码方面的缺陷。在某些情况下,可能需要这种区分。
b) int – 存在并有值。
- 使用“bool_flag_style” API:
有趣的是,当使用旧的 API 样式/方法“bool_flag_style”时,该工具能够区分所有三种状态:“不存在”、“存在-nill”、“存在-int”。仔细看,您会发现它可以做到。在上面的情况中,值(Data4_Int_O_NRValueSpecified, Data4_Int_O_NRValue)=(true, null) 表示“存在-nill”。
我没有看到使用现代 API 样式/方法“nullable_type_style”可以实现同样的效果。如果 Data4_Int_O_NR 为 null,我们无法知道它是“不存在”还是“存在-nill”。
9 结论
这个 XmlSchemaClassGenerator 工具非常有趣,并且是免费提供的。我测试生成的代码运行良好。对于那些想要使用nullable_type_style API(通常是处理可选 Xml-Elements 的更现代的方法)的用户来说,它可能非常有用。
10 参考文献
[1] XML 架构
https://w3schools.org.cn/xml/xml_schema.asp
[2] 可选和非必需的区别
https://www.infopathdev.com/blogs/greg/archive/2004/09/16/The-Difference-Between-Optional-and-Not-Required.aspx
[3] nillable 和 minOccurs XSD 元素属性
https://stackoverflow.com/questions/1903062/nillable-and-minoccurs-xsd-element-attributes
[21] .NET8 中的 XSD 工具 – 第 1 部分 – VS2022
https://codeproject.org.cn/Articles/5388391/XSD-Tools-in-NET8-Part1-VS2022
[22] XSD 工具在 .NET8 中 – 第 2 部分 – C# 验证
https://codeproject.org.cn/Articles/5388393/XSD-Tools-in-NET8-Part2-Csharp-validation
[23] XSD 工具在 .NET8 中 – 第 3 部分 – XsdExe – 简单
https://codeproject.org.cn/Articles/5388396/XSD-Tools-in-NET8-Part3-XsdExe-Simple
[24] XSD 工具在 .NET8 中 – 第 4 部分 – XsdExe – 高级
https://codeproject.org.cn/Articles/5388483/XSD-Tools-in-NET8-Part4-XsdExe-Advanced
[25] XSD 工具在 .NET8 中 – 第 5 部分 – XmlSchemaClassGenerator – 简单
https://codeproject.org.cn/Articles/5388548/XSD-Tools-in-NET8-Part5-XmlSchemaClassGenerator-Si
[26] XSD 工具在 .NET8 中 – 第 6 部分 – XmlSchemaClassGenerator – 高级
https://codeproject.org.cn/Articles/5388549/XSD-Tools-in-NET8-Part6-XmlSchemaClassGenerator-Ad
[27] XSD 工具在 .NET8 中 – 第 7 部分 – LinqToXsdCore – 简单
https://codeproject.org.cn/Articles/5388628/XSD-Tools-in-NET8-Part7-LinqToXsdCore-Simple
[28] XSD 工具在 .NET8 中 – 第 8 部分 – LinqToXsdCore – 高级
https://codeproject.org.cn/Articles/5388629/XSD-Tools-in-NET8-Part8-LinqToXsdCore-Advanced
[29] XSD 工具在 .NET8 中 – 第 9 部分 – LiquidXMLObjects – 简单
https://codeproject.org.cn/Articles/5388683/XSD-Tools-in-NET8-Part9-LiquidXMLObjects-Simple
[30] XSD 工具在 .NET8 中 – 第 10 部分 – LiquidXMLObjects – 高级
https://codeproject.org.cn/Articles/5388684/XSD-Tools-in-NET8-Part10-LiquidXMLObjects-Advanced