手动遍历+行构造是处理复杂XML扁平化的可靠方法:用XPath定位记录节点,带上下文前缀生成唯一列名,显式处理属性,空值转DBNull,类型安全转换,并对大文件采用XmlReader流式解析。

XmlDocument.Load() 后用 ReadXml() 无法处理嵌套重复节点
直接用 DataSet.ReadXml() 加载复杂 XML 往往失败,尤其是当存在同名但层级不同的重复节点(比如多个 <Item> 分散在不同父节点下),DataSet 会尝试自动推断关系,结果生成多张表+关系约束,根本不是你想要的单表扁平结构。
真正能控住扁平化逻辑的,是手动遍历 + 行构造。核心思路:把每个“有意义的叶子路径”作为列名,每条完整路径对应的值作为单元格内容。
- 先用
XmlDocument或XDocument加载 XML,确保能 XPath 定位 - 确定你要提取的“记录级”节点(例如所有
//Order/Items/Item),不是根节点也不是中间容器 - 对每个该层级节点,用相对 XPath 提取字段,如
./ProductId/text()、../Customer/Name/text(),支持跨层回溯 - 避免用
SelectNodes("*/text()")这类模糊表达——它会混入空白文本节点,导致DataTable列类型推断出错
用 XPath 构造列名时,斜杠太多容易撞上命名冲突
如果原始 XML 是 <Root><A><B><C>val</C></B></A><D><B><C>val2</C></B></D></Root>,直接用 B/C 当列名,两个 C 值会写进同一列,数据错位。
必须带上下文前缀,比如 A_B_C 和 D_B_C,否则 DataTable.AddColumn() 会静默跳过重复列名,后续赋值全丢。
- 列名生成规则建议:用
node.ParentNode.Name + "_" + node.Name拼接,不依赖绝对路径 - 遇到属性节点(
@id)要显式标注,如Item_@id,和元素区分开 - 空格、冒号、斜杠这些非法字符必须替换为下划线,否则
DataTable.Columns.Add()抛ArgumentException - 别依赖
AutoIncrement或AllowDBNull=false—— XML 字段天然稀疏,设成true和true更安全
DataTable.Rows.Add() 传 object[] 时类型不匹配会静默转成 string
如果你某列定义为 typeof(DateTime),但对应 XML 节点内容是空或格式错误(如 "2024-13-01"),Rows.Add() 不报错,而是把整个值转成字符串塞进去,后面做日期计算就崩了。
类型校验不能省。尤其注意数字、布尔、时间这三类,XML 里全是文本,得手动 parse。
- 对每个字段值,先
Trim()再判断是否为空,空字符串一律转DBNull.Value - 数字字段用
int.TryParse()/decimal.TryParse(),失败就给DBNull.Value,别硬转 - 日期字段优先用
DateTime.TryParseExact()指定格式,XML 常见yyyy-MM-ddTHH:mm:ss和yyyy-MM-dd两种,别只试一种 - 布尔字段小心
"true"/"false"大小写和"1"/"0",统一转小写再比对
大 XML 文件用 XmlDocument 会爆内存,XDocument 也不够用
超过 5MB 的 XML,用 XmlDocument 或 XDocument 全量加载,GC 压力极大,且 XPath 查询变慢。这时候必须切到流式解析。
不是不用 DataTable,而是换种方式喂数据:用 XmlReader 边读边建行,跳过不关心的分支,内存占用稳定在几百 KB。
- 用
XmlReader.ReadToFollowing("Item")快速定位记录节点,比 XPath 快一个数量级 - 进到
Item后,用reader.ReadToDescendant("ProductId")找字段,避免递归遍历 - 每个字段值用
reader.ReadElementContentAsString()直接读,不拼接节点树 - 别在循环里反复 new
DataRow,先建好DataTable结构,然后table.NewRow()+table.Rows.Add()
路径深度、命名冲突、类型转换、内存控制——这四点漏掉任何一环,出来的 DataTable 看似整齐,实际查数就错。尤其是跨层取值时用错相对路径,或者把属性和元素当成一回事,问题当场就藏进数据里,后面很难排查。










