使用 Func<T, string> lambda 动态构造 where 子句 - linq 到实体

Posted

技术标签:

【中文标题】使用 Func<T, string> lambda 动态构造 where 子句 - linq 到实体【英文标题】:Dynamically construct where clause with Func<T, string> lambda - linq to entities 【发布时间】:2021-06-19 14:24:03 【问题描述】:

(适用于 .Net Framework 4.7)

我正在尝试编写一些扩展方法来帮助为各种实体创建动态 where 子句。我是几天前开始的,所以可能有很多我不知道的地方,还有一些我可能误解了。

我已经设法创建了一个扩展方法,用于按 1 个按预期工作的属性进行过滤(我确实使用反射来获取该属性,但无法让它与接口一起工作 - 好吧,没有它执行 sql那是)。 不过,我似乎无法让这个用于 lambda 表达式。

注意,解决方案一定不能触发sql执行。因为我能够写出一些“有效”的变体,但它们会触发 sql 执行。

我的工作方式是,一旦我准备好代码,我就开始调试并在手表中使用“查询”。它看起来像这样(注意 sql 代码)

一旦我跳过我的 FilterString 方法调用,它要么变成一个 sql 结果,要么我得到一个异常(使用当前代码),它不应该:

这是我当前抛出异常的代码(目前不处理“匹配”参数,我正在实现“等于”调用。还会有其他类似、开头为、类似等)

例外只是具有函数的“类型不匹配”之一不能作为参数传递给字符串 Equals 或其他什么。

    public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
        string criteriaItem, Expression<Func<T, string>> getItemString)
        where T : class
    
        if (string.IsNullOrEmpty(criteriaItem))
        
            return query;
        

        var param = Expression.Parameter(typeof(T), "r");
        var selector = Expression.Lambda<Func<T, string>>(getItemString, param);
        Expression<Func<string, bool>> prototype = item => item == criteriaItem;
        var predicate = Expression.Lambda<Func<T, bool>>(
            prototype.Body.ReplaceParameter(prototype.Parameters[0], selector.Body),
            selector.Parameters[0]);

        return query.Where(predicate);
    

以及执行 sql 而不仅仅是生成它的那个

    public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
        string criteriaItem, Expression<Func<T, string>> getItemString)
        where T : class
    
        if (string.IsNullOrEmpty(criteriaItem))
        
            return query;
        

        var param = Expression.Parameter(typeof(T), "r");
        //var value = Expression.Constant(getItemString);
        var equals = typeof(string).GetMethod("Equals", new Type[]  typeof(string) );
        var item = Expression.Invoke(getItemString, param);
        var body = Expression.Call(Expression.Constant(criteriaItem),
            equals,
            item);

        return query.Where(Expression.Lambda<Func<T, bool>>(body, param));
    

这样调用它们

query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.MaybeSomeOtherProp.SomeString);
query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.Name);

将在任意数量的不同实体上调用相同的扩展方法,具有任意数量的不同属性和道具名称。我想我可以利用我正在工作的反射版本,并以某种数组的形式传入所有属性名称,但这实在是太难看了。

长话短说,我怎样才能按照我上面解释的方式让它工作,那就是:生成 sql 而不是执行?

谢谢,

注意,“ReplaceParameter”扩展方法是来自这里:https://***.com/a/39206392/630515

【问题讨论】:

我没有找到任何问号。那么真正的问题是什么?您在寻找代码审查吗? Match 参数的用途是什么?我没有看到它在任何地方使用? @Marco 我解释了我的问题中缺少它的原因。请仔细阅读。谢谢 @JeroenvanLangen 抱歉,我将此作为一个问题提出,“虽然我似乎无法让这个用于 lambda 表达式。”,但我很高兴以一个问题,所以它更清楚。谢谢 【参考方案1】:

所以,您正在尝试合并您的原型item =&gt; item == criteriaItem。使用传入的字符串属性表达式,如(r) =&gt; r.SomeProperty.Name 来创建(r) =&gt; r.SomeProperty.Name == criteriaItem

    Expression<Func<string, bool>> prototype = item => item == criteriaItem;
    var predicate = Expression.Lambda<Func<T, bool>>(
        ReplacingExpressionVisitor.Replace(
            prototype.Parameters[0],
            getItemString.Body,
            prototype.Body),
        getItemString.Parameters[0]);

我认为您正在尝试这样做,以便criteriaItem 绑定到 sql 参数,而不是作为字符串常量内联。但是你的问题有点难以理解。

【讨论】:

抱歉问题不清楚。希望通过添加问题更清楚?另外,我认为 EF Core 在这方面与我们的 .net 框架没有什么不同,但是看到 ReplacingExpressionVisitor 仅存在于 .net 5 中,我还注意到这是针对 v4.7 同时我想看看是否有人写过我可以使用的类似方法(或者你可能会为我指出正确的方向?)谢谢 我使用了上面提到的 ReplaceParameter ext 方法而不是 .net5 的 ReplacingExpressionVisitor 并将您的建议调整到我的代码中,我得到了它的工作,谢谢

以上是关于使用 Func<T, string> lambda 动态构造 where 子句 - linq 到实体的主要内容,如果未能解决你的问题,请参考以下文章

C# fun

将 .net Func<T> 转换为 .net Expression<Func<T>>

IQueryable 和 IEnumerable

Expression<Func<T,TResult>>和Func<T,TResult>

有没有办法在 C# 中使用 Enumerable.Repeat 方法重复 Func<T, T> ?

使用 T 类型的反射为具有属性的属性创建 Expression<Func<T, TValue>>