tags:

views:

144

answers:

3

hi,

been taxing my brain trying to figure out how to perform a linq xml query.

i'd like the query to return a list of all the "product" items where the category/name = "First Category" in the following xml

<catalog>
  <category>
    <name>First Category</name>
    <order>0</order>
    <product>
      <name>First Product</name>
      <order>0</order>
    </product>
    <product>
      <name>3 Product</name>
      <order>2</order>
    </product>
    <product>
      <name>2 Product</name>
      <order>1</order>
    </product>
  </category>
</catalog>
A: 

You want to use the Single extension method here. Try the following:

var category = doc.RootNode.Elements("category").Single(
    c => c.Attribute("name").Value == "First Category");
var products = category.Elements("product");

Note that this assumes you only have one category with name "First Category". If you possibly have more, I recommend using Marc's solution; otherwise, this should be the more appropiate/efficient solution. Also, this will throw an exception if any category node doesn't have a name child node. Otherwise, it should do exactly what you want.

Noldorin
Reason for down vote please?
Noldorin
Now this is baffling me. This answer is fully correct, given the (IMO fair) assumption that the OP is only looking for one category called "First Category"...
Noldorin
I agree it should work (athough the attribute stuff could be simpler, and you run the risk of throwing exceptions if any of the elements/attributes are missing); +1 here... I don't know why, but all the original answers on this question got downvotes.
Marc Gravell
@Marc: Cheers. And yeah, I should probably add the caveat for null attribute objects, though if `Name` is guaranteed to exist... I've only downvoted MrTortoise's answer now. (I changed yours to an upvote once I reread the question and your answer properly!)
Noldorin
+6  A: 

Like so:

    XDocument doc = XDocument.Parse(xml);
    var qry = from cat in doc.Root.Elements("category")
              where (string)cat.Element("name") == "First Category"
              from prod in cat.Elements("product")
              select prod;

or perhaps with an anonymous type too:

    XDocument doc = XDocument.Parse(xml);
    var qry = from cat in doc.Root.Elements("category")
              where (string)cat.Element("name") == "First Category"
              from prod in cat.Elements("product")
              select new
              {
                  Name = (string)prod.Element("name"),
                  Order = (int)prod.Element("order")
              };
    foreach (var prod in qry)
    {
        Console.WriteLine("{0}: {1}", prod.Order, prod.Name);
    }
Marc Gravell
There would seem to be only one `category` with name *First Category*, so `Single` is probably more appropiate here.
Noldorin
I think it is safe to assume that this is a fragment from a larger xml block, otherwise the filtering itself is redundant. In which case you cannot say how many categories there are with given names...
Marc Gravell
Possibly, though *name* typically implies uniqueness.
Noldorin
No it doesn't. "key" or "id" *might*. Name could be unique or not...
Marc Gravell
My point is that **we don't know**.
Marc Gravell
In various UI frameworks, name implies uniqueness. I could probably think of several other examples. I don't think there's much point arguing over what is a rather subjective issue anyway.
Noldorin
I agree, this is mainly conjecture. The OP has both solutions anyway, so they can choose whichever. :)
Noldorin
I take it the explicit conversion to `string` won't complain if `Element` returns null?
Noldorin
thanks guys. just to clear it up, name is unique in this case. but i have other similar scenarios where it isnt unique. So both are awesome :D
sf
@sf: No problem. And yeah, Where/Single will often be interchangeable, though the latter is generally more efficient when applicable.
Noldorin
@Noldorin - indeed; using a cast (instead of .Value) means it doesn't throw if null (missing)
Marc Gravell
+1  A: 

Here's an example:

        string xml = @"your XML";

        XDocument doc = XDocument.Parse(xml);

        var products = from category in doc.Element("catalog").Elements("category")
                       where category.Element("name").Value == "First Category"
                       from product in category.Elements("product")
                       select new
                       {
                           Name = product.Element("name").Value,
                           Order = product.Element("order").Value
                       };
        foreach (var item in products)
        {
            Console.WriteLine("Name: {0} Order: {1}", item.Name, item.Order);
        }
bruno conde