views:

1964

answers:

3

I have the following XML document:

<?xml version="1.0" encoding="UTF-8"?>
<FamilyTree>
  <Parent name="Ken">
    <Child name="Lorna">
      <Grandchild name="Andrew"/>
      <Grandchild name="Brian"/>
    </Child>
    <Child name="Mike">
      <Grandchild name="Ann"/>
      <Grandchild name="Beth"/>
    </Child>
  </Parent>
  <Parent name="Norma">
    <Child name="Owen">
      <Grandchild name="Charles"/>
    </Child>
    <Child name="Peter">
      <Grandchild name="Charlotte"/>
    </Child>
  </Parent>
  <Parent name="Quinn">
    <Child name="Robert">
      <Grandchild name="Debbie"/>
      <Grandchild name="Eric"/>
    </Child>
    <Child name="Susan">
      <Grandchild name="Frank"/>
    </Child>
  </Parent>
  <Parent name="Tom">
    <Child name="Ursula">
      <Grandchild name="George"/>
      <Grandchild name="Harriet"/>
    </Child>
    <Child name="Victor">
      <Grandchild name="Ian"/>
      <Grandchild name="Juliet"/>
    </Child>
  </Parent>
</FamilyTree>

I'm trying to select all the "Parents" with a Child who has at least two children ("Grandchild") of his/her own. Note that I'm not looking for "Parents" with at least two "Grandchild[ren]".

The following LINQ query works, but I've a feeling it's not the most elegant.

IEnumerable<XElement> parents = (from c in familyTreeElement.Descendants("Child")
                                 where c.Elements().Count() > 1
                                 select c.Parent).Distinct();

Is there a better way to specify this?

A: 

I don't know the "SQL-like" syntax enough to guarantee that I'll get the syntax right if I write it that way, but you want to use .Any() instead of .Count(), and if you select in a different manner, you don't need the Distinct() at the end. Try this:

IEnumerable<XElement> parents =
    familyTreeElement.Elements("Parent").Where(
        parent => parent.Elements("Child").Any(
            child => child.Elements().Count() >= 2));

EDIT: If you want to ensure that there are at least 2, you do pretty much have to use .Count().

mquander
Count() is for wimps - you can get away without it ;)
Jon Skeet
+2  A: 

Hmmm... I'm finding it hard to get my head round it exactly :)

Normally to find out if there are any elements, I'd use Any - but you want to see if there are at least two elements. We still don't need to use Count though - because there being at least two elements is the same as skipping an element and seeing if there are still any. So...

var parents = familyTreeElement.Elements("Parent")
    .Where(parent => parent.Elements("Child").Any(
                     child => child.Elements("Grandchild").Skip(1).Any()));

I think that works - and actually it doesn't read too badly:

For each parent, see whether any of there children has any (grand)children after ignoring the first (grand)child.

I suspect using XPath (as per Marc's answer) would be the most readable option though.

Jon Skeet
That's a clever way to find whether there are two without counting. +1.
mquander
I hadn't thought of it before, but it's exactly how we'd do it as humans. If we were asked to make sure there were at least ten biscuits in a tin, we'd only look as far as the first ten we saw :)
Jon Skeet
+1  A: 

Ahh the edit (2 grand-children) helps ;-p

While XDocument is useful, at times I miss XPath/XQuery. With XmlDocument you could just use doc.DocumentElement.SelectNodes("Parent[Child/Grandchild[2]]").

Marc Gravell
Any reason not to use XPathExtensions then?
Jon Skeet
Yes, sorry. Typos. Was thinking too hard when trying to explain it all.
Gayle
@Jon - true, true - I seem to have blindness for them...
Marc Gravell
familyTreeElement.XPathSelectElements("Parent[Child/Grandchild[2]]"); seems to work.
Gayle