首页 新闻 会员 周边

.NET: LINQ 中 Expression 重用问题

0
悬赏园豆:50 [已解决问题] 解决于 2023-05-23 09:47

下面代码中的 PublicAuditResultPredicate Expression

public static IQueryable<Ing> ByPublicAuditResult(this IQueryable<Ing> ings)
{
    return ings.Where(PublicAuditResultPredicate);
}

public static Expression<Func<IngInfo, bool>> PublicAuditResultPredicate
{
    get
    {
        return i => i.AuditResult == null;
    }
}

如何在下面的代码中重用?

public static IQueryable<Comment> ByPublicAuditResult(this IQueryable<Comment> comments)
{
    return comments.Where(
        c => c.Ing);
}
dudu的主页 dudu | 高人七级 | 园豆:30994
提问于:2023-05-22 12:21

相关链接:Reusable expression

dudu 11个月前
< >
分享
最佳答案
0

参考 github 上 efcore 仓库中的 issue:3.0 regression: Translate Expression.Invoke,通过 Expression.Lambda 解决了。

public static IQueryable<Comment> ByPublicAuditResult(this IQueryable<Comment> comments)
{
    return comments.Where(PublicAuditResultPredicate);
}

private static Expression<Func<Comment, bool>> PublicAuditResultPredicate
{
    get
    {
        Expression<Func<IngInfo, bool>> ingPredicate = IngSpecification.PublicAuditResultPredicate;
        Expression<Func<Comment, IngInfo>> selectIng = c => c.Ing;
        return Expression.Lambda<Func<Comment, bool>>(
            Expression.Invoke(ingPredicate, selectIng.Body), selectIng.Parameters);
    }
}
dudu | 高人七级 |园豆:30994 | 2023-05-23 09:44
其他回答(3)
0

重用PublicAuditResultPredicate?

LiveCoding | 园豆:497 (菜鸟二级) | 2023-05-22 15:05

是的

支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2023-05-22 16:37
0

chatgpt回复:
由于传递给Where方法的参数是一个Expression<Func<Comment, bool>>类型的lambda表达式,所以您需要根据PublicAuditResultPredicate创建一个等效的表达式。

您可以使用Invoke方法将PublicAuditResultPredicate的参数替换为Comment类型的参数,并在Where表达式中使用它。修改后的代码如下:

public static IQueryable<Comment> ByPublicAuditResult(this IQueryable<Comment> comments)
{
    var predicate = PublicAuditResultPredicate.Expand<Comment, IngInfo>();
    return comments.Where(c => predicate.Invoke(c.Ing));
}

在这个代码中,Expand方法将PublicAuditResultPredicate转换为lambda表达式,根据其参数的类型指定了泛型类型参数。然后,predicate.Invoke(c.Ing)c.Ing作为参数传递给PublicAuditResultPredicate,并返回等效的表达式。最后,Where方法将此表达式应用于comments序列中的所有元素。

希望这可以帮到您!

收获园豆:20
xAaron | 园豆:163 (初学一级) | 2023-05-22 15:46

Expand 方法的实现

支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2023-05-22 16:53

@dudu: 以下为chatgpt回复:
下面代码中的 PublicAuditResultPredicate Expression
public static IQueryable<Ing> ByPublicAuditResult(this IQueryable<Ing> ings)
{
return ings.Where(PublicAuditResultPredicate);
}

public static Expression<Func<IngInfo, bool>> PublicAuditResultPredicate
{
get
{
return i => i.AuditResult == null;
}
}
如何在下面的代码中重用?
public static IQueryable<Comment> ByPublicAuditResult(this IQueryable<Comment> comments)
{
return comments.Where(
c => c.Ing);
}
你的答复是:
由于传递给Where方法的参数是一个Expression<Func<Comment, bool>>类型的lambda表达式,所以您需要根据PublicAuditResultPredicate创建一个等效的表达式。

您可以使用Invoke方法将PublicAuditResultPredicate的参数替换为Comment类型的参数,并在Where表达式中使用它。修改后的代码如下:

public static IQueryable<Comment> ByPublicAuditResult(this IQueryable<Comment> comments)
{
    var predicate = PublicAuditResultPredicate.Expand<Comment, IngInfo>();
    return comments.Where(c => predicate.Invoke(c.Ing));
}

在这个代码中,Expand方法将PublicAuditResultPredicate转换为lambda表达式,根据其参数的类型指定了泛型类型参数。然后,predicate.Invoke(c.Ing)c.Ing作为参数传递给PublicAuditResultPredicate,并返回等效的表达式。最后,Where方法将此表达式应用于comments序列中的所有元素。
那么,回答中的Expand方法要怎么写
回复为:Expand方法应该使用System.Linq.Expressions.ExpressionVisitor类来遍历和修改表达式树,将其中的参数类型IngInfo替换为Comment类型。下面是一个可能的实现:

public static Expression<Func<TTo, bool>> Expand<TFrom, TTo>(this Expression<Func<TFrom, bool>> predicate)
{
    var visitor = new ParameterTypeVisitor<TFrom, TTo>();
    var body = visitor.Visit(predicate.Body);
    return Expression.Lambda<Func<TTo, bool>>(body, visitor.NewParameter);
}

public class ParameterTypeVisitor<TFrom, TTo> : ExpressionVisitor
{
    public ParameterExpression NewParameter { get; private set; }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == typeof(TFrom))
        {
            NewParameter = Expression.Parameter(typeof(TTo), node.Name);
            return NewParameter;
        }
        return base.VisitParameter(node);
    }
}

