Linq to entity 使用“Func”在生成匿名对象的选择语句中创建属性

Posted

技术标签:

【中文标题】Linq to entity 使用“Func”在生成匿名对象的选择语句中创建属性【英文标题】:Linq to entities use `Func` to create property in a select statement that produces an anonymous object 【发布时间】:2017-10-02 14:06:09 【问题描述】:

我正在研究一种使用 linq 到实体的简单文本搜索方法,我想在几个看起来像这样的地方重用它:

IQueryable<MyObject> query = db.MyObjects.Where(o => /* some criteria */);

query = query
    .Select(o => new 
    
        value = o,
        search = o.Foo + " " + o.Bar.X + " " + o.Bar.Y
    )
    .Where(o => o.search.contains("foo"))
    .Select(o => o.value);

query = query.Where(o => /* some other criteria */);

我希望能够将 Select > Where > Select 序列转换为一个扩展方法,该方法可以给定一个 Funcsearch 属性拉在一起,如下所示:

public static IQueryable<T> Search<T>(this IQueryable<T> query, Func<T, string> selector, string phrase)

    return query
        .Select(o => new 
        
            value = o,
            search = selector.Invoke(o)
        )
        .Where(o => o.search.Contains(phrase))
        .Select(o => o.value);

然后可以这样使用:

query.Search(o => o.Foo + " " + o.Bar.X + " " + o.Bar.Y, "foo");

我认为这很简洁,它编译得很愉快,但它不会运行,因为 Linq to entity 不知道如何处理 Func.Invoke() 方法。我还有其他一些 SO 问题,我可能应该使用 Expressiong&lt;Func&lt;T,string&gt;&gt; 而不仅仅是 Func,但我发现使这项工作起作用的唯一方法是用表达式替换 Select 语句的整个主体,然后要求表达式返回具有 value 和 search 属性的对象。

有没有办法使用FuncExpression 只在匿名对象中创建搜索属性的值?

【问题讨论】:

正在转换为 IEnumerable 一个选项并使用 linq 到对象。 可悲的是,这会将所有内容都拉入内存,理想情况下我想使用数据库来完成这项艰苦的工作,因此需要通过实体框架将其转换为 SQL。 不需要两个选择,您可以只用一个Where 编写该查询,如o =&gt; (o.Foo + " " + o.Bar.X + " " + o.Bar.Y).Contains("foo")。鉴于该查询编写起来非常简单,我也认为不需要扩展方法来帮助您编写它。 @Servy 上面的代码说明了我正在尝试做的事情,而不是生产代码。我希望能够根据我正在查询的实体类型向它抛出不同的Funcs。此外,在我的真实代码中,.Where(o =&gt; o.search.Contains(phrase)) 将扩展为使用多个搜索词,并根据它们的匹配程度对结果进行排名。以上只是一个简化的例子来说明手头的问题。 【参考方案1】:

正如您所提到的,您需要在函数中接受 Expression,而不是 Func,EF 才能实际翻译查询。

您正在寻找的是编写表达式的能力,就像您可以编写函数一样:

public static Expression<Func<T, TResult>> Compose<T, TIntermediate, TResult>(
    this Expression<Func<T, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)

    return Expression.Lambda<Func<T, TResult>>(
        second.Body.Replace(second.Parameters[0], first.Body),
        first.Parameters[0]);

这依赖于以下方法将一个表达式的所有实例替换为另一个:

public class ReplaceVisitor:ExpressionVisitor

    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    
        this.from = from;
        this.to = to;
    

    public override Expression Visit(Expression ex)
    
        if(ex == from) return to;
        else return base.Visit(ex);
      


public static Expression Replace(this Expression ex,
    Expression from,
    Expression to)

    return new ReplaceVisitor(from, to).Visit(ex);

现在你可以很容易地编写你的方法了:

public static IQueryable<T> Search<T>(this IQueryable<T> query, 
    Expression<Func<T, string>> selector, 
    string phrase)

    return query.Where(selector.Compose(search => search.Contains(phrase)));

【讨论】:

以上是关于Linq to entity 使用“Func”在生成匿名对象的选择语句中创建属性的主要内容,如果未能解决你的问题,请参考以下文章

在CQ中使用动态表达式在LINQ to Entities中使用INT列

使用 LINQ-to-Entities 搜索时忽略空字符串

LINQ to Entities 不支持 LINQ 表达式节点类型“Invoke”

在 LINQ to Entities 中批量删除

如果存在-UPDATE-else-INSERT 与 Linq-to-Entities?

LINQ to Entities 无法识别方法“布尔包含 [Int32]