Linq to entities use `Func` to create property in a select statement that produces an anonymous object

回眸只為那壹抹淺笑 提交于 2019-12-14 01:49:37

问题


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?


回答1:


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


来源:https://stackoverflow.com/questions/43782110/linq-to-entities-use-func-to-create-property-in-a-select-statement-that-produc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!