views:

1435

answers:

8

Is is possible to have a local variable in an anonymous c# methods, i.e. in the following code I would like to perform the count only once.

IQueryable<Enquiry> linq = db.Enquiries;

if(...) linq = linq.Where(...);

if(...) linq = linq.Where(e => 
    (x <= (from p in db.Orders where p.EnquiryId == e.Id select p).Count() && 
        (from p in db.Orders where p.EnquiryId == e.Id select p).Count() <= y));

if(...) linq = linq.Where(...);

var result = (from e in linq select e);

Is there a "let" for anonymous functions?

Update: Note that I'm adding several Where clauses after this statement so I can't close with a select.

/Niels

+15  A: 

Yes, why not?! After all it's a function, just anonymous!

Example:

 x => { int y = x + 1; return x + y; }

Or alternatively:

 delegate(int x) {
     int y = x + 1;
     return x + y;
 }

So your code can be written as:

  ... = linq.Where(e => {
         var count = (from p in db.Orders where p.EnquiryId == e.Id select p).Count();
         return x <= count && count <= y;
  });

UPDATE: To clarify things about the comment, it's important to know the difference between anonymous methods and lambda expressions. An anonymous method is just like a normal method, without an explicit name. When you compile it, the compiler generates a normal method with a weird name for you instead, so it will not have any special limitations. However, one representation of an anonymous method is a lambda expression. Lambda expressions can be interpreted in a couple different ways. The first is a delegate. In that way, they are equal to an anonymous method. The second is an expression tree. This way is normally used by LINQ to SQL and some other LINQ providers. They don't execute your expression directly by any means. They parse it as an expression tree and use the tree as input data to generate the equivalent SQL statement to be run on the server. It's not executed like a method and it's not considered an anonymous method. In that case, you can't define a local variable as it's not possible to parse the lambda as an expression tree.

Mehrdad Afshari
That won't work. You'll get the error "A lambda expression with a statement body cannot be converted to an expression tree."
BFree
The problem is that the asker didn't phrase the question correctly - he really wants to know about doing this in the context of a Linq to SQL query, not a Linq to Objects query. Sucks that we have to treat them differently, but there you have it.
Erik Forbes
"In that case [Linq to SQL], you can't define a local variable as it's not possible to parse it as an expression tree." - except you can, if you read my answer to this question.
Daniel Earwicker
Your assertion of no limitations is incorrect. There are a variety of limitations on lambda expressions/anonymous functions. For instance, try using base.Something in a C# anonymous function. Depending on your version you will get a warning or a failed compile
JaredPar
@Earwicker: In your example, you are not defining a local variable in a lambda expression body. By using let, you haven't defined a local variable in your lambda. I'm talking about lambda expressions not features in LINQ to SQL.
Mehrdad Afshari
@JaredPar: You can't use it in a normal static method too, it's not a limitation of an anonymous method.
Mehrdad Afshari
@Mehrdad - the effect of let is the same as a read-only local variable: it captures and names a computed value so it can be referred to within a scope with well-defined boundaries. The storage is implemented differently but logically it's the same, which is why it meets the needs of the questioner.
Daniel Earwicker
Yes, I didn't argue about that. His issue is hopefully resolved. But this doesn't change the fact that you can't declare local variable in an expression tree. I answered the question (title) from a generic point of view, not LINQ to SQL.
Mehrdad Afshari
+1  A: 

The Where method takes a Func so what you're passing in there in the second part ins't actually a method, but just a bool expression. My suggestion would be to have an actual method that returns a bool, that takes in the paremeters you need, and in your call to the Where method you just do something like this Where(p=> MyMethod(p,...))

BFree
+2  A: 

If you're using Linq to SQL, you won't be able to use Mehrdad Afshari's answer. Your LINQ expressions need to be Expression Trees, and those don't support the anonymous delegate syntax.

Neither will you be able to create your delegate elsewhere and call it from inside the lambda - Linq to SQL only allows certain operations to be performed in the body of the query, and calling a delegate isn't one of them.

Your best bet, assuming you're using Linq to SQL (as it appears given your example), is to bring down the count in one query, then capture the count variable in the query that requires the count.

Erik Forbes
A: 

I tried using a extension:

        public static Boolean Between(this int value, int from, int to)
        {
            return from <= value && value <= to;    
        }

But I got an error saying it wasn't possible translate this to SQL?

Niels Bosma
See my answer -- http://stackoverflow.com/questions/369334/c-is-it-possible-to-create-a-local-variable-in-a-anonymous-function#369365
Erik Forbes
+4  A: 

Yes, you can do exactly what you want, in Linq to objects and Linq to SQL.

There is a let in Linq, allowing you to give a name to an intermediate result in the middle of your query, just as you want to. Based on your example:

... = from e in linq 
      let count = (from p in db.Orders where p.EnquiryId == e.Id select p).Count()
      where (x <= count) && (count <= y)
      select e;

By the way, I think there was something syntactically erroneous about your original example, which is easier to spot when the count is just a name:

where (x <= count) && /* <= */ (count <= y);
Daniel Earwicker
This doesn't work, I get an syntax error saying that the query must end with a select. Note that I need to add several Where clauses so I can't end the query.
Niels Bosma
I added a 'select e' above to make the example correct. But what do you mean you can't end the query? You have to end it with something that states what it produces. You can put several conditions in the Where clause - just add more to the two already in there.
Daniel Earwicker
A: 

With a little background in Scheme you would know that 'let' is just syntax sugar for defining a lambda and invoking it.

So with that knowledge, lets see how it can be done.

(count => x <= count && count <= y)
  ((from p in db.Orders 
    where p.EnquiryId == e.Id 
    select p).Count())

As a bonus, it looks like Scheme too :)

Disclaimer: I did not test this snippet, but there is no reason it should not work. Personally, I would just use the 'let' construct provided in LINQ.

Update:

It does not work... :(

leppie
I assume you mean linq = linq.Where(e => ((count => (soldFrom <= count This still won't compile...
Niels Bosma
@leppie, the problem is that the question is about LINQ to SQL. Unfortunately this is only indicated by the tag, not anywhere in the title or description. The "let" keyword in LINQ is not the same thing as a directly invoked lambda. It's a Select that produces a sequence of input/output pairs.
Daniel Earwicker
A: 

I got a lot of good answers but none of them works for linq 2 sql, but surely there must be a solution?

Niels Bosma
A: 

Hi,

I've run into a similar problem. The solution is to create a custom expression tree generating method.

I asked my question on MSDN-forums. Please see the question and answer here: Reusing Where expressions.

This may give you an idea on how to proceed, but I must admit that custom expression trees are not for the faint-hearted ;-)

Torben Rahbek Koch