Suppose that I have some C# code that looks like this:
var query1 = query.Where(x => x.BirthDate > now);
var query2 = query.Where(x => x.EnrollmentDate > now);
var query3 = query.Where(x => x.GraduationDate > now);
The actual code is more complicated, but I'm using a simple example. I'm passing this code to Entity Framework.
Suppose I look at this and say, "This is un-DRY", and then I write a function like this.
public IQueryable<Student> FilterAfterDate(IQueryable<Student> query,
Expression<Func<Student, DateTime>> GetDateExpression, DateTime now)
{
return query.Where(x => GetDateExpression(x) > now);
}
At runtime, this produces the following error:
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
I don't know the particulars, but I believe that the solution to this probably is to somehow take my FilterAfterDate
which is Expression<Func<Student, DateTime>>
and somehow combine that with the datetime comparison to produce an expression of type Expression<Func<Student, bool>>
to pass to the Where
function, but I don't know how to do this.
Using LINQKit , you can write your method (I prefer as an extension method) like so:
public static class StudentExt {
public static IQueryable<Student> FilterAfterDate(this IQueryable<Student> query,
Expression<Func<Student, DateTime>> GetDateExpression, DateTime now)
=> query.AsExpandable().Where(x => GetDateExpression.Invoke(x) > now);
}
And use it like so:
var q1 = query.FilterAfterDate(q => q.BirthDate, now);
var q2 = query.FilterAfterDate(q => q.EnrollmentDate, now);
var q3 = query.FilterAfterDate(q => q.GraduationDate, now);
To roll your own, you just use a common ExpressionVisitor
that does a replace:
public static class ExpressionExt {
/// <summary>
/// Replaces an Expression (reference Equals) with another Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
}
/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
Now, with Replace
available, you can create a lambda template and use it to substitute in the Expression
parameter:
public static class StudentExt {
public static IQueryable<Student> FilterAfterDate(this IQueryable<Student> query,
Expression<Func<Student, DateTime>> GetDateExpression, DateTime now) {
Expression<Func<DateTime,bool>> templateFn = x => x > now;
var filterFn = Expression.Lambda<Func<Student,bool>>(templateFn.Body.Replace(templateFn.Parameters[0], GetDateExpression.Body), GetDateExpression.Parameters);
return query.Where(filterFn);
}
}
And you use it the same as with LINQKit.
Here's my solution:
public static class MyExtensions
{
public static MethodInfo GetMethod<TSource, TResult>(this Expression<Func<TSource, TResult>> lambda)
=> (lambda?.Body as MethodCallExpression)?.Method ?? throw new ArgumentException($"Not a {nameof(MethodCallExpression)}.");
private static readonly MethodInfo _miWhere = GetMethod((IQueryable<int> q) => q.Where(x => false)).GetGenericMethodDefinition();
public static IQueryable<TSource> WhereGreaterThan<TSource, TCompare>(this IQueryable<TSource> source, Expression<Func<TSource, TCompare>> selector, Expression<Func<TCompare>> comparand)
{
var predicate = Expression.Lambda<Func<TSource, bool>>(Expression.GreaterThan(selector.Body, comparand.Body), selector.Parameters[0]);
var where = Expression.Call(_miWhere.MakeGenericMethod(typeof(TSource)), source.Expression, predicate);
return source.Provider.CreateQuery<TSource>(where);
}
}
Test:
var now = DateTime.Now;
var result = new[]
{
new { Item = "Past", Date = now.AddDays(-1) },
new { Item = "Future", Date = now.AddDays(1) }
}
.AsQueryable()
.WhereGreaterThan(x => x.Date, () => now)
.Select(x => x.Item)
.Single();
// "Future"
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.