LINQ to XML Tip - Checking for Values
This tip comes right from some of the examples I am writing for my book Data Services with Silverlight 2. I was writing a LINQ query definition for LINQ to XML that parses through a response from an Amazon RESTful web service when I realized I cannot be sure if a book will have a price stored in this XML element, or that element, or some other element. So I had to check to see what element existed and contained the appropriate value.
So I ran a query like this:
var bookQuery =
from book in bookXml.Descendants("Item")
let attributes = book.Element("ItemAttributes")
let price = Decimal.Parse((
book.Elements("OfferSummary").Any()
&& book.Element("OfferSummary").Elements("LowestNewPrice").Any()
? book.Element("OfferSummary").Element("LowestNewPrice").Element("Amount").Value
: (attributes.Elements("ListPrice").Any()
? attributes.Element("ListPrice").Element("Amount").Value
: "0"))) / 100
select new {Price = price};
The problem was that the price could be stored in the OfferSummary element if an offer exists for the item (a book in my case). But the OfferSummary element ay not even exist. If it does, then I have to check for the LowestNewPrice element and it may not exist. Finally if it does, I go ge the Amount element and grab its value. Otherwise I have to get get the ListPrice element. But it may not exist either. I think you get the point ... sometimes the elements exist and sometimes they don't.
So one way around this is to check if the element exists by using the Elements collection (instead of the Element ... singular) and using its Any() method. This returns a boolean value that tells if any items exist in that collection. So the above code makes sure I get a price whether it be the lower offer price or the current list price (as a fallback). If no price exists, it returns a 0. All of this is done outside of the select statement in the LINQ query definition. This allows the select to remain unclutterred. As you can see, the let statement for the price got ugly quick.
Using the Elements("foo").Any() technique is pretty easy to do and makes it easier to grab a value and prep.