ASP.NET Hosting

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

4 Comments

  • 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?

  • @Randy: Couldn't you just do

    let myPayment = bill.Element("payment")
    where myPayment != null
    select new Payment
    {
    Amount = (decimal) myPayment.Element("Amount")
    }

  • 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"
    };

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

Comments have been disabled for this content.