'IQueryable extension to group dynamicly by minutes ...and other
I wanted to create an IQueryable
extension to allow other developers to group entities by minutely interval but also by custom group key result.
My idea was to create a method with the following signature:
public static IQueryable<IGrouping<TKey, TSource>> GroupByMinutelyTimePeriode<TSource, TKey>(this IQueryable<TSource> source
, Expression<Func<DateTime, TSource, TKey>> keySelector
, Func<TSource, DateTime> timestampSelector
, int minutes)
{
Something that you can use just like this example:
var query = dataContext.Datas
.Where(d => d.Timestamp >= lowerTimestampRange && d.Timestamp < upperTimestampRange)
////// extension /////
.GroupByMinutelyTimePeriode((t, d) => new
{
DeviceId = d.DeviceId,
TimestampBoundary = t
}
, d => d.Date
, 15)
/////////////////////
.Select(g => new
{
DeviceId = g.Key.DeviceId,
Date = g.Key.TimestampBoundary,
Value = g.Sum(d => d.Value)
});
Within the extension something like this should happens (for sure not working because not translateable by linq-to-sql):
public static IQueryable<IGrouping<TKey, TSource>> GroupByMinutelyTimePeriode<TSource, TKey>(this IQueryable<TSource> source
, Func<DateTime, TSource, TKey> keySelector
, Func<TSource, DateTime> timestampSelector
, int minutes)
{
return source.GroupBy(d => keySelector(new DateTime(timestampSelector(d).Year
, timestampSelector(d).Month
, timestampSelector(d).Day
, timestampSelector(d).Hour
, timestampSelector(d).Minute / minutes * minutes, 0)
, d
));
}
I must say I completely failed to translate this into a proper working IQueryable
expression syntax. I try to understand how to work with Expressions from existing IQueryable
extensions like GroupBy
from GitHub.
Maybe someone can help me to find a good example.
Solution 1:[1]
It is possible, but instead of Func<,>
you have to pass Expression<Func<,>>
as parameters. Only in this case you can reuse selectors body.
Some magic with Expression Tree transformation:
public static class QueryableExtensions
{
public static IQueryable<IGrouping<TKey, TSource>> GroupByMinutlyTimePeriode<TSource, TKey>(this IQueryable<TSource> source
, Expression<Func<DateTime, TSource, TKey>> keySelector
, Expression<Func<TSource, DateTime>> timestampSelector
, int minutes)
{
Expression<Func<DateTime, int, DateTime>> dateTimeTemplate = (t, m) => new DateTime(t.Year
, t.Month
, t.Day
, t.Hour
, t.Minute / m * m, 0);
var entityParam = keySelector.Parameters[1];
var dateTimeBody =
ExpressionReplacer.GetBody(dateTimeTemplate, ExpressionReplacer.GetBody(timestampSelector, entityParam), Expression.Constant(minutes));
var keyBody = ExpressionReplacer.GetBody(keySelector, dateTimeBody, entityParam);
var keyLambda = Expression.Lambda<Func<TSource, TKey>>(keyBody, entityParam);
return source.GroupBy(keyLambda);
}
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression node)
{
if (node != null && _replaceMap.TryGetValue(node, out var replacement))
return replacement;
return base.Visit(node);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |