17

I have an XML document that contains a series of item nodes that look like this:

<data>
    <item>
        <label>XYZ</label>
        <description>lorem ipsum</description>
        <parameter type="id">123</parameter>
        <parameter type="name">Adam Savage</parameter>
        <parameter type="zip">90210</parameter>
    </item> 
</data>

and I want to LINQ it into an anonymous type like this:

var mydata =
    (from root in document.Root.Elements("item")
    select new {
       label = (string)root.Element("label"),
       description = (string)root.Element("description"),
       id = ...,
       name = ...,
       zip = ...
     });

What's the best way to pull each parameter type according to the value of its 'type' attribute? Since there are many parameter elements you wind up with root.Elements("parameter") which is a collection. The best way I can think to do it is like this by method below but I feel like there must be a better way?

(from c in root.Descendants("parameter") where (string)c.Attribute("type") == "id"
select c.Value).SingleOrDefault()
Dariusz Woźniak
  • 9,640
  • 6
  • 60
  • 73
snappymcsnap
  • 2,050
  • 2
  • 29
  • 53

2 Answers2

31

I would use the built-in query methods in LINQ to XML instead of XPath. Your query looks fine to me, except that:

  • If there are multiple items, you'd need to find the descendants of that instead; or just use Element if you're looking for direct descendants of the item
  • You may want to pull all the values at once and convert them into a dictionary
  • If you're using different data types for the contents, you might want to cast the element instead of using .Value
  • You may want to create a method to return the matching XElement for a given type, instead of having several queries.

Personally I don't think I'd even use a query expression for this. For example:

static XElement FindParameter(XElement element, string type)
{
    return element.Elements("parameter")
                  .SingleOrDefault(p => (string) p.Attribute("type") == type);
}

Then:

var mydata = from item in document.Root.Elements("item")
             select new {
                 Label = (string) item.Element("label"),
                 Description = (string) item.Element("description"),
                 Id = (int) FindParameter(item, "id"),
                 Name = (string) FindParameter(item, "name"),
                 Zip = (string) FindParameter(item, "zip")
             };

I suspect you'll find that's neater than any alternative using XPath, assuming I've understood what you're trying to do.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • yeah that seems like a good approach Jon...the other issue I have to contend with is that sometimes the values are empty strings so having a helper method to handle both the attribute query and gracefully handling the cast to the correct type in the event of empty strings might make the most sense, thanks – snappymcsnap Feb 07 '12 at 23:51
7

use XPATH - it is very fast ( except xmlreader - but a lot of if's)

   using (var stream = new StringReader(xml))
   {
    XDocument xmlFile = XDocument.Load(stream);

    var query = (IEnumerable)xmlFile.XPathEvaluate("/data/item/parameter[@type='id']");

     foreach (var x in query.Cast<XElement>())
     {
         Console.WriteLine(  x.Value );
     }

    }
Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • 8
    so is xpath faster than using the built in LINQ methods? – snappymcsnap Feb 07 '12 at 23:51
  • 3
    An unlucky question that was never answered! – Sharif Mamun Apr 12 '16 at 22:42
  • 2
    @snappymcsnap seems like the answer is **no**. see this comparison done by [Microsoft](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/comparison-of-xpath-and-linq-to-xml#performance-differences) – itsho Jan 10 '19 at 09:51