为何为.NET DataSet的XML Schema添加maxOccurs后会失效?
问题现象
在基于.NET Framework v4.8的C#程序中,使用DataSet.ReadXml读取以下XML Schema时,会抛出**"Duplicated declaration 'foo'"**异常:
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="container" type="containerType"/> <xs:complexType name="containerType"> <xs:sequence> <xs:element name="A" type="AType" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="B" type="BType" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:complexType name="AType"> <xs:sequence> <xs:element name="foo" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:complexType name="BType"> <xs:sequence> <xs:element name="foo" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:schema>
但移除foo元素的maxOccurs属性(使用默认值1)后,该XML Schema可被正常读取:
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="container" type="containerType"/> <xs:complexType name="containerType"> <xs:sequence> <xs:element name="A" type="AType" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="B" type="BType" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:complexType name="AType"> <xs:sequence> <xs:element name="foo" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="BType"> <xs:sequence> <xs:element name="foo" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType> </xs:schema>
疑问
已查阅微软官方文档未找到相关说明,尝试修改maxOccurs值仅默认值1时有效。重命名其中一个foo可解决问题,但想了解异常原因及其他可行方案。
附C#验证代码:
using System; using System.Data; using System.Windows.Forms; static class Program { static void Main() { OpenFileDialog ofd = new OpenFileDialog(); if (ofd.ShowDialog() == DialogResult.OK) { var ds = new DataSet(); try { // 传入第一个Schema文件时会抛出异常 ds.ReadXml(ofd.FileName); } catch (Exception ex) { MessageBox.Show(ex.Message); } } } }
异常原因
DataSet的XML Schema自动映射规则中:
- 当元素设置
maxOccurs="unbounded"时,该元素会被映射为独立的DataTable; - 当元素
maxOccurs为默认值1(或显式设置为1)时,该元素会被映射为对应父DataTable中的DataColumn。
在第一个Schema中,AType和BType下的foo都设置了maxOccurs="unbounded",DataSet会尝试为这两个foo创建同名的DataTable。但DataSet不允许内部存在同名的DataTable(即使它们属于不同的父类型),因此触发"重复声明"的异常。
而第二个Schema中,foo的maxOccurs为默认1,此时foo会被分别映射到A表和B表中的列,不同DataTable的列名允许重复,因此不会产生冲突。
可行解决方案
方案一:为同名 unbounded 元素指定唯一表名
通过XML Schema的扩展注解msdata:TableName,给每个foo对应的DataTable指定唯一名称。需要先在schema根节点添加xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"命名空间,示例:<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <!-- 其他结构不变 --> <xs:complexType name="AType"> <xs:sequence> <xs:element name="foo" type="xs:string" minOccurs="0" maxOccurs="unbounded"> <xs:annotation> <xs:appinfo> <msdata:TableName>AFoo</msdata:TableName> </xs:appinfo> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="BType"> <xs:sequence> <xs:element name="foo" type="xs:string" minOccurs="0" maxOccurs="unbounded"> <xs:annotation> <xs:appinfo> <msdata:TableName>BFoo</msdata:TableName> </xs:appinfo> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> </xs:schema>方案二:调整Schema结构避免冲突
比如将其中一个foo重命名,或者把重复的元素结构抽象为独立类型,再通过不同的元素名引用。方案三:手动构建DataSet结构
跳过ReadXml的自动Schema映射,手动创建DataTable、DataColumn和表间关系,再读取XML数据填充DataSet,完全控制结构命名。
内容的提问来源于stack exchange,提问作者alexandre gravel

