如何在linq中追加表达式?
How to append expressions in linq?
网络核心应用。我实现了一个通用的存储库模式。我正在尝试实现一些过滤功能。我有以下代码。
var param = Expression.Parameter(typeof(SiteAssessmentRequest), "x");
Expression<Func<SiteAssessmentRequest, bool>> query;
query = x => x.CreatedBy == request.Userid || x.AssignedTo == request.Userid;
Expression body = Expression.Invoke(query, param);
if (request.Client != null && request.Client.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.Client.Contains(x.Client);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
if (request.CountryId != null && request.CountryId.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.CountryId.Contains(x.CountryId);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
if (request.SiteName != null && request.SiteName.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.SiteName.Contains(x.SiteName);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
if (request.Status != null && request.Status.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.Status.Contains(x.Status);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
var lambda = Expression.Lambda<Func<SiteAssessmentRequest, bool>>(body, param);
var siteAssessmentRequest = await _siteAssessmentRequestRepository.GetAsync(lambda, null, x => x.Country).ConfigureAwait(false);
在上面的代码中,当我传递多个参数时,例如request。状态和 request.SiteName 我想根据状态和站点名称进行过滤。当我看到查询时,查询中只附加了一个参数
{x => (Invoke(x => (Not(IsNullOrEmpty(x.CreatedBy)) AndAlso Not(IsNullOrWhiteSpace(x.CreatedBy))), x)
AndAlso Invoke(x => value(Site.V1.Implementation.GetSARByFilterAr+<>c__DisplayClass12_0)
.request.Status.Contains(x.Status), x))}
搜索了这么多我得到了下面的代码
public static class ExpressionCombiner
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> exp, Expression<Func<T, bool>> newExp)
{
// get the visitor
var visitor = new ParameterUpdateVisitor(newExp.Parameters.First(), exp.Parameters.First());
// replace the parameter in the expression just created
newExp = visitor.Visit(newExp) as Expression<Func<T, bool>>;
// now you can and together the two expressions
var binExp = Expression.And(exp.Body, newExp.Body);
// and return a new lambda, that will do what you want. NOTE that the binExp has reference only to te newExp.Parameters[0] (there is only 1) parameter, and no other
return Expression.Lambda<Func<T, bool>>(binExp, newExp.Parameters);
}
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);
}
}
}
但我正在努力让它在代码之上工作。
在上面的查询中,我只看到状态而不是站点名称。所以我想包含多个表达式。有人可以帮助我吗?任何帮助将不胜感激。谢谢
我来解释一下LambdaExpression的核心概念。 LambdaExpression 有 0..N 个参数和主体。 LambdaExpression 的主体是我们实际上想要重用的 Expression。
基本错误是试图组合 lambda 表达式(更改了参数名称,因为它是 ExpressionTree 的工作方式 - 它通过引用而不是名称比较参数):
Expression<Func<Some, bool>> lambda1 = x1 => x1.Id == 10;
Expression<Func<Some, bool>> lambda2 = x2 => x2.Value == 20;
// wrong
var resultExpression = Expression.AndAlso(lambda1, lambda2);
之前错误样本的示意图应该是这样的(即使它会崩溃)
(x1 => x1.Id == 10) && (x2 => x2.Value == 20)
我们所拥有的——不是想要的表达,而是“功能”的组合。
所以让我们重用 LambdaExpression 的主体
// not complete
var newBody = Expression.AndAlso(lambda1.Body, lambda2.Body);
结果更可接受,但仍需要更正:
(x1.Id == 10) && (x2.Value == 20)
为什么我们需要修正?因为我们正在尝试构建以下 LambdaExpression
var param = Expression.Parameter(typeof(some), "e");
var newBody = Expression.AndAlso(lambda1.Body, lambda2.Body);
// still wrong
var newPredicate = Expression.Lambda<Func<Some, bool>>(newBody, param)
newPredicate
的结果应该类似于
e => (x1.Id == 10) && (x2.Value == 20)
如您所见,我们在前两个 lambda 表达式中留下了主体参数,这是错误的,我们必须用新参数 param
("e") 替换它们,最好在组合之前这样做。
我正在使用我的替换器实现,returns 需要 body
。
所以,让我们写出正确的 lambda!
Expression<Func<Some, bool>> lambda1 = x1 => x1.Id == 10;
Expression<Func<Some, bool>> lambda2 = x2 => x2.Value == 20;
var param = Expression.Parameter(typeof(Some), "e");
var newBody = Expression.AndAlso(
ExpressionReplacer.GetBody(lambda1, param),
ExpressionReplacer.GetBody(lambda2, param));
// hurray!
var newPredicate = Expression.Lambda<Func<Some, bool>>(newBody, param);
var query = query.Where(newPredicate);
完成这些步骤后,您将得到想要的结果:
e => (e.Id == 10) && (e.Value == 20)
和ExpressionReplacer
实施
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Zip(lambda.Parameters, toReplace, (f, s) => Tuple.Create(f, s))
.ToDictionary(e => (Expression)e.Item1, e => e.Item2)).Visit(lambda.Body);
}
}
如果你打算更近距离地使用 ExpressionTree,我建议安装这个 VS 扩展,它应该会简化你的生活:Readable Expressions
网络核心应用。我实现了一个通用的存储库模式。我正在尝试实现一些过滤功能。我有以下代码。
var param = Expression.Parameter(typeof(SiteAssessmentRequest), "x");
Expression<Func<SiteAssessmentRequest, bool>> query;
query = x => x.CreatedBy == request.Userid || x.AssignedTo == request.Userid;
Expression body = Expression.Invoke(query, param);
if (request.Client != null && request.Client.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.Client.Contains(x.Client);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
if (request.CountryId != null && request.CountryId.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.CountryId.Contains(x.CountryId);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
if (request.SiteName != null && request.SiteName.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.SiteName.Contains(x.SiteName);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
if (request.Status != null && request.Status.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.Status.Contains(x.Status);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
var lambda = Expression.Lambda<Func<SiteAssessmentRequest, bool>>(body, param);
var siteAssessmentRequest = await _siteAssessmentRequestRepository.GetAsync(lambda, null, x => x.Country).ConfigureAwait(false);
在上面的代码中,当我传递多个参数时,例如request。状态和 request.SiteName 我想根据状态和站点名称进行过滤。当我看到查询时,查询中只附加了一个参数
{x => (Invoke(x => (Not(IsNullOrEmpty(x.CreatedBy)) AndAlso Not(IsNullOrWhiteSpace(x.CreatedBy))), x)
AndAlso Invoke(x => value(Site.V1.Implementation.GetSARByFilterAr+<>c__DisplayClass12_0)
.request.Status.Contains(x.Status), x))}
搜索了这么多我得到了下面的代码
public static class ExpressionCombiner
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> exp, Expression<Func<T, bool>> newExp)
{
// get the visitor
var visitor = new ParameterUpdateVisitor(newExp.Parameters.First(), exp.Parameters.First());
// replace the parameter in the expression just created
newExp = visitor.Visit(newExp) as Expression<Func<T, bool>>;
// now you can and together the two expressions
var binExp = Expression.And(exp.Body, newExp.Body);
// and return a new lambda, that will do what you want. NOTE that the binExp has reference only to te newExp.Parameters[0] (there is only 1) parameter, and no other
return Expression.Lambda<Func<T, bool>>(binExp, newExp.Parameters);
}
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);
}
}
}
但我正在努力让它在代码之上工作。 在上面的查询中,我只看到状态而不是站点名称。所以我想包含多个表达式。有人可以帮助我吗?任何帮助将不胜感激。谢谢
我来解释一下LambdaExpression的核心概念。 LambdaExpression 有 0..N 个参数和主体。 LambdaExpression 的主体是我们实际上想要重用的 Expression。
基本错误是试图组合 lambda 表达式(更改了参数名称,因为它是 ExpressionTree 的工作方式 - 它通过引用而不是名称比较参数):
Expression<Func<Some, bool>> lambda1 = x1 => x1.Id == 10;
Expression<Func<Some, bool>> lambda2 = x2 => x2.Value == 20;
// wrong
var resultExpression = Expression.AndAlso(lambda1, lambda2);
之前错误样本的示意图应该是这样的(即使它会崩溃)
(x1 => x1.Id == 10) && (x2 => x2.Value == 20)
我们所拥有的——不是想要的表达,而是“功能”的组合。
所以让我们重用 LambdaExpression 的主体
// not complete
var newBody = Expression.AndAlso(lambda1.Body, lambda2.Body);
结果更可接受,但仍需要更正:
(x1.Id == 10) && (x2.Value == 20)
为什么我们需要修正?因为我们正在尝试构建以下 LambdaExpression
var param = Expression.Parameter(typeof(some), "e");
var newBody = Expression.AndAlso(lambda1.Body, lambda2.Body);
// still wrong
var newPredicate = Expression.Lambda<Func<Some, bool>>(newBody, param)
newPredicate
的结果应该类似于
e => (x1.Id == 10) && (x2.Value == 20)
如您所见,我们在前两个 lambda 表达式中留下了主体参数,这是错误的,我们必须用新参数 param
("e") 替换它们,最好在组合之前这样做。
我正在使用我的替换器实现,returns 需要 body
。
所以,让我们写出正确的 lambda!
Expression<Func<Some, bool>> lambda1 = x1 => x1.Id == 10;
Expression<Func<Some, bool>> lambda2 = x2 => x2.Value == 20;
var param = Expression.Parameter(typeof(Some), "e");
var newBody = Expression.AndAlso(
ExpressionReplacer.GetBody(lambda1, param),
ExpressionReplacer.GetBody(lambda2, param));
// hurray!
var newPredicate = Expression.Lambda<Func<Some, bool>>(newBody, param);
var query = query.Where(newPredicate);
完成这些步骤后,您将得到想要的结果:
e => (e.Id == 10) && (e.Value == 20)
和ExpressionReplacer
实施
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Zip(lambda.Parameters, toReplace, (f, s) => Tuple.Create(f, s))
.ToDictionary(e => (Expression)e.Item1, e => e.Item2)).Visit(lambda.Body);
}
}
如果你打算更近距离地使用 ExpressionTree,我建议安装这个 VS 扩展,它应该会简化你的生活:Readable Expressions