首页 新闻 会员 周边

.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 | 高人七级 | 园豆:22763
提问于:2023-05-22 12:21

相关链接:Reusable expression

– dudu 3年前
< >
分享
最佳答案
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 | 高人七级 |园豆:22763 | 2023-05-23 09:44
其他回答(3)
0

重用PublicAuditResultPredicate?

LiveCoding | 园豆:501 (小虾三级) | 2023-05-22 15:05

是的

支持(0) 反对(0) dudu | 园豆:22763 (高人七级) | 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 | 园豆:22763 (高人七级) | 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 类型。

方法首先创建一个新的参数,作为新的表达式树的顶级参数。然后,它使用 innerSelector 和 outerSelector 的 Body 属性访问表达式树,并使用 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 | 园豆:8059 (大侠五级) | 2023-05-22 23:31

运行时报错:

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

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