如果有人能够解释这一点,我会感到惊讶,但知道其他人是否可以重现我正在经历的奇怪感觉会很有趣......
我们有一个基于InfoPath的东西可以处理很多表单。表单数据应符合XSD,但InfoPath会以所谓的“my-fields”的形式不断添加自己的元数据。我们想删除my-fields,然后我写了这个简单的方法:
string StripMyFields(string xml)
{
var doc = new XmlDocument();
doc.LoadXml(xml);
var matches = doc.SelectNodes("//node()").Cast<XmlNode>().Where(n => n.NamespaceURI.StartsWith("http://schemas.microsoft.com/office/infopath/"));
Dbug("Found {0} nodes to remove.", matches.Count());
foreach (var m in matches)
m.ParentNode.RemoveChild(m);
return doc.OuterXml;
}
现在出现了非常奇怪的东西!当我运行此代码时,它的行为与我期望的一样,删除InfoPath名称空间中的任何节点。但是,如果我注释掉对Dbug的调用,代码就会完成,但是一个“my-field”仍然在XML中。
我甚至评论了方便的Dbug方法的内容,它的行为方式仍然相同:
void Dbug(string s, params object[] args)
{
//if (args.Length > 0)
// s = string.Format(s, args);
//Debug.WriteLine(s);
}
输入XML:
<?xml version="1.0" encoding="UTF-8"?>
<skjema xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2008-03-03T22:25:25" xml:lang="en-us">
<Field-1643 orid="1643">data.</Field-1643>
<my:myFields>
<my:field1>Al</my:field1>
<my:group1>
<my:group2>
<my:field2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2009-01-01</my:field2>
<Field-1611 orid="1611">More data.</Field-1611>
<my:field3>true</my:field3>
</my:group2>
<my:group2>
<my:field2>2009-01-31</my:field2>
<my:field3>false</my:field3>
</my:group2>
</my:group1>
</my:myFields>
<Field-1612 orid="1612">Even more data.</Field-1612>
<my:field3>Blah blah</my:field3>
</skjema>
除非我调用Dbug,否则不会删除“my:field3”元素(在底部,文本“Blah blah”)。
显然宇宙不应该是这样的,但我很想知道其他人是否能够重现。
我在Win8 Enterprise 6.2.9200上使用VS2012 Premium(11.0.50727.1 RTMREL)和FW 4.5.50709。
答案 0 :(得分:3)
首先要做的事情。 LINQ使用称为deferred execution的概念。这意味着在实际实现查询之前不会获取任何结果(例如通过枚举)。
为什么节点删除问题会有问题?让我们看看代码中会发生什么:
SelectNodes
创建XPathNodeIterator
,XPathNavigator
使用XmlNodeList
将数据提供给SelectNodes
XPathNodeIterator
Cast
根据提供的XPath表达式遍历xml文档树Where
和XPathNodeIterator
只是决定DBug
返回的节点是否应参与最终结果我们在Cast
方法调用之前到达。暂时,假设它不在那里。此时,没有什么实际上已经发生了。我们只有非物质化的 LINQ查询。
当我们开始迭代时,事情会发生变化。所有迭代器(Where
和WhereIterator
都有自己的迭代器)开始滚动。 CastIterator
向XPathNodeIterator
询问项目,然后请求Field-1643
最终返回第一个节点(Where
)。不幸的是,这个没有通过my:myFields
测试,因此我们要求下一个测试。运气好my:field1
,这是一场比赛 - 我们将其删除。
我们快速前往my:field1
(再次, WhereIterator → CastIterator → XPathNodeIterator ),这也已删除。在这里停留片刻。删除my:field1
将其与父项分离,从而将其(null
)兄弟节点设置为XPathNodeIterator
(删除节点之前/之后没有其他节点)。
目前的状况如何? my:field1
知道它的当前元素是XPathNodeIterator
节点,它刚被删除。已删除,如与父分离,但迭代器仍保留引用。听起来不错,让我们问下一个节点。 Current
的作用是什么?检查其NextSibling
项目,并询问null
(因为它没有孩子可以先行走) - 这是<Root>
<James>Bond</James>
<Jason>Bourne</Jason>
<Jimmy>Keen</Jimmy>
<Tom />
<Bob />
</Root>
,因为我们刚刚执行了分离。这意味着迭代结束了。完成工作。
因此,通过在迭代期间更改集合结构,您只从文档中删除了两个节点(实际上只有一个节点,因为第二个删除的节点是已删除的节点的子节点)。
使用更简单的XML可以观察到相同的行为:
J
假设我们想要摆脱以var doc = new XmlDocument();
doc.LoadXml(xml);
var matches = doc
.SelectNodes("//node()")
.Cast<XmlNode>()
.Where(n => n.Name.StartsWith("J"));
foreach (var node in matches)
{
node.ParentNode.RemoveChild(node);
}
Console.WriteLine(doc.InnerXml);
开头的节点,导致文档只包含诚实的人名:
DBug
不幸的是, Jason 和 Jimmy 仍然存在。 James '下一个兄弟(由迭代器返回的那个)本来是 Jason ,但是只要我们从树中分离 James 没有兄弟姐妹和迭代结束。
现在,为什么它适用于Count
? ToList
调用实现了查询。迭代器已经运行,我们可以访问开始循环时所需的所有节点。在Where
之后调用{{1}}或者在调试期间检查结果时会发生同样的事情(VS甚至通知您检查结果将枚举集合)。
答案 1 :(得分:0)
我认为这取决于schrodinger的cat问题,在你查看或采取行动之前,实际上不会编译查询结果。这意味着,在调用Count()(或获取结果的任何其他函数)或在调试器中查看它之前,结果不存在。作为测试,试试这样:
if (matches.Any())
foreach (var m in matches)
m.ParentNode.RemoveChild(m);
答案 2 :(得分:0)
非常奇怪,只有当您在调试时实际查看结果时才会删除最后一个节点。顺便说一句,将结果转换为List然后循环遍历它也可以。
List<XmlNode> matches = doc.SelectNodes("//node()").Cast<XmlNode>().Where(n => n.NamespaceURI.StartsWith("http://schemas.microsoft.com/office/infopath/")).ToList();
foreach (var m in matches)
{
m.ParentNode.RemoveChild(m);
}
答案 3 :(得分:0)
jimmy_keen的解决方案对我有用。我只有一个简单的
//d is an XmlDocument
XmlNodeList t = d.SelectNodes(xpath);
foreach (XmlNode x in t)
{
x.ParentNode.RemoveChild(x);
}
d.Save(outputpath);
这将只删除3个节点,而在调试模式下单步执行将删除1000多个节点。
在foreach解决问题之前添加一个Count:
var count = t.Count;