问题
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