将一个表达式作为参数传递到另一个表达式中
本文关键字:表达式 参数传递 另一个 一个 | 更新日期: 2023-09-27 18:26:39
我的问题源自复杂的可重用逻辑规范。
我有以下Expression
:
Expression<Func<User, bool>> userExp =
user => user.UserInRoles.Any(userInRole => userInRole.RoleId == SystemRoles.SysAdmin.Id);
我需要获得以下能力:
new CommonContext().Set<Estate>().Where(estate => userExp.WithParameter(estate.CreatorUser)).ToList();
那么,我如何将Estate
实体的Creator
传递到接受User
实体的表达式中,并最终使用linq to sql
中的最终表达式呢?
问题是:WithParameter
编辑:
这个有效,但效率不高:
new CommonContext().Set<Estate>().ToList().Where(estate => userExp.Compile()(estate.CreatorUser)).ToList()
以下不是答案,因为Invoke method
无法转换为存储表达式:
Expression<Func<User, bool>> userExp =
user => user.UserInRoles.Any(userInRole => userInRole.RoleId == SystemRoles.SysAdmin.Id);
Expression<Func<Estate, User>> propAccessor = estate => estate.ApprovedByUser;
var estateParam = Expression.Parameter(typeof(Estate));
var userParam = Expression.Invoke(propAccessor, estateParam);
var translatedExp = Expression.Invoke(userExp, userParam);
var result = (Expression<Func<Estate, bool>>)Expression.Lambda(translatedExp, estateParam);
var exceptionProvider = new CommonContext().Set<Estate>().Where(result).ToList();
但我需要一些可以翻译成Store Expression
的东西也许最终的解决方案是分解然后重新组合表达式,如果是这样,我如何封装它以便在类似的情况下重用它?(因为这就是我要做的)
您需要重写表达式。我为WithParameter做了一个扩展方法。
首先,我们需要借用这个答案中的ParameterVisitor类https://stackoverflow.com/a/5431309/1798889然后稍微调整一下。我们不希望只是用不同的参数替换一个表达式中的参数,我们希望删除该参数并用新的表达式替换它。
public class ParameterVisitor : ExpressionVisitor
{
private readonly ReadOnlyCollection<ParameterExpression> from;
// Changed from ReadOnlyCollection of ParameterExpression to Expression
private readonly Expression[] to;
public ParameterVisitor(ReadOnlyCollection<ParameterExpression> from, params Expression[] to)
{
if (from == null) throw new ArgumentNullException("from");
if (to == null) throw new ArgumentNullException("to");
if (from.Count != to.Length)
throw new InvalidOperationException(
"Parameter lengths must match");
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
for (int i = 0; i < from.Count; i++)
{
if (node == from[i]) return to[i];
}
return node;
}
}
请注意,我将To参数更改为仅作为表达式,而不是ParameterExpression。
现在我们要创建WithParameter
public static class QueryableExtensions
{
public static Expression<Func<TResult, bool>> WithParameter<TResult, TSource>(this Expression<Func<TSource, bool>> source, Expression<Func<TResult, TSource>> selector)
{
// Replace parameter with body of selector
var replaceParameter = new ParameterVisitor(source.Parameters, selector.Body);
// This will be the new body of the expression
var newExpressionBody = replaceParameter.Visit(source.Body);
return Expression.Lambda<Func<TResult, bool>>(newExpressionBody, selector.Parameters);
}
}
在这篇文章中,我们选择了如何知道我们想要什么属性的选择器,并用该属性替换Expression>的参数。
你像一样使用它
new CommonContext().Set<Estate>().Where(userExp.WithParameter<Estate, User>(estate => estate.CreatorUser)).ToList();
警告:我不了解Linq到SQL,也没有针对它进行测试,也没有对IQueryable进行测试,但它确实适用于IEnumberable。我不明白为什么它不起作用,因为最后两个表达式的调试视图看起来是一样的。
Expression<Func<Estate, bool>> estateExp = estate => estate.CreatorUser.UserInRoles.Any(userInRole => userInRole.RoleId == SystemRoles.SysAdmin.Id);
是与相同的表达式树
userExp.WithParameter(estate => estate.CreatorUser)
一种替代解决方案可以是首先查询用户并选择所有房地产:
Expression<Func<User, bool>> userExp =
user => user.UserInRoles.Any(userInRole => userInRole.RoleId == SystemRoles.SysAdmin.Id);
new CommonContext().Set<User>().Where(userExp).SelectMany(u => u.Estates).ToList();