Sunday, August 2, 2009

LINQ and the outer variable trap

I'd written the following piece of code and was debugging this to try and work out why it wouldn't work. Take a look at it and see if you can figure out my mistake:

foreach (string word in words)
{
    query = query.Where(a => (a.Message).Contains(word));
}

The objective of this code is to build a query with an AND clause between each of the given words.

As a quick aside, while trying to debug this I ended up with a query which ended like this:

WHERE ([t0].[MESSAGE] LIKE @p0) AND ([t0].[MESSAGE] LIKE @p

 

To find the values that p0 and p1 have been set to you need to set the DataContext's .Log property to a TextWriter such as Console.Out or TextWriter tw = new StringWriter();

Back to the quiz, the reason why that code is flawed is because of the outer variable trap. The same variable is being captured in the loop because LINQ uses delayed execution and the Contains(word) part is not evaluated until later. To fix this problem we put a temporary variable inside the loop to force it to be evaluated. This will work:

foreach (string word in words)
{
    string innerWord = word;
    query = query.Where(a => (a.Message).Contains(innerWord));
}

2 comments:

  1. Curios as to why you would take that approach, rather than something like:
    query = query.Where( a => words.All( w => a.Message.Contains(w)))

    ReplyDelete
  2. It's a while since I wrote this but I think that this was a gross simplification of what I was actually doing (probably LINQ to SQL) and I distilled it down to this simple example to demonstrate the problem. I have not tried the syntax that you suggested but I'm sure that for a simple case like this it would work. i.e. this example is contrived for the purpose of demonstrating the outer variable trap and not to solve this particular problem.

    ReplyDelete