views:

209

answers:

6

I am trying to query this very complicated XML document using xDocument and LINQ to XML. I want to perform the following action:

Get all elements that answer to a certain criteria, and if they don't, return another attribute from the xDocument.

Example:

<cars>
    <car>
        <patrol type="oil">
            <url> http://Toyotaoil.com </url>
        </patrol>
    </car>
    <car>
        <patrol type="oil">
            <url> http://BMWoil.com </url>
        </patrol>
        <patrol type="gas">
            <url> http://BMWgas.com </url>
        </patrol>
    </car>
    <car>
        <patrol type="gas">
            <url> http://Hondagas.com </url>
        </patrol>
    </car>

Now what I'd like to get from this query is a list of patrols of type oil, unless the car doesn't use petrol, and then I'd be satisfied with gas.

If I use the where clause I just miss the cases where the car uses gas. Is there any such thing like a where clause, where I can specify what to do if they condition wasn't met?

A: 

Order by type according to your specifications, take FirstOrDefault().


Edit: What I meant was something like this:

var patrols = from car in doc.Root.Elements()
              let p = car.Elements().OrderBy(patrol=>patrol.Attribute("type").Value).First()
              select p;

This returns one result per car. Check the OrderBy clause and adjust it accordingly. This would result in:

<patrol type="oil">
  <url> http://Toyotaoil.com </url>
</patrol>

<patrol type="gas">
  <url> http://BMWgas.com </url>
</patrol>

<patrol type="gas">
  <url> http://Hondagas.com </url>
</patrol>

Edit again: Ah, now it clicked. Yes, this only returns a single item per car - Zyphrax gave a nice solution.

Benjamin Podszun
He's trying to select multiple items, not a single one.
Jon Skeet
@Jon: I don't get your answer, nor the downvote. Please check the post again.
Benjamin Podszun
A: 

It sounds like you don't actually want a where clause at all - you just want a select clause that either picks one value or another.

However, your example doesn't really describe how you'd select a different item based on the values - which "other attribute" would you select? What do you mean by "where the car uses gas"? If you can give more details of the example, it shouldn't be too hard to give you matching code.

Jon Skeet
+2  A: 
xdoc.Element("cars")
    .Elements("car")
    .Select(car => car.Elements("patrol")
                      .SingleOrDefault(p => (string)p.Attribute("type") == "oil")
                   ??
                   car.Elements("patrol")
                      .Single(p => (string)p.Attribute("type") == "gas"));
dtb
This will result in only one oil element. If I understand correctly it should return all oil patrol elements (if any) otherwise return any patrol elements.
Zyphrax
+1  A: 

You can just make something like this:

var query = from element in someElements
            select element.Attribute("type").Value == "oil"
                ? returnSomethingWhenItIsOil
                : returnSomethingWhenItIsSomethingElse;

or

var query = from element in someElements
            where element.Attribute("type") == "oil"
                || element.Attribute("type") == "gas"
            select element;

But explain the problem better, thanks :)

lasseespeholt
Not quite correct, this will return oil or gas. Not just oil or if there is no oil, then gas.
Zyphrax
Ahh now I got it ;) thanks for clearing. Then I guess dtb's solution will do the trick.
lasseespeholt
A: 
                       var cars = from c in xdoc.Descendants("car")
                       where
                       (c.Element("patrol").Attribute("type").Value == "oil" ||
                       c.Element("patrol").Attribute("type").Value == "gas")
                       select new Car
                       {
                           FuelType = c.Element("patrol").Attribute("type").Value.ToString()
                       };

    foreach (Car c in cars)
        {
            Console.WriteLine(c.ToString());
        }

    class Car
    {
        public string FuelType { get; set; }
        public override string ToString()
        {
            return "Car FeulType = " + this.FuelType.ToString();
        }
    }

These are the results, I am getting

alt text

Asad Butt
+2  A: 

The solution below should give you the flexibility to query whatever you like:

var result = from car in xdoc.Element("cars").Elements("car") 
             let patrols = car.Elements("patrol")
             let oils = patrols.Where(patrol => patrol.Attribute("type") == "oil")
             select new {
                    Car = car,
                    Patrols = (oils.Any() ? oils : patrols)
             }

I don't have Visual Studio here, so I hope it compiles :)
Give a bit more information on what you like to select, and I'll give you a more specific LINQ statement.

Zyphrax
this didn't completely work, but I learned a totally new approach (using the let keyword) that pointed me to the right direction... so, thank you!
vondip