在这个实现中,Expand方法将predicate.Body传递给ParameterTypeVisitor类的实例进行修改。ParameterTypeVisitor类在重写VisitParameter方法时,检查参数类型是否为TFrom类型。如果是,它将创建一个新的参数表达式,类型为TTo,并将其存储在NewParameter属性中。在表达式树中遍历时,它会将原始的参数表达式替换为新的参数表达式。最终,Expand方法返回一个等效的lambda表达式,其中的参数类型为TTo

有意思的是,由于之前问的记录可能过期了,我重新问了遍,发现答复是不一样的,第二个答复如下:
可以通过创建一个通用方法来重用 PublicAuditResultPredicate 表达式,如下所示:

public static class PredicateExtensions
{
    public static IQueryable<T> ByPublicAuditResult<T>(this IQueryable<T> source, Expression<Func<T, IngInfo>> ingSelector)
    {
        // Use the ingSelector to access AuditResult property of IngInfo
        var predicate = PublicAuditResultPredicate.Compose(ingSelector);
        return source.Where(predicate);
    }
}

在此方法中,我们扩展了 IQueryable<T> 并将 PublicAuditResultPredicate 表达式作为一个参数组合到一个新的表达式中,以便重用。该方法使用 Compose() 扩展方法将查询可组合成新的表达式,从而使其能够访问 IngInfo 类型的 AuditResult 属性。

然后,可以像下面这样调用该方法:

var query = comments.ByPublicAuditResult(c => c.Ing.IngInfo);

在这个调用中,我们使用了 Ing.IngInfo 属性作为参数,以访问与 PublicAuditResultPredicate 表达式中使用的 IngInfo 属性相同的属性。
然后我又问了Compose()的实现,如下:
下面是一个实现了 Compose() 方法的示例扩展方法:

public static class PredicateExtensions
{
    public static Expression<Func<TInput, bool>> Compose<TInput, TMiddle, TOutput>(
        this Expression<Func<TInput, TMiddle>> innerSelector,
        Expression<Func<TMiddle, TOutput>> outerSelector)
    {
        var parameter = Expression.Parameter(typeof(TInput));
        var inner = innerSelector.Body.ReplaceParameter(innerSelector.Parameters[0], parameter);
        var outer = outerSelector.Body.ReplaceParameter(outerSelector.Parameters[0], inner);
        var lambda = Expression.Lambda<Func<TInput, bool>>(outer, parameter);
        return lambda;
    }
    
    private static Expression ReplaceParameter(this Expression expression,
                                                ParameterExpression source,
                                                ParameterExpression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }

    private class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source { get; set; }
        public ParameterExpression Target { get; set; }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == Source ? Target : base.VisitParameter(node);
        }
    }
}

Compose() 方法接受两个表达式参数:一个从输入类型到中间类型的,和一个从中间类型到输出类型的。在这个示例中,TInput 等于 Comment 类型,TMiddle 等于 IngInfo 类型,TOutput 等于 bool 类型。

方法首先创建一个新的参数,作为新的表达式树的顶级参数。然后,它使用 innerSelectorouterSelectorBody 属性访问表达式树,并使用 ReplaceParameter() 方法将引用 innerSelector 参数的所有节点替换为新创建的顶级参数引用。然后它使用 Expression.Lambda() 方法创建新的 LambdaExpression 并返回。

ReplaceParameter() 方法实现了表达式树中的节点替换。它使用 ParameterReplacer 访问者类将 Source 参数引用替换为 Target 参数引用。在此过程中,我们使用递归遍历 Expression 树的节点,并使用 VisitParameter() 方法来替换原始参数。

请注意,这只是一种用于组合 Expression 树的方法,且在不同的情况下可能需要进行调整。例如,您可能需要将该方法调整为支持更多或不同类型的参数。

以上,
希望可以帮到你。

支持(0) 反对(0) xAaron | 园豆:163 (初学一级) | 2023-05-22 17:09
0

要在下面的代码中重用 PublicAuditResultPredicate 表达式,你可以使用 LINQ 的 Compose 方法来组合表达式。首先,你需要将 PublicAuditResultPredicate 转换为一个可调用的表达式,然后将其组合到新的表达式中。

以下是一个示例代码,演示如何重用 PublicAuditResultPredicate 表达式:

csharp
Copy code
public static IQueryable<Comment> ByPublicAuditResult(this IQueryable<Comment> comments)
{
var predicate = PublicAuditResultPredicate.Compile(); // 将 PublicAuditResultPredicate 转换为可调用的表达式

Expression<Func<Comment, bool>> combinedPredicate = c => predicate(c.Ing); // 组合表达式

return comments.Where(combinedPredicate);

}
在上面的示例中,首先使用 PublicAuditResultPredicate.Compile() 方法将 PublicAuditResultPredicate 转换为可调用的表达式。然后,使用 combinedPredicate 来组合新的表达式,其中使用了 predicate(c.Ing) 来调用之前转换的表达式,并将其作为新表达式的一部分。

这样,你就可以在 ByPublicAuditResult 方法中重用 PublicAuditResultPredicate 表达式,并将其应用于 comments 查询。

收获园豆:30
Technologyforgood | 园豆:5633 (大侠五级) | 2023-05-22 23:31

运行时报错:

Where(c => __Compile_2.Invoke(c.Outer.Inner))' could not be translated

支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2023-05-23 09:28
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册