How to "let" in lambda expression? - linq

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.

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);

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

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));

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);

Related

Set the list value using linq

Hi I want to set the value in the list of objects that matches the given condition in the where clause.Is it possible?
Other work around is to get the list of objects using where clause and then iterate using for Or foreach loop and update the value.
listOfRequestAssigned.Where(x => x.RequestedNumber == CurrentRequest);
I have list listOfRequestAssigned of objects and want to update some propery of the objects that match my search criteria.
class Request
{
bool _requestCompleted;
int _requestedNumber;
public int RequestedNumber
{
get { return _requestedNumber; }
set { _requestedNumber = value; }
}
public bool RequestCompleted
{
get { return _requestCompleted; }
set { _requestCompleted = value; }
}
}
I want to update RequestCompleted property of all objects that match criteria using Linq
You can use ForEach in Linq
listOfRequestAssigned.Where(x => x.RequestedNumber == CurrentRequest).ToList().ForEach(x => x.RequestCompleted = true);
if you have more than one update to do,
listOfRequestAssigned.Where(x => x.RequestedNumber == CurrentRequest).ToList().ForEach(x => { x.RequestCompleted = true; x.OtherProperty = value; } );
Where(...) give you a query, not a Request or a List<Request>. Use FirstOrDefault() if you want to have one (or 0) result, or ToList() if you want to have a list of results on wich you can use ForEach().
In general Linq is a query- not an update tool, but you can use a foreach:
var currentRequests = listOfRequestAssigned
.Where(x => x.RequestedNumber == CurrentRequest);
foreach(var req in currentRequests)
{
req.RequestCompleted = true;
}
Since you have specific collection of type List, you can just use ForEach and a conditional set:
listOfRequestAssigned.Foreach(x => { if (x.RequestedNumber == CurrentRequest) x.RequestCompleted = true;}});
If you had a more generic collection IEnumerable, you can use Select in Linq to build a projection where property will be set as desired (original collection will be left untouched!):
listOfRequestAssigned
.Where(x => x.RequestedNumber == CurrentRequest)
.Select(x => { x.RequestCompleted = true; return x; })
You can use to assign boolean value by following on comparing time. This is the very simplest and smart way for bool property.
listOfRequestAssigned.ForEach(x => x.RequestCompleted = x.RequestedNumber
== CurrentRequest);

NHibernate Linq ToFuture and Max

I want the following two queries to be executed in single round trip to database so I'm using ToFutureValue.
var query = AsQueryable()
.Where(x => x.User == user);
var singinCount = query
.Where(x => x.ActionDate >= from && x.ActionDate <= to)
.ToFutureValue(q => q.Count());
var lastSignin = query
.Where(x => x.ActionName.ToLower() == Actions.Signin))
.ToFutureValue(q => q.Max(x => x.ActionDate));
The problem is that Count and Max returns result but not IQueryable. I have found the following extension that enables me to pass Count to ToFutureValue:
public static IFutureValue<TResult> ToFutureValue<TSource, TResult>(
this IQueryable<TSource> source, Expression<Func<IQueryable<TSource>, TResult>> selector)
where TResult : struct
{
var provider = (INhQueryProvider)source.Provider;
var method = ((MethodCallExpression)selector.Body).Method;
var expression = Expression.Call((Expression)null, method, source.Expression);
return (IFutureValue<TResult>)provider.ExecuteFuture(expression);
}
Now I need to adopt that extension for Max because I have to pass arguments to Max (.ToFutureValue(q => q.Max(x => x.ActionDate))). How can I do that?
as a workaround
var lastSignin = query
.Where(x => x.ActionName.ToLower() == Actions.Signin))
.OrderByDescending(x => x.ActionDate)
.Take(1)
.ToFutureValue();
It looks like there is appropriate patch in GitHub pull request
#120
And here is jira bug NH-3184
the following is code from pull #120 that works:
public static IFutureValue<TResult> ToFutureValue<T, TResult>(
this IQueryable<T> query, Expression<Func<IQueryable<T>, TResult>> selector)
{
var nhQueryable = query as QueryableBase<T>;
if (nhQueryable == null)
{
throw new NotSupportedException("Query needs to be of type QueryableBase<T>");
}
var provider = (INhQueryProvider) query.Provider;
var expression = ReplacingExpressionTreeVisitor.Replace(
selector.Parameters.Single(),
query.Expression,
selector.Body);
return (IFutureValue<TResult>) provider.ExecuteFuture(expression);
}

Integrating custom method into LINQ to Entities query

