如何在Entity Framework中将一个C#表达式嵌套到另一个C#表达式中使用?

3
假设我有一些C#代码看起来像这样:

最初的回答

var query1 = query.Where(x => x.BirthDate > now);
var query2 = query.Where(x => x.EnrollmentDate > now);
var query3 = query.Where(x => x.GraduationDate > now);

实际代码更加复杂,但我使用了一个简单的例子。我将这段代码传递给Entity Framework。

假设我看到这段代码并说:“这不够DRY”,然后我写了一个像这样的函数。

最初的回答:

原始代码比较繁琐,但我使用了一个简单的示例。我将这段代码传递给Entity Framework。

假设我看到这段代码并认为:“这段代码不够简洁”,然后我编写了以下函数。

public IQueryable<Student> FilterAfterDate(IQueryable<Student> query,
    Expression<Func<Student, DateTime>> GetDateExpression, DateTime now)
{
    return query.Where(x => GetDateExpression(x) > now);
}

在运行时,这会产生以下错误:

最初的回答:

The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

最初的回答:我不知道具体细节,但我相信解决这个问题的方法可能是将我的FilterAfterDate,它是Expression<Func<Student, DateTime>>,与日期比较结合起来,生成一个类型为Expression<Func<Student, bool>>的表达式,以传递给Where函数,但我不知道该如何做到这一点。

谢谢,@KirkWoll。我已经修复了。 - Vivian River
但长话短说,你需要进行一些表达式树操作。我有一个用于组合EF映射表达式的库,但它不适用于你的特定需求。这里有一个重要的类,不过你需要查看一些附属文件才能完整了解情况。 - Kirk Woll
LINQKit 是处理这个问题最常见的方法,除了自己编写代码之外。我的 EF 实现的 full outer join 包括一个专门为 EF 设计的 Invoke 替代方法,但它被包含在连接代码中,可能难以理解。我可以发布一些更简单的内容。 - NetMage
听起来像是我正在寻找的东西。可惜这样一个看似简单的任务竟然需要这么一件事情。你愿意提供一个使用LINQKit来展示如何实现我想要的功能的答案吗? - Vivian River
我更喜欢自己编写代码,尽管我在思考如果你必须将 now 传递给你的 FilterAfterDate 函数,那么在这个简单的示例中,与原始代码相比并没有真正获得任何优势,只是将 > 替换为 , - NetMage
2个回答

1
使用 LINQKit,你可以像这样编写你的方法(我更喜欢作为扩展方法):
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);
}

然后像这样使用:

var q1 = query.FilterAfterDate(q => q.BirthDate, now);
var q2 = query.FilterAfterDate(q => q.EnrollmentDate, now);
var q3 = query.FilterAfterDate(q => q.GraduationDate, now);

要自己实现,只需要使用一个常见的ExpressionVisitor来进行替换:
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);
}

现在,有了可用的Replace函数,您可以创建一个lambda模板,并将其用于替换Expression参数。
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);
    }
}

而且你使用它的方式和LINQKit一样。


-1
这是我的解决方案:
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);
    }
}

测试:

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"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接