Linq for entities uses `Func` to create a property in a select statement that creates an anonymous object

I am working on a simple text search method using linq for objects that I would like to reuse in multiple places that look something 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 assignable extension method Func

that concatenates a property search

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

      

Then it can be used like this:

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

      

I think this is pretty neat and it compiles happily, but it won't work because Linq for entities doesn't know what to do with the .Invoke()

method Func

. I have a few more SO questions I should probably use Expressiong<Func<T,string>>

instead of just Func

, but the only way I found this to work was to replace the entire text of the Select expression with an expression, which then required the expression to return an object with both value properties, so and search properties.

Can it be used Func

or Expression

only to create a value for a lookup property in an anonymous object?

+2


source to share


1 answer


As you mentioned, you need to accept Expression

in your function, not Func

so that EF can actually translate the request.

What you're looking for is the ability to compose expressions the same way you can create 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]);
}

      

It depends 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:

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

      

0


source







All Articles