I have a custom method that performs some calculation on a set of data:
private int GetPercentages(int OriginalValue, int TotalValue)
{
var newValue = (int)Math.Round(((decimal)OriginalValue / (decimal)TotalValue) * 100);
return newValue;
}
I need to be able to run this method inside of a LINQ to Entities query:
var data = from SurveyResponseModel in db.SurveyResponseModels
group SurveyResponseModel by SurveyResponseModel.MemberId into resultCount
select new ResultsViewModel()
{
MemberId = resultCount.Key,
PatientFollowUpResult = db.SurveyResponseModels.Count(r => r.PatientFollowUp),
PatientFollowUpResultPct = GetPercentages(db.SurveyResponseModels.Count(r => r.PatientFollowUp),totalResponsesResult),
ChangeCodingPracticeResult = db.SurveyResponseModels.Count(r => r.ChangeCodingPractice),
};
I need to run this on about 20 more lines inside of the query so just sticking it inline doesn't seem like a great option. I understand that it needs to be converted into SQL syntax, but is there anything else like this that I can do?
You need to make a lambda expression that calculates the percentage like this:
Expression<Func<int, int, int>> calcPercentage =
(OriginalValue, TotalValue) => (int)Math.Round(((decimal)OriginalValue / (decimal)TotalValue) * 100);
And use it like this:
var data = from SurveyResponseModel in db.SurveyResponseModels.ToExpandable()
group SurveyResponseModel by SurveyResponseModel.MemberId into resultCount
select new ResultsViewModel()
{
MemberId = resultCount.Key,
PatientFollowUpResult = db.SurveyResponseModels.Count(r => r.PatientFollowUp),
PatientFollowUpResultPct = calcPercentage.Invoke(db.SurveyResponseModels.Count(r => r.PatientFollowUp), totalResponsesResult),
ChangeCodingPracticeResult = db.SurveyResponseModels.Count(r => r.ChangeCodingPractice),
};
More info about calling functions in LINQ queries here.

EF Code First selecting rows based on many to many relationship

I have the following code in my repository:
public PagedResult<Post> GetAllPublishedByTag(int tagId, int start, int max)
{
var query = Database.Set<Post>().Where(p => p.IsPublished)
.OrderByDescending(p => p.CreatedAt)
.Skip(start)
.Take(max);
int total = query.Count();
var result = query.ToList();
return new PagedResult<Post>(result, total);
}
This will give me all published posts. But what I want is selecting all published posts for a certain tag. My model is setup in such a way that tags have a many to many relationship to posts. I tried to slightly modify the above code but this did not work:
public PagedResult<Post> GetAllPublishedByTag(Tag tag, int start, int max)
{
var query = Database.Set<Post>().Where(p => p.Tags.Contains(tag) && p.IsPublished)
.OrderByDescending(p => p.CreatedAt)
.Skip(start)
.Take(max);
int total = query.Count();
var result = query.ToList();
return new PagedResult<Post>(result, total);
}
I would prefer to pass in the tagId (as per the first code example) as opposed to the tag object but not sure how to correctly write the LINQ statement.
var query = Database.Set<Post>().Where(p => p.Tags.Any(t => t.Id == tagId) && p.IsPublished)
.OrderByDescending(p => p.CreatedAt)
.Skip(start)
.Take(max);
Side Note: I believe you may have issues with your pagination, as the variable total is calculated after skip/take are called.

How to select decreasing sub-series with Linq

I have a list of prices ordered by date. I need to select all monotonously decreasing values. The following code works:
public static List<DataPoint> SelectDecreasingValues(List<DataPoint> dataPoints)
{
var ret = new List<DataPoint>(dataPoints.Count);
var previousPrice = dataPoints[0].Price;
for (int i = 0; i < dataPoints.Count; i++)
{
if (dataPoints[i].Price <= previousPrice)
{
ret.Add(dataPoints[i]);
previousPrice = dataPoints[i].Price;
}
}
return ret;
}
However, is there a shorter/cleaner way to accomplish it with Linq?
This code is equivalent:
previousPrice = dataPoints[0].Price;
var ret = dataPoints.Where(x => {
if(x.Price <= previousPrice)
{ previousPrice = x.Price; return true;}
return false;
}).ToList();
However, if you don't need to have a list, go with plain enumerables and drop the ToList at the end. That way you can make use of the deferred execution feature built into LINQ.
The following code is also equivalent:
DataPoint previous = dataPoints.FirstOrDefault();
var ret = dataPoints.Where(x => x.Price <= previous.Price)
.Select(x => previous = x).ToList();
This works because of the deferred execution in LINQ. For each item in dataPoints it will first execute the Where part and then the Select part and only then will it move to the second item in dataPoints.
You need to decide which version you want to use. The second one is not as intention revealing as the first one, because you need to know about the internal workings of LINQ.
public IEnumerable<T> WhereMonotonicDecreasing<T>(
IEnumerable<T> source,
Func<T, IComparable> keySelector)
{
IComparable key;
bool first = true;
foreach(T t in source)
{
if (first)
{
key = keySelector(t);
yield return t;
first = false;
}
else
{
IComparable newKey = keySelector(t);
if (newKey.CompareTo(key) < 0)
{
key = newKey;
yield return t;
}
}
}
}
Called by:
dataPoints.WhereMonotonicDecreasing(x => x.Price);
previousPrice = dataPoints[0];
dataPoints.Where(p => p.Price <= previousPrice.Price)
.Select(p => previousPrice = p);
You can then use .ToList() if you really need one.
How about (untested):
return dataPoints.Take(1)
.Concat(dataPoints.Skip(1)
.Zip(dataPoints,
(next, previous) =>
new { Next = next, Previous = previous })
.Where(a => a.Next.Price <= a.Previous.Price)
.Select(a => a.Next))
.ToList();
Essentially, this overlays a "one-deferred" version of the sequence over the sequence to produce "next, previous" tuples and then applies the relevant filters on those tuples. The Take(1) is to pick the first item of the sequence, which it appears you always want.
If you don't care for the readability of the variable names, you could shorten it to:
return dataPoints.Take(1)
.Concat(dataPoints.Skip(1)
.Zip(dataPoints, Tuple.Create)
.Where(a => a.Item1.Price <= a.Item2.Price)
.Select(a => a.Item1))
.ToList();

Resources