将表达式(Expression< Func< TIn,TOut>>和Expression< Func< TOut&gt ;, bool>)组合起来) [英] Combine Expression (Expression<Func<TIn,TOut>> with Expression<Func<TOut, bool>>)

查看:82
本文介绍了将表达式(Expression< Func< TIn,TOut>>和Expression< Func< TOut&gt ;, bool>)组合起来)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个表达式:

Expression<Func<T1, T2>> 
Expression<Func<T2, bool>> 

我想将它们结合起来并得到类型为Expression< Func< T1,bool>>的新表达式.以便可以在Entity Framework LINQ中使用.

And I want to combine these and get a new expression of type Expression<Func<T1, bool>> so that it can be used in Entity Framework LINQ.

我在Expression.Invoke()的帮助下将它们组合在一起,但是它不起作用.

I combine them with help of Expression.Invoke(), but it doesn't work.

//Extensinon method
public static Expression<Func<T1, bool>> Compose<T1, T2>(this Expression<Func<T1, T2>> convertExpr, Expression<Func<T2, bool>> predicate) 
    => Expression.Lambda<Func<T1, bool>>(Expression.Invoke(predicate, convertExpr.Body), convertExpr.Parameters.First());
...
Expression<Func<One, Two>> convert;
Expression<Func<Two, bool>> predicate;
Expression<Func<One, bool>> filter=convert.Compose(predicate);

// Works fine
List<One> lst;
lst.AsQueryable().Where(filter);

DbSet<One> src;
// In next line throws runtime NotSupportedException: The LINQ expression node type 'Invoke' is not supported in LINQ to Entities
src.Where(filter);

更新

例如:

public class Book
{ 
  public int Id {get; protected set;}
  public string Name {get; protected set;}
  public virtual Genre {get; protected set;}
}

public class Genre
{
  public int Id {get; protected set;}
  public string Name {get; protected set;}
}
...
Expression<Func<Book, Genre>> convert = b=>b.Genre;
Expression<Func<Genre, bool>> predicate = g=>g.Name=="fantasy";
// Need: b=>b.Genre=="fantasy"
Expression<Func<Genre, bool>> filter=convert.Compose(predicate);

// Works fine
List<Book> lst;
lst.AsQueryable().Where(filter);

DbSet<Book> books;
// In next line throws runtime NotSupportedException: The LINQ expression node type 'Invoke' is not supported in LINQ to Entities
books.Where(filter);

推荐答案

使用通用的ExpressionVisitor将另一个Expression替换为我的标准Compose函数(我认为是更常见的数学顺序),用一个LambdaExpressionBody替换另一个参数:

Using the common ExpressionVisitor for replacing one Expression with another, my standard Compose function (in the more common mathematical order, I think), substitutes the Body of one LambdaExpression for the parameter in another:

public static class ExpressionExt {
    // Compose: (y => f(y)).Compose(x => g(x)) -> x => f(g(x))
    /// <summary>
    /// Composes two LambdaExpression into a new LambdaExpression
    /// </summary>
    /// <param name="Tpg">Type of parameter to gFn, and type of parameter to result lambda.</param>
    /// <param name="Tpf">Type of result of gFn and type of parameter to fFn.</param>
    /// <param name="TRes">Type of result of fFn and type of result of result lambda.</param>
    /// <param name="fFn">The outer LambdaExpression.</param>
    /// <param name="gFn">The inner LambdaExpression.</param>
    /// <returns>LambdaExpression representing outer composed with inner</returns>
    public static Expression<Func<Tpg, TRes>> Compose<Tpg, Tpf, TRes>(this Expression<Func<Tpf, TRes>> fFn, Expression<Func<Tpg, Tpf>> gFn) =>
        Expression.Lambda<Func<Tpg, TRes>>(fFn.Body.Replace(fFn.Parameters[0], gFn.Body), gFn.Parameters[0]);

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

有了这个功能,您的示例就很简单:

With this available, your example is straightforward:

Expression<Func<One, Two>> convert = p1 => new Two(p1);
Expression<Func<Two, bool>> predicate = p2 => p2 == new Two();
Expression<Func<One, bool>> filter = predicate.Compose(convert);

但是,尤其是对于EF表达式,在参数可能为null的情况下,最好使用我的Invoke替代品来处理null传播.它还使用与上述相同的Replace ExpressionVisitor:

However, especially with EF expressions, it may be preferable to use my replacement for Invoke, which handles null propogation, in cases where your arguments may be null. It also uses the same Replace ExpressionVisitor as above:

public static class ExpressionExt2 {   
    public static Expression PropagateNull(this Expression orig) => new NullVisitor().Visit(orig);

    // Apply: (x => f).Apply(args)
    /// <summary>
    /// Substitutes an array of Expression args for the parameters of a lambda, returning a new Expression
    /// </summary>
    /// <param name="e">The original LambdaExpression to "call".</param>
    /// <param name="args">The Expression[] of values to substitute for the parameters of e.</param>
    /// <returns>Expression representing e.Body with args substituted in</returns>
    public static Expression Apply(this LambdaExpression e, params Expression[] args) {
        var b = e.Body;

        foreach (var pa in e.Parameters.Zip(args, (p, a) => (p, a)))
            b = b.Replace(pa.p, pa.a);

        return b.PropagateNull();
    }
}

/// <summary>
/// ExpressionVisitor to replace a null.member Expression with a null
/// </summary>
public class NullVisitor : System.Linq.Expressions.ExpressionVisitor {
    public override Expression Visit(Expression node) {
        if (node is MemberExpression nme && nme.Expression is ConstantExpression nce && nce.Value == null)
            return Expression.Constant(null, nce.Type.GetMember(nme.Member.Name).Single().GetMemberType());
        else
            return base.Visit(node);
    }
}

public static class MeberInfoExt {
    public static Type GetMemberType(this MemberInfo member) {
        switch (member) {
            case FieldInfo mfi:
                return mfi.FieldType;
            case PropertyInfo mpi:
                return mpi.PropertyType;
            case EventInfo mei:
                return mei.EventHandlerType;
            default:
                throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member));
        }
    }    
}

给出Apply,您的Compose就是:

public static Expression<Func<T1, bool>> Compose<T1, T2>(this Expression<Func<T1, T2>> convertExpr, Expression<Func<T2, bool>> predicate)
    => Expression.Lambda<Func<T1, bool>>(predicate.Apply(convertExpr.Body), convertExpr.Parameters.First());

这篇关于将表达式(Expression&lt; Func&lt; TIn,TOut&gt;&gt;和Expression&lt; Func&lt; TOut&gt ;, bool&gt;)组合起来)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