C#基于子节点过滤XML集合 跨节点关联提取学生交易数据
问题根因
你之前的代码无法得到预期结果,核心是三个写法错误:
- XPath路径层级错误:原查询路径
/stdXmlFormat/students/students/field多写了一层students节点,实际<student>节点是根下<students>的直接子节点,路径写错自然拿不到正确节点。 - 字段匹配大小写错误:代码里判断的字段id是
sFirstNm、aBLastNm,和XML里实际的SFirstNm、SLastNm大小写不匹配,XML属性值匹配是大小写敏感的,导致字段值提取失败。 - 测试XPath逻辑错误:你写的查询是找id属性等于GUID值的field节点,但实际GUID是
<field id="StudentId">的文本内容,不是id属性的值,所以查询返回空。
实现方案
推荐用LINQ to XML(XDocument)实现,先把关联集合预加载为字典做索引,避免多层嵌套遍历,逻辑清晰效率更高,完整实现代码如下:
using System.Xml.Linq; // 加载XML,替换成你实际的XML来源(文件/字符串/流都可以) XDocument xDoc = XDocument.Parse(xmlContent); XNamespace ns = ""; // 你的XML没有命名空间,直接留空即可 // 1. 预加载学生集合映射:key是(名,姓)元组,value是StudentId var studentMap = xDoc.Descendants(ns + "collection") .First(c => c.Attribute("id")?.Value == "StudentCollection") .Elements(ns + "record") .ToDictionary( record => ( FirstName: record.Elements(ns + "field").First(f => f.Attribute("id")?.Value == "FirstName").Value, LastName: record.Elements(ns + "field").First(f => f.Attribute("id")?.Value == "LastName").Value ), record => record.Elements(ns + "field").First(f => f.Attribute("id")?.Value == "StudentId").Value ); // 2. 预加载学生-交易关联映射:key是StudentId,value是关联的(StudentAccountTransactionsId, AccountTransactionsId)列表 var transactionRelMap = xDoc.Descendants(ns + "collection") .First(c => c.Attribute("id")?.Value == "StudentAccountTransactions") .Elements(ns + "record") .GroupBy(r => r.Elements(ns + "field").First(f => f.Attribute("id")?.Value == "StudentId").Value) .ToDictionary( g => g.Key, g => g.Select(record => new { SATId = record.Elements(ns + "field").First(f => f.Attribute("id")?.Value == "Id").Value, AccountTransId = record.Elements(ns + "field").First(f => f.Attribute("id")?.Value == "AccountTransactionsId").Value }).ToList() ); // 3. 遍历旧student节点,提取所有需要的字段 var result = new List<dynamic>(); var oldStudents = xDoc.Descendants(ns + "student"); foreach (var student in oldStudents) { // 提取旧节点基础字段 string sFirst = student.Elements(ns + "field").First(f => f.Attribute("id")?.Value == "SFirstNm").Value; string sLast = student.Elements(ns + "field").First(f => f.Attribute("id")?.Value == "SLastNm").Value; string rollNo = student.Elements(ns + "field").First(f => f.Attribute("id")?.Value == "RollNumber").Value; // 匹配StudentId if (!studentMap.TryGetValue((sFirst, sLast), out string studentId)) { // 未匹配到新学生数据的场景,按你的业务需求处理即可,这里示例直接跳过 continue; } // 匹配关联的交易记录 if (transactionRelMap.TryGetValue(studentId, out var transList)) { foreach (var trans in transList) { result.Add(new { SFirstNm = sFirst, SLastNm = sLast, RollNumber = rollNo, StudentId = studentId, AccountTransactionsId = trans.AccountTransId, StudentAccountTransactionsId = trans.SATId }); } } }
补充说明
- 预加载映射的方式避免了多层嵌套遍历XML,数据量较大时性能远高于逐节点XPath查询
- 所有字段匹配严格对齐XML里的id属性大小写,不会出现匹配失败的问题
- 最终返回的
result集合就是你需要的所有入库字段,一个学生关联多条交易时会生成多条平铺记录,符合关系型数据库入库要求 - 如果需要兼容姓名可能存在的空格、大小写差异,可以在生成
studentMap的key时做统一处理(比如都转小写、去掉首尾空格)
如果坚持用XmlDocument + XPath实现,匹配指定姓名对应StudentId的正确XPath写法参考:
//collection[@id='StudentCollection']/record[field[@id='FirstName']='Ram' and field[@id='LastName']='Raju']/field[@id='StudentId']注意XPath里属性和节点名都是大小写敏感的,不要写错拼写和大小写。
内容的提问来源于stack exchange,提问作者A_developer
相关产品推荐
相关产品推荐

