问题
I have an XML document like this
<root>
<item id="1" creator="me">
<childA>1</childA>
<childB>2</childB>
</item>
<item id="2" creator="me">
<childA>1</childA>
<childB>3</childB>
<childB>4</childB>
</item>
</root>
I'm trying to find duplicate items, then again duplicate child items for the duplicate items with logic like this
XDocument XmlRoot //whatever...you get the point
// Get item nodes
var items = XmlRoot.Descendants("item");
// Find duplicate items keys using creator attribute
var duplicateItemKeys = items.GroupBy(x => x.Attribute("creator").Value)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
foreach(var duplicateItemKey in duplicateItemKeys)
{
// Get the duplicate item XML elements using the duplicate keys
var duplicateItems = items.Where(x => x.Attribute("creator").Value == duplicateToucheKey)
.OrderBy(xelement => xelement.Attribute("CreatedOn").Value);
}
This works, however there is a problem later when I try to use duplicateItems. Any time it enumerates (like in a foreach duplicateItems) the first item looses the context of it's children. The second one is just fine.
So for example, later in code I say
var allItemB = new List<XElement>();
foreach (duplicateItem in duplicateItems)
{
allItemB.AddRange(duplicateItem.Descendants("childB"));
}
I expect "allItemB" to contain 2 on the first pass, then 234 on the second. What ends up happening is that it only contains 34 because once the duplicateItems array is enumerated the first XElement looses it's children.
Does anyone know how to fix this?
回答1:
If I understand your question correctly, you want allItemB to have 3 elements - allItemB[0] is the XElement childB with value 2, [1] to be 3 and [2] to be 4?
If so, the problem is where you are declaring duplicateItems. Your code same doesn't even compile, as the variable's scope is limited to the first foreach loop, and thus not available at the second.
My code to get the above result:
XDocument XmlRoot = XDocument.Load( "C:\\somefile.xml" );
// Get item nodes
var items = XmlRoot.Descendants("item");
// Find duplicate items keys using creator attribute
var duplicateItemKeys = items.GroupBy(x => x.Attribute("creator").Value)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
IEnumerable<XElement> duplicateItems = new List<XElement>();
foreach(var duplicateItemKey in duplicateItemKeys)
{
// Get the duplicate item XML elements using the duplicate keys
duplicateItems = items.Where(x => x.Attribute("creator").Value == duplicateItemKey)
.OrderBy(xelement => xelement.Attribute("id").Value);
}
var allItemB = new List<XElement>();
foreach (var duplicateItem in duplicateItems)
{
allItemB.AddRange(duplicateItem.Descendants("childB"));
}
Edit: forgot to mention that I changed the OrderBy in the first foreach loop because the sample xml file didn't have the CreatedOn attribute.
And if you want, you can use a little more Linq and drop the foreach loops entirely, like so:
XDocument XmlRoot = XDocument.Load( "C:\\somefile.xml" );
// Get item nodes
var items = XmlRoot.Descendants("item");
// Find duplicate items keys using creator attribute
var duplicateItemKeys = items.GroupBy(x => x.Attribute("creator").Value)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
// Get the duplicate item XML elements using the duplicate keys
var duplicateItems = items.Where(i => duplicateItemKeys.Contains(i.Attribute("creator").Value))
.OrderBy( xelement => xelement.Attribute("id").Value );
// Get the child nodes named childB
var allItemB = new List<XElement>();
allItemB.AddRange( duplicateItems.Descendants("childB") );
来源:https://stackoverflow.com/questions/11371200/linq-xml-descendants-being-lost-on-enumeration