2つの式を組み合わせると(式<のFunc >)
-
19-08-2019 - |
質問
私はExpression<Func<T, bool>>
タイプの2つの式を持っていると私はORに撮りたい、ANDまたはNOTこれらのと同じタイプの新しい表現を取得
Expression<Func<T, bool>> expr1;
Expression<Func<T, bool>> expr2;
...
//how to do this (the code below will obviously not work)
Expression<Func<T, bool>> andExpression = expr AND expr2
解決
さて、あなたは論理式を組み合わせることExpression.AndAlso
/ OrElse
などを使用することができますが、問題はパラメータです。あなたはexpr1とexpr2のと同じでParameterExpression
で作業していますか?もしそうなら、それは簡単です:
var body = Expression.AndAlso(expr1.Body, expr2.Body);
var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);
これは、単一の操作を否定するためにうまく機能します:
static Expression<Func<T, bool>> Not<T>(
this Expression<Func<T, bool>> expr)
{
return Expression.Lambda<Func<T, bool>>(
Expression.Not(expr.Body), expr.Parameters[0]);
}
それ以外の場合は、LINQプロバイダによっては、あなたがそれらを結合することができるかもしれません。Invoke
:
// OrElse is very similar...
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.AndAlso(
Expression.Invoke(left, param),
Expression.Invoke(right, param)
);
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return lambda;
}
どこかが、私はいくつかのコード持っている<=>の必要性を削除するには、ノードを置き換える表現ツリーを再書き込みますが、それは非常に長いです(と私は...それをしたところ、私は覚えていないことができます)
<時間> 最も簡単なルートを選ぶ一般化バージョン:
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
// need to detect whether they use the same
// parameter instance; if not, they need fixing
ParameterExpression param = expr1.Parameters[0];
if (ReferenceEquals(param, expr2.Parameters[0]))
{
// simple version
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(expr1.Body, expr2.Body), param);
}
// otherwise, keep expr1 "as is" and invoke expr2
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(
expr1.Body,
Expression.Invoke(expr2, param)), param);
}
4.0、.NETからスタート。あなたはEFが安全である式を構築することができますExpressionVistorクラスがあります。
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}
他のヒント
あなたは論理式を組み合わせることExpression.AndAlso / OrElseを使用することができますが、ParameterExpressionsが同じであることを確認する必要があります。
私はEFと PredicateBuilder のように私が作った私自身の呼び出しに頼ることなく、そのIとのトラブルを抱えていましたこのように使用することができます:
var filterC = filterA.And(filterb);
私のPredicateBuilderのソース・コード:
public static class PredicateBuilder {
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
}
そしてラムダにパラメータを置換するユーティリティクラスます:
internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor {
public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node) {
Expression newValue;
if (subst.TryGetValue(node, out newValue)) {
return newValue;
}
return node;
}
}
ジョーAlbahari(簡単に言えばとLINQPadにC#3.0の著者)と共にAND及びOR関数に使用することができるPredicateBuilderというユーティリティを書いた。
http://www.albahari.com/nutshell/predicatebuilder.aspxする
それは機能に動作しますが、あなたがそれをチェックアウトし、それがどのように動作するかを見ることができるようにそれはオープンソースです。
、あなたは最初の式におけるパラメータによって、第2の式でパラメータを置き換えるためにExpressionVisitorを使用することができます。
class ParameterUpdateVisitor : ExpressionVisitor
{
private ParameterExpression _oldParameter;
private ParameterExpression _newParameter;
public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (object.ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
static Expression<Func<T, bool>> UpdateParameter<T>(
Expression<Func<T, bool>> expr,
ParameterExpression newParameter)
{
var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
var body = visitor.Visit(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
[TestMethod]
public void ExpressionText()
{
string text = "test";
Expression<Func<Coco, bool>> expr1 = p => p.Item1.Contains(text);
Expression<Func<Coco, bool>> expr2 = q => q.Item2.Contains(text);
Expression<Func<Coco, bool>> expr3 = UpdateParameter(expr2, expr1.Parameters[0]);
var expr4 = Expression.Lambda<Func<Recording, bool>>(
Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]);
var func = expr4.Compile();
Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" }));
}
私は1点の以上ののPredicateBuilder のへの改善とExpressionVisitor
解決策を提案します。私はそれを呼ばUnifyParametersByName
、あなたは私のMITライブラリでそれを見つけることができます: LinqExprHelper に。それはarbitaryラムダ式を組み合わせることができます。通常の質問は、述語式について尋ねているが、この考え方は同様に、投影式に拡張します。
次のコードは、インラインラムダを使用して、複雑なパラメータ化表現を作成する方法を採用ExprAdres
。この複雑な式はLinqExprHelper
ミニライブラリーのおかげで一度だけコード化され、その後、再利用されます。
public IQueryable<UbezpExt> UbezpFull
{
get
{
System.Linq.Expressions.Expression<
Func<UBEZPIECZONY, UBEZP_ADRES, UBEZP_ADRES, UbezpExt>> expr =
(u, parAdrM, parAdrZ) => new UbezpExt
{
Ub = u,
AdrM = parAdrM,
AdrZ = parAdrZ,
};
// From here an expression builder ExprAdres is called.
var expr2 = expr
.ReplacePar("parAdrM", ExprAdres("M").Body)
.ReplacePar("parAdrZ", ExprAdres("Z").Body);
return UBEZPIECZONY.Select((Expression<Func<UBEZPIECZONY, UbezpExt>>)expr2);
}
}
そして、これは部分式の建物のコードです:
public static Expression<Func<UBEZPIECZONY, UBEZP_ADRES>> ExprAdres(string sTyp)
{
return u => u.UBEZP_ADRES.Where(a => a.TYP_ADRESU == sTyp)
.OrderByDescending(a => a.DATAOD).FirstOrDefault();
}
私は何を達成しようとしたことは、コピー&ペーストをする必要がなく、とてもきれいですインラインラムダを、使用する機能とパラメータ化クエリを実行することでした。これらすべてのヘルパー表現のものがなければ、私は一度に全体のクエリを作成することを余儀なくされるだろう。
私は同じ結果を達成するために必要ですが、(型が知られていなかったとして)、より汎用的なものを使用して。絞りかすの答えのおかげで最終的に私は達成しようとしていたものを考え出しI:
public static LambdaExpression CombineOr(Type sourceType, LambdaExpression exp, LambdaExpression newExp)
{
var parameter = Expression.Parameter(sourceType);
var leftVisitor = new ReplaceExpressionVisitor(exp.Parameters[0], parameter);
var left = leftVisitor.Visit(exp.Body);
var rightVisitor = new ReplaceExpressionVisitor(newExp.Parameters[0], parameter);
var right = rightVisitor.Visit(newExp.Body);
var delegateType = typeof(Func<,>).MakeGenericType(sourceType, typeof(bool));
return Expression.Lambda(delegateType, Expression.Or(left, right), parameter);
}
私はそれが、これは正常に動作していないと思いますか?
Func<T, bool> expr1 = (x => x.Att1 == "a");
Func<T, bool> expr2 = (x => x.Att2 == "b");
Func<T, bool> expr1ANDexpr2 = (x => expr1(x) && expr2(x));
Func<T, bool> expr1ORexpr2 = (x => expr1(x) || expr2(x));
Func<T, bool> NOTexpr1 = (x => !expr1(x));