【问题标题】:Pass an Expression into another Expression as parameter将一个表达式作为参数传递给另一个表达式
【发布时间】:2014-10-26 21:36:20
【问题描述】:

我的问题源于复杂的可重用逻辑规范。

我有以下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的东西 也许最终的解决方案是分解然后重新组合表达式,如果是这样,我该如何封装它以便在类似情况下重用它? (因为这是我想要做的)

【问题讨论】:

  • 如果你想创建过滤器来重用,你可以看看entityrestsdk.codeplex.com,在这个SDK中,我们一次性设置表达式,但是,你这样做的方式,它将难以调试和维护,因为您无法轻松地可视化您的过滤器。由于这些规则不会被动态重写,它们会在整个生命周期中保持不变,因此最好编写它们,因为它们看起来易于使用和测试。

标签: c# linq-to-sql expression


【解决方案1】:

您需要重写表达式。我为WithParameter做了一个扩展方法。

首先,我们需要从这个答案https://stackoverflow.com/a/5431309/1798889 中借用 ParameterVisitor 类并稍微调整一下。我们不想只用不同的参数替换一个表达式中的参数,我们想删除参数并用新的表达式替换它。

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 to SQL,也没有针对它进行测试,也没有针对 IQueryable 进行测试,但它确实适用于 IEnumberable。我不明白为什么它不起作用,因为最后两个表达式的调试视图看起来相同。

Expression<Func<Estate, bool>> estateExp = estate => estate.CreatorUser.UserInRoles.Any(userInRole => userInRole.RoleId ==  SystemRoles.SysAdmin.Id);

是与

相同的表达式树
userExp.WithParameter(estate => estate.CreatorUser)

【讨论】:

    【解决方案2】:

    另一种解决方案是先查询用户并选择所有资产:

    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();
    

    【讨论】:

    • 我的主要问题是传递表达式,这不是我问题的答案,我遇到了更复杂的问题,这只是其中的一部分(我需要重用规范)。
    猜你喜欢
    • 2015-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-11
    相关资源
    最近更新 更多