[英]How do I combine two Expressions?
我正在嘗試構建一個將應用於IQueryable集合的表達式。
我可以構建一個這樣的表達式:
[TestClass]
public class ExpressionTests
{
private IQueryable<MyEntity> entities;
private class MyEntity
{
public string MyProperty { get; set; }
}
[TestInitialize]
public void Setup()
{
entities = new[]
{
new MyEntity {MyProperty = "first"},
new MyEntity {MyProperty = "second"}
}.AsQueryable();
}
[TestMethod]
public void TestQueryingUsingSingleExpression()
{
Expression<Func<MyEntity, bool>> expression = e => e.MyProperty.Contains("irs");
Assert.AreEqual(1, entities.Where(expression).Count());
}
}
現在我想分開表達式的兩個部分:
[TestMethod]
public void TestQueryingByCombiningTwoExpressions()
{
Expression<Func<MyEntity, string>> fieldExpression = e => e.MyProperty;
Expression<Func<string, bool>> operatorExpression = e => e.Contains("irs");
// combine the two expressions somehow...
Expression<Func<MyEntity, bool>> combinedExpression = ???;
Assert.AreEqual(1, entities.Where(combinedExpression).Count());
}
有關如何做到這一點的任何建議?
Btw將解析表達式的提供程序是Linq for NHibernate。
看看你的兩個表達式樹:
| | Lambda Lambda / \ / \ / \ / \ Property Parameter x Call Parameter y / \ / | \ / \ / | \ x MyProperty EndsWidth y Constant | "5"
您需要創建一個如下所示的新樹:
| Lambda / \ / \ Call Parameter z / | \ / | \ EndsWith | Constant | \ Property "5" / \ / \ z MyProperty
您可以輕松地看到新樹的哪些部分來自哪個原始樹。
要創建樹,可以使用第二個lambda表達式(Call)的主體,並將所有出現的y
替換為第一個lambda表達式(Property)的主體,並將所有出現的x
替換為z
。 然后將結果包裝在帶有參數z
的新lambda表達式中。
您可以使用ExpressionVisitor類重寫樹,使用Expression.Lambda方法創建新的lambda表達式。
這取決於提供商支持的內容; 如果它支持子表達式(LINQ-to-SQL沒有,EF不支持;我不知道NH),那么:
var combinedExpression = Expression.Lambda<Func<MyEntity, bool>>(
Expression.Invoke(operatorExpression, fieldExpression.Body),
fieldExpression.Parameters);
但是,如果不是,則需要使用ExpressionVisitor
來合並它們。
根據dtb和Marc的建議,我使用ExpressionVisitor重寫表達式樹,這是我能夠管理的最干凈的:
public class ExpressionBuilder<T> : ExpressionVisitor where T : class
{
private Expression fieldExpressionBody;
protected override Expression VisitParameter(ParameterExpression node)
{
return fieldExpressionBody;
}
public Expression<Func<T, bool>> Build(
Expression<Func<T, string>> fieldExpression,
Expression<Func<string, bool>> operatorExpression)
{
fieldExpressionBody = fieldExpression.Body;
Expression newExpressionBody = Visit(operatorExpression.Body);
return Expression.Lambda<Func<T, bool>>(newExpressionBody, fieldExpression.Parameters[0]);
}
}
並在我的單元測試中使用它:
[TestMethod]
public void TestQueryingByCombiningTwoExpressions()
{
Expression<Func<MyEntity, string>> fieldExpression = e => e.MyProperty;
Expression<Func<string, bool>> operatorExpression = o => o.Contains("irs");
var builder = new ExpressionBuilder<MyEntity>();
Expression<Func<MyEntity, bool>> combinedExpression = builder.Build(fieldExpression, operatorExpression);
Assert.AreEqual(1, entities.Where(combinedExpression).Count());
Assert.AreEqual("e => e.MyProperty.Contains(\"irs\")", combinedExpression.ToString());
}
完成之后,這是一個可以使用2,3或n個表達式的版本
public class ExpressionMerger : ExpressionVisitor
{
Expression CurrentParameterExpression { get; set; }
public Expression<Func<TIn, TOut>> Merge<TIn, TA, TOut>(Expression<Func<TIn, TA>> inner, Expression<Func<TA, TOut>> outer)
{
return MergeAll<TIn, TOut>(inner, outer);
}
public Expression<Func<TIn, TOut>> Merge<TIn, TA, TB, TOut>(Expression<Func<TIn, TA>> inner, Expression<Func<TA, TB>> transition, Expression<Func<TB, TOut>> outer)
{
return MergeAll<TIn, TOut>(inner, transition, outer);
}
protected Expression<Func<TIn, TOut>> MergeAll<TIn, TOut>(params LambdaExpression[] expressions)
{
CurrentParameterExpression = expressions[0].Body;
foreach (var expression in expressions.Skip(1))
{
CurrentParameterExpression = Visit(expression.Body);
}
return Expression.Lambda<Func<TIn, TOut>>(CurrentParameterExpression, expressions[0].Parameters[0]);
}
protected override Expression VisitParameter(ParameterExpression node)
{
//replace current lambda parameter with ~previous lambdas
return CurrentParameterExpression;
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.