65.9K
CodeProject 正在变化。 阅读更多。
Home

NET8 中的 XSD 工具 – 第 8 部分 – LinqToXsdCore - 高级

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2024年9月27日

CPOL

6分钟阅读

viewsIcon

2425

downloadIcon

145

.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 – 高级

 

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 使用 LinqToXsdCore 工具创建 C# 类

本文将重点介绍如何使用 LinqToXsdCore 工具从 XSD 文件生成 C# 类。
以下是该工具的基本信息。

 

Tool name:  ============================
XSD LinqToXsdCore

License============================ 
Open Source/Freeware

Where to get it============================
It is NuGet package of type DotnetTool. You need to install it in a special way.

Install Instructions====================================
Create somewhere directory Tool>
I had some additional custom sources. Just using --ignore-failed-sources wasn't enough. I went into Visual Studio -> Tools -> Options -> NuGet Package Manager -> Package Sources and unchecked the extra package sources so only nuget.org and Microsoft Visual Studio Offline Packages was left checked. After doing that I was able to install the tool , and then I re-enabled my custom package sources.

PowerShell, as Administrator

> dotnet new tool-manifest
> dotnet tool install LinqToXsdCore --ignore-failed-sources
You can invoke the tool from this directory using the following commands: 'dotnet tool run LinqToXsd' or 'dotnet LinqToXsd'.
Tool 'linqtoxsdcore' (version '3.4.7') was successfully installed. Entry is added to the manifest file _________\Tool\.config\dotnet-tools.json.


Version============================
Windows PowerShell > ./LinqToXsd.exe
LinqToXsd 3.4.7
Copyright (C) Muhammad Miftah, GitHub Contributors (2019-2022) & Microsoft Corporation (2008-2011)

Help============================
Windows PowerShell >  ./LinqToXsd.exe gen
LinqToXsd 3.4.7
Copyright (C) Muhammad Miftah, GitHub Contributors (2019-2022) & Microsoft Corporation (2008-2011)

ERROR(S):
  A required value not bound to option name is missing.

  -c, --Config                    (string) Specify the file or folder path to one or more configuration file(s).
                                  Multiple configuration files are merged as one. Incompatible with -AutoConfig

  -e, --EnableServiceReference    (bool) Imports the 'System.Xml.Serialization' namespace into the generated code.

  -a, --AutoConfig                (bool) Specify this with a folder containing XSDs and accompanying configuration
                                  files. This argument associate a configuration file with an XSD when one follows the
                                  naming convention: 'schema.xsd' + 'schema.xsd.config' - this is the default convention
                                  used by the 'config -e' verb when you specify a folder. Use this parameter to
                                  associate an XSD with its own configuration settings to prevent those settings being
                                  overriden or merged as the -Config argument would do. Only accepts folder paths.
                                  Incompatible with -Config. Will only generate code for XSDs that have an accompanying
                                  .config file. If no output is generated, run the 'config' verb on the folder first.

  -o, --Output                    (string) Output file name or folder. When specifying multiple input XSDs or input
                                  folders, and this value is a file, all output is merged into a single file. If this
                                  value is a folder, multiple output files are output to this folder.

  --help                          Display this help screen.

  --version                       Display version information.

  value pos. 1                    Required. (string[]) One or more schema files or folders containing schema files.
                                  Separate multiple files using a comma (,). If folders are given, then the files
                                  referenced in xs:include or xs:import directives are not imported twice. Usage:
                                  'LinqToXsd [verb] <file1.xsd>,<file2.xsd>' or 'LinqToXsd [verb] <folder1>,<folder2>'.
                                  You can also include folder and file paths in the same invocation: 'LinqToXsd [verb]
                                  <file1.xsd>,<folder1>'
====================================================    
Usage Examples===================                          
Instructions to generate C# class 
Windows PowerShell > dotnet LinqToXsd gen SmallCompany.xsd 
Windows PowerShell > dotnet LinqToXsd gen BigCompany.xsd 
====================================

 

5 生成的 C# 类

这是上面工具根据上面提供的 XSD BigCompany.xsd 生成的 C# 代码。

该工具生成的类的完整代码很长(835 行),因此我在这里只展示文件的开头部分。

