C# 如何组合这两个表达式?
我正在尝试建立一个表达式,该表达式将应用于IQueryable集合 我可以构建如下表达式:C# 如何组合这两个表达式?,c#,linq,linq-to-nhibernate,C#,Linq,Linq To Nhibernate,我正在尝试建立一个表达式,该表达式将应用于IQueryable集合 我可以构建如下表达式: [TestClass] public class ExpressionTests { private IQueryable<MyEntity> entities; private class MyEntity { public string MyProperty { get; set; } } [TestInitialize]
[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());
}
}
|
Lambda
/ \
/ \
Call Parameter z
/ | \
/ | \
EndsWith | Constant
| \
Property "5"
/ \
/ \
z MyProperty
[TestClass]
公共类表达式测试
{
私人可转让实体;
私有类MyEntity
{
公共字符串MyProperty{get;set;}
}
[测试初始化]
公共作废设置()
{
实体=新[]
{
新MyEntity{MyProperty=“first”},
新MyEntity{MyProperty=“second”}
}.AsQueryable();
}
[测试方法]
使用SingleExpression()的公共void测试查询
{
Expression=e=>e.MyProperty.Contains(“irs”);
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());
}
[TestMethod]
通过组合WoExpressions()进行的公共无效测试查询
{
表达式fieldExpression=e=>e.MyProperty;
表达式运算符Expression=e=>e.Contains(“irs”);
//以某种方式将这两个表达式组合起来。。。
表达式组合表达式=???;
AreEqual(1,entities.Where(combinedExpression.Count());
}
有没有关于我该怎么做的建议
顺便说一句,将解析表达式的提供程序是Linq for NHibernate。查看两个表达式树: | | Lambda Lambda / \ / \ / \ / \ Property Parameter x Call Parameter y / \ / | \ / \ / | \ x MyProperty EndsWidth y Constant | "5" | | 兰姆达兰姆达 / \ / \ / \ / \ 属性参数x调用参数y / \ / | \ / \ / | \ x MyProperty EndsWidth y常量 | "5" 您需要创建一个如下所示的新树:
[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());
}
}
|
Lambda
/ \
/ \
Call Parameter z
/ | \
/ | \
EndsWith | Constant
| \
Property "5"
/ \
/ \
z MyProperty
|
兰姆达
/ \
/ \
调用参数z
/ | \
/ | \
EndsWith |常数
| \
财产“5”
/ \
/ \
Myz属性
您可以很容易地看到新树的哪些部分来自哪个原始树
要创建树,请获取第二个lambda表达式(调用)的主体,并将所有出现的y
替换为第一个lambda表达式(属性)的主体,将所有出现的x
替换为z
。然后使用参数z
将结果包装在一个新的lambda表达式中
可以使用重写树,使用创建新的lambda表达式。这取决于提供程序支持的内容;如果它支持子表达式(LINQ到SQL,EF不支持;我不知道NH),那么:
var combinedExpression=Expression.Lambda(
Expression.Invoke(operatorExpression,fieldExpression.Body),
字段表达式(参数);
但是,如果没有,您将需要使用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]);
}
}
公共类ExpressionBuilder:ExpressionVisitor其中T:class
{
私有表达式字段表达式体;
受保护的重写表达式VisitParameter(ParameterExpression节点)
{
返回字段表达式体;
}
公共表达构建(
表达式字段表达式,
表达式运算符(表达式)
{
fieldExpressionBody=fieldExpression.Body;
表达式newExpressionBody=Visit(operatiorexpression.Body);
返回表达式.Lambda(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());
}
[TestMethod]
通过组合WoExpressions()进行的公共无效测试查询
{
表达式fieldExpression=e=>e.MyProperty;
表达式运算符Expression=o=>o.Contains(“irs”);
var builder=new ExpressionBuilder();
表达式combinedExpression=builder.Build(fieldExpression,operatorExpression);
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;
}
}
公共类expressionmerge:ExpressionVisitor
{
表达式CurrentParameterExpression{get;set;}
公共表达式合并(表达式内部、表达式外部)
{
返回所有(内部、外部);
}
公共表达式合并(表达式内部、表达式转换、表达式外部)
{
返回MergeAll(内部、过渡、外部);
}
受保护的表达式MergeAll(参数LambdaExpression[]表达式)
{
CurrentParameterExpression=表达式[0]。正文;
foreach(表达式中的var表达式。跳过(1))
{
CurrentParameterExpression=访问(expression.Body);
}
返回表达式.Lambda(CurrentParameterExpression,表达式[0]。参数[0]);
}
受保护的重写表达式VisitParameter(ParameterExpression节点)
{
//将当前lambda参数替换为~previous lambda
返回CurrentParameterExpression;
}
}
}谢谢dtb,很棒的图表,+1。我不认为您可以提供一个适用于我的示例代码的示例?我整理了我的示例代码,以便它可以作为独立的单元测试运行。当然可以。