Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "let" in lambda expression?

How can I rewrite this linq query to Entity on with lambda expression?
I want to use let keyword or an equivalent in my lambda expression.

var results = from store in Stores
              let AveragePrice =  store.Sales.Average(s => s.Price)
              where AveragePrice < 500 && AveragePrice > 250

For some similar questions like what is commented under my question, it's suggested to

.Select(store=> new { AveragePrice = store.Sales.Average(s => s.Price), store})

which will calculate AveragePrice for each item, while in Query style I mentioned, let expression prevents to calculate average many times.

like image 449
Reza Owliaei Avatar asked Feb 11 '12 12:02

Reza Owliaei


People also ask

Can we write a Parameterless lambda expression?

No, there isn't. Lambda expressions are optimised (in terms of syntax) for the single parameter case. I know that the C# team feels your pain, and have tried to find an alternative. Whether there ever will be one or not is a different matter.

What does => mean in LINQ?

The => operator can be used in two ways in C#: As the lambda operator in a lambda expression, it separates the input variables from the lambda body. In an expression body definition, it separates a member name from the member implementation.

What is let clause in LINQ?

The Let keyword allows you to create a range variable and initialized with the result of the query expression and then you are allowed to use that variable with the upcoming clause in the same query.

How do you read a lambda expression?

All lambda expressions use the lambda operator =>, which is read as "goes to". The left side of the lambda operator specifies the input parameters (if any) and the right side hold the expression or statement block. The lambda expression x => x * 2 is read "x goes to 2 times x." This reduced the no.


4 Answers

So, you can use the extension method syntax, which would involve one lambda expression more than you are currently using. There is no let, you just use a multi-line lambda and declare a variable:

var results = Stores.Where(store => 
{
    var averagePrice = store.Sales.Average(s => s.Price);
    return averagePrice > 250 && averagePrice < 500;
});

Note that I changed the average price comparison, because yours would never return any results (more than 500 AND less that 250).

The alternative is

var results = Stores.Select(store => new { Store = store, AveragePrice = store.Sales.Average(s => s.Price})
    .Where(x => x.AveragePrice > 250 && x.AveragePrice < 500)
    .Select(x => x.Store);
like image 199
Jay Avatar answered Oct 18 '22 03:10

Jay


Basically, you need to use Select and an anonymous type to add the average to your object, followed by the rest of your statement.

Not tested but it should look like this:

Stores.Select(
    x => new { averagePrice = x.Sales.Average(s => s.Price), store = x})
.Where(y => y.averagePrice > 500 && y.averagePrice < 250)
.Select(x => x.store);

Warning: This works well for Linq-to-Entities, but be careful with these constructs in Linq-to-Objects. Using let creates a new anonymous type per object in your collection, it consumes a lot of memory with large collections.

Look here for details: Let in chained extension methods

like image 27
Yoeri Avatar answered Oct 18 '22 03:10

Yoeri


Another option is to define this extension method:

public static class Functional
{
    public static TResult Pipe<T, TResult>(this T value, Func<T, TResult> func)
    {
        return func(value);
    }
}    

Then write your query like this:

var results = Stores
    .Where(store => store.Sales.Average(s => s.Price)
        .Pipe(averagePrice => averagePrice < 500 && averagePrice > 250));
like image 4
Timothy Shields Avatar answered Oct 18 '22 04:10

Timothy Shields


We can avoid the overhead of the lambda used in all the other answers with an inline out declaration:

public static class FunctionalExtensions
{
    public static T Assign<T>(this T o, out T result) =>
        result = o;
}

And call it like this

var results = Stores
    .Where(store => store.Sales
        .Average(s => s.Price)
        .Assign(out var averagePrice) < 500 && averagePrice > 250);
like image 1
daw Avatar answered Oct 18 '22 05:10

daw