//BigCompany_ver3_3.cs, 835 lines
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace https.markpelf.com.BigCompany.xsd {
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Diagnostics;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Linq;
    using Xml.Schema.Linq;
    
    
    /// <summary>
    /// <para>
    /// Regular expression: (CompanyName, Employee+, InfoData+)
    /// </para>
    /// </summary>
    public partial class BigCompany : XTypedElement, IXMetaData {
        
        public void Save(string xmlFile) {
            XTypedServices.Save(xmlFile, Untyped);
        }
        
        public void Save(System.IO.TextWriter tw) {
            XTypedServices.Save(tw, Untyped);
        }
        
        public void Save(System.Xml.XmlWriter xmlWriter) {
            XTypedServices.Save(xmlWriter, Untyped);
        }
        
        public static BigCompany Load(string xmlFile) {
            return XTypedServices.Load<BigCompany>(xmlFile);
        }
        
        public static BigCompany Load(System.IO.TextReader xmlFile) {
            return XTypedServices.Load<BigCompany>(xmlFile);
        }
        
        public static BigCompany Parse(string xml) {
            return XTypedServices.Parse<BigCompany>(xml);
        }
        
        public static explicit operator BigCompany(XElement xe) { return XTypedServices.ToXTypedElement<BigCompany>(xe,LinqToXsdTypeManager.Instance as ILinqToXsdTypeManager); }
        
        public override XTypedElement Clone() {
            return XTypedServices.CloneXTypedElement<BigCompany>(this);
        }
        
        /// <summary>
        /// <para>
        /// Regular expression: (CompanyName, Employee+, InfoData+)
        /// </para>
        /// </summary>
        public BigCompany() {
        }
        
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        protected internal static readonly System.Xml.Linq.XName CompanyNameXName = System.Xml.Linq.XName.Get("CompanyName", "https://markpelf.com/BigCompany.xsd");
        
//
// file shorten, full file 835 lines
//
   

 

这是类的类图。

 

生成的类需要安装非常特定的依赖项来支持。

 

 

6 可选 Xml-Elements 的两种 C# API 样式

在生成的 C# 代码中,有两种方法/样式可以标记可选 Xml-Element 的存在。

  1. 第一种是 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)。
  2. 第二种是 nullable_type_style – 使用可空类型来指示 Xml-Element 的存在,value=null 表示 Xml-Element 不存在。
    例如,对于某个 Xml-Element ElemA,如果存在,其值为整数,您将在 C# 中得到生成的变量“int? ElemA_nullableValue”。您需要通过首先检查 ElemA_nullableValue 不为 null 来检查元素 ElemA 是否存在;然后,如果它不为 null,则表示该元素存在,您就可以获取 ElemA_nullableValue 的 int 值。

 

7 示例 C# 应用程序

这是一个使用上面生成的 C# 类来加载和处理上面提供的 XML BigCompanyMMM.xml 的示例 C# 代码。

public static void ProcessVer3_Process2(
    string? filePath,
    Microsoft.Extensions.Logging.ILogger? logger)
{
    try
    {
        logger?.LogInformation(
            "+++ProcessVer3_Process2-Start++++++++++++++++++");
        logger?.LogInformation("filePath:" + filePath);

        TextReader textReader = File.OpenText(filePath ?? String.Empty);
        //string xmlFile = textReader.ReadToEnd();
        https.markpelf.com.BigCompany.xsd.BigCompany xmlObject =
            https.markpelf.com.BigCompany.xsd.BigCompany.Load(textReader);

        if (xmlObject != null)
        {
            logger?.LogInformation("CompanyName:" + xmlObject.CompanyName);

            foreach (https.markpelf.com.BigCompany.xsd.BigCompany.EmployeeLocalType 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 (https.markpelf.com.BigCompany.xsd.BigCompany.InfoDataLocalType 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_R:" + (item.Data3_Int_O_R?.ToString() ?? "null"));

                logger?.LogInformation("Data4_Int_O_NR:" + (item.Data4_Int_O_NR?.ToString() ?? "null"));
            }
        }
        else
        {
            logger?.LogError("xmlObject == null");
        }

        logger?.LogInformation(
            "+++ProcessVer3_Process2-End++++++++++++++++++");
    }
    catch (Exception ex)
    {
        string methodName =
            $"Type: {System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType?.FullName}, " +
            $"Method: ProcessVer3_Process2; ";
        logger?.LogError(ex, methodName);
    }
}

 

这是执行日志。

 +++ProcessVer3_Process2-Start++++++++++++++++++
 filePath:C:\TmpXSD\XsdExample_Ver3\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_R:333
 Data4_Int_O_NR:17
 ------------
 Data1_Int_NO_R:123
 Data2_Int_NO_NR:null
 Data3_Int_O_R:15
 Data4_Int_O_NR:null
 ------------
 Data1_Int_NO_R:777
 Data2_Int_NO_NR:null
 Data3_Int_O_R:null
 Data4_Int_O_NR:null
 +++ProcessVer3_Process2-End++++++++++++++++++

 

8 分析

该工具使用“nullable_type_style”方法/样式在生成的 C# 代码中标记可选的 Xml-Element。它不试图指示“nill”的特殊情况。

 

  • Data1_Int_NO_R - 是 int 类型,并且始终有值。
  • Data2_Int_NO_NR - 是 int? 类型,其含义是:
    a) null – 存在但为“nill”(即使元素存在但值为“nill”,此处也为 null)
    b) int – 存在并有值。
  • Data3_Int_O_R – 是 int? 类型,含义是
    a) null - 表示它不存在。
    b) int – 存在并有值。
  • Data4_Int_O_NR - 是 int? 类型,含义是
    a) null – 表示它要么不存在,要么存在但为“nill”。我们无法知道这两种情况中的哪一种发生了。严格来说,这是此方法/生成代码的一个缺陷。在某些情况下,可能需要这种区分。
    b) int – 存在并有值。

 

 

有趣的是,该工具在处理 Xml-Element Data4_Int_O_NR 时,能够指示所有三种状态:“不存在”、“存在-nill”、“存在-int”。我看不出使用现代 API 风格/方法“nullable_type_style”可以实现这一点。如果 Data4_Int_O_NR 为 null,我们就无法知道它是“不存在”还是“存在-nill”。

 

9 结论

该工具 LinqToXsdCore 非常有趣且是免费软件。生成的 C# 类代码非常大(包含一些额外的加载、保存、克隆等方法),但在我的测试中运行稳定。唯一的问题是“nillable”选项处理得非常不好。理论上,这是一个问题,对于一些用户来说,如果他们想要一个遵循所有 XML 规范的工具,这可能很重要。
对于希望使用nullable_type_style API 的用户来说,这可能很有趣,这通常是处理可选 Xml-Elements 的更现代的方法。

有些事情让我对使用此工具有些保留,例如为什么生成的类如此庞大。更重要的是那些非常特定的依赖库的问题,它们是否得到维护,是否可靠,它们是否会与其他 XML 库产生任何冲突?要回答这些问题,需要在决定是否在生产环境中使用此工具之前,对该工具进行更多的研究。

 

10 参考文献

 

[1] XML 架构
https://w3schools.org.cn/xml/xml_schema.asp

 

© . All rights reserved.