Linq到实体使用`Func`在一个生成匿名对象的select语句中创建属性 [英] Linq to entities use `Func` to create property in a select statement that produces an anonymous object

查看:151
本文介绍了Linq到实体使用`Func`在一个生成匿名对象的select语句中创建属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一个简单的文本搜索方法,使用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 => / *一些其他条件* /);

我希望能够将选择>其中>选择顺序转换为可以给予 Func ,将搜索属性一起拉,如下所示:

  public static IQueryable< T>搜索< T>(此IQueryable< T>查询,Func< T,string>选择器,字符串短语)
{
返回查询
.Select(o => new
{
value = o,
search = selector.Invoke(o)
})
.Where(o => o.search.Contains(phrase))
。选择(o => o.value);
}

然后可以这样使用:

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

我觉得这很整洁,而且编译愉快但不会运行,因为Linq对实体不知道该如何处理 Func .Invoke()方法。我有一些其他的SO问题,我应该使用一个 Expressiong< Func< T,string>> 而不是仅仅 Func ,但是我发现这个工作的唯一方法是用表达式替换Select语句的整个主体,然后表达式需要表达式返回一个具有值和搜索属性的对象。



有没有办法使用 Func 表达式仅在匿名对象中创建搜索属性的值?

解决方案

正如您所提到的,您需要接受您的函数中的表达式,而不是一个 Func ,可以实际翻译查询。 p>

您正在寻找的是组合表达式的功能,就像您可以组合函数一样:

  public static Expression< Func< T,TResult>>组合< T,TIntermediate,TResult>(
此表达式< Func< T,TIntermediate>>第一,
表达式&FunC< TIntermediate,TResult>>秒)
{
返回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表达式访问(表达式ex)
{
if(ex == from)return to;
else return base.Visit(ex);
}
}

public static Expression替换(此表达式ex,
表达式,
表达式)
{
返回新的ReplaceVisitor(from,to).Visit(ex);
}

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

  public static IQueryable< T>搜索< T>(此IQueryable< T>查询,
表达式< Func< T,string>>>选择器,
字符串短语)
{
return query.Where .Compose(search => search.Contains(phrase)));
}


I am working on a simple text search method using linq to entities that I would like to reuse in several places that looks a bit like this:

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 */);

I would like to be able to turn the Select > Where > Select sequence into an extension method that can be given a Func that pulls the search property together, like this:

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);
}

This could then be used like this:

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

I think this is quite neat and it compiles happily but it won't run because Linq to entities doesn't know what to do with the .Invoke() method of the Func. I have in a few other SO questions that I should probably be using an Expressiong<Func<T,string>> instead of just the Func, but the only way I have found of making that work is to replace the entire body of the Select statement with the expression, which then required the expression to return an object with both the value and search properties.

Is there any way of using a Func or Expression to only create the value of the search property in the anonymous object?

解决方案

As you've mentioned, you need to accept an Expression in your function, rather than a Func, for EF to be able to actually translate the query.

What you're looking for is the ability to compose expressions, just as you can compose functions:

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]);
}

This relies on the following method to replace all instances of one expression with another:

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);
}

Now you can write your method easily enough:

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到实体使用`Func`在一个生成匿名对象的select语句中创建属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