Fabrice's weblog

Tools and Source

News

My .NET Toolbox
An error occured. See the script errors signaled by your web browser.
No tools selected yet
.NET tools by SharpToolbox.com

Read sample chapters or buy LINQ in Action now!
Our LINQ book is also available on AMAZON

.NET jobs

Emplois .NET

Tuneo

ASP.NET Hosting transatlantys

Contact

Me

Others

Selected content

Archives

Use the power of let in your LINQ queries

Often, when you try to find out how to write the correct LINQ query you need, you end up being confused because it becomes too complex. In such situations, you should remember that the let clause is here to help you.

Let's see is an example from the official LINQ forum.
Someone asked how to query the following XML document:

<cars>
  <car name="Toyota Coupe">
    <profile name="Vendor" value="Toyota"/>
    <profile name="Model" value="Celica"/>
    <profile name="Doors" value="2"/>
    <support name="Racing" value="yes"/>
    <support name="Towing" value="no"/>
  </car>
  <car name="Honda Accord Aerodec">
    <profile name="Vendor" value="Honda"/>
    <profile name="Model" value="Accord"/>
    <profile name="Doors" value="4"/>
    <support name="Racing" value="no"/>
    <support name="Towing" value="yes"/>
  </car>
</cars>

Here is one way to do it:

from car in root.Elements("car")
let profiles =
  from profile in car.Elements("profile")
  select new {
    Name = profile.Attribute("name").Value,
    Value = profile.Attribute("value").Value
  }
let supports =
  from support in car.Elements("support")
  select new {
    Name = support.Attribute("name").Value,
    Value = support.Attribute("value").Value
  }
select new Car {
  Name = car.Attribute("name").Value,
  Vendor = profiles.Single(prof => prof.Name == "Vendor").Value,
  Model = profiles.Single(prof => prof.Name == "Model").Value,
  Doors = int.Parse(profiles.Single(prof => prof.Name == "Doors").Value),
  RacingSupport = supports.Single(sup => sup.Name == "Racing").Value == "yes"
};

It's easier to isolate the "profile" and "support" elements in separate sequences using let clauses, as above. Then the select clause becomes simple to write.

As Luke Hoban explains on his blog: 

With let query clauses, you can introduce a variable into scope and use it in the subsequent query clauses. Similar to local variables in a method body, this gives you a way to avoid evaluating a common expression multiple times by storing it in a variable. This can be very useful even in much simpler queries. Of course, in the query above - let is absolutely critical.

The "query above" he refers to is a ray tracer coded as a big LINQ query full of let clauses!


Cross-posted from http://LinqInAction.net

Comments

Randy Magruder said:

What if "let" returns null?

If you declare an object with "let" such as

let myPayment = bill.Element("payment")

select new Payment

  {

     Amount = (decimal) myPayment.Element("Amount")

  }

If myPayment is null, this blows up with a null reference exception.  The alternative would be

  Amount = myPayment != null ? (decimal) myPayment.Element("Amount")

but think how cumbersome that is for a bunch of fields.

What's the solution?

# April 15, 2009 1:54 PM

Geoff said:

@Randy: Couldn't you just do

let myPayment = bill.Element("payment")

where myPayment != null

select new Payment

 {

    Amount = (decimal) myPayment.Element("Amount")

 }

# April 24, 2009 5:52 PM

Iuri said:

Instead of, you what do you think using this way to simplify the query?

           var query = from car in root.Elements("car")

                       let items =

                         from item in car.Elements("profile").Concat(car.Elements("support"))

                         select new

                         {

                             Name = item.Attribute("name").Value,

                             Value = item.Attribute("value").Value

                         }

                       select new

                       {

                           Name = car.Attribute("name").Value,

                           Vendor = items.Single(prof => prof.Name == "Vendor").Value,

                           Model = items.Single(prof => prof.Name == "Model").Value,

                           Doors = int.Parse(items.Single(prof => prof.Name == "Doors").Value),

                           RacingSupport = items.Single(sup => sup.Name == "Racing").Value == "yes",

                           TowingSupport = items.Single(sup => sup.Name == "Towing").Value == "yes"

                       };

# February 11, 2010 7:24 AM

Fabrice Marguerie said:

Iuri, yes it's possible to do it this way.

# February 11, 2010 9:04 AM