【问题标题】:Linq to entities use `Func` to create property in a select statement that produces an anonymous objectLinq to entity 使用“Func”在生成匿名对象的选择语句中创建属性
【发布时间】: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)) 将扩展为使用多个搜索词,并根据它们的匹配程度对结果进行排名。以上只是一个简化的例子来说明手头的问题。

标签: c# entity-framework linq linq-to-entities


【解决方案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)));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-23
    • 1970-01-01
    • 2016-12-25
    • 2021-12-25
    • 2010-11-20
    相关资源
    最近更新 更多