我正在尝试构建一个表达式树,我可以将其提供给Linq2SQL,以便它可以生成一个很好的干净查询。我的目的是构建一个过滤器,它将任意一组单词带到AND和NOT(或OR和NOT)。因为我想改变我搜索的字段,我最好通过调用各种辅助函数来组合Expresssion<Func<T, string, bool>>
的列表(其中T是我正在操作的实体)。然后我会收到一个单词的数组并通过它们循环并构建一个Expresssion<Func<T, bool>>
(在必要时否定某些表达式),我最终可以提供给.Where语句。
我一直在使用LINQKit PredicateBuilder,但此代码处理单个参数表达式。但是,它为我自己的尝试提供了一些基础。我的目标是做这样的事情:
var e = (Expression<Func<Entity, string, bool>>)((p, w) => p.SomeField.ToLower().Contains(w));
var words = new []{"amanda", "bob"};
var expr = (Expression<Func<Entity, bool>>)(p => false);
// building up an OR query
foreach(var w in words) {
var w1 = w;
>>>>expr = Expression.Lambda<Func<Entity, bool>>(Expression.OrElse(expr.Body, (Expression<Func<Entity, bool>>)(p => e(p, w))));
}
var filteredEntities = table.Where(expr);
但是因为我使用了表达式标记为&gt;&gt;&gt;&gt;的行显然是非法的(不能像我能做的那样e(p, w)
)。所以我的问题是如何将单个变量(单词)部分应用于包含多个参数的函数的表达式?
好的,在LINQPad中摆弄并想出了一个适合我的解决方案。 This question让我在那里。我很擅长构建表达式树,所以我会赞赏(并赞成)任何改进或批评的评论/答案。
// Some set of expressions to test against
var expressions = new List<Expression<Func<Entity, string, bool>>>();
expressions.Add((p, w) => p.FirstName.ToLower().Contains(w));
expressions.Add((p, w) => p.LastName.ToLower().Contains(w));
expressions.Add((p, w) => p.Department != null && p.Department.Name.ToLower().Contains(w));
var words = new []{"amanda", "bob"};
var negs = new []{"smith"}; // exclude any entries including these words
var isAndQuery = true; // negate for an OR query
Expression<Func<Entity, bool>> posExpr = p => isAndQuery;
var entityParameter = Expression.Parameter(typeof(Entity), null);
// Build up the NOTs
var negExpr = (Expression<Func<Entity, bool>>)(p => true);
foreach(var w in negs) {
var w1 = w;
foreach(var e in expressions) {
var andNot = Expression.Invoke(e, entityParameter, Expression.Constant(w1));
negExpr = Expression.Lambda<Func<Entity, bool>>(Expression.AndAlso(negExpr.Body, Expression.Not(andNot)), entityParameter);
}
}
// Build up the ANDs or ORs
foreach(var w in words) {
var w1 = w;
var orExpr = (Expression<Func<Entity, bool>>)(p => false);
foreach(var e in expressions) {
var orElse = Expression.Invoke(e, entityParameter, Expression.Constant(w1));
orExpr = Expression.Lambda<Func<Entity, bool>>(Expression.OrElse(orExpr.Body, orElse), entityParameter);
}
var orInvoked = Expression.Invoke(orExpr, posExpr.Parameters.Cast<Expression>());
if(isAndQuery)
posExpr = Expression.Lambda<Func<Entity, bool>>(Expression.AndAlso(posExpr.Body, orInvoked), entityParameter);
else
posExpr = Expression.Lambda<Func<Entity, bool>>(Expression.OrElse(posExpr.Body, orInvoked), entityParameter);
}
var posInvoked = Expression.Invoke(posExpr, posExpr.Parameters.Cast<Expression>());
var finalExpr = Expression.Lambda<Func<Entity, bool>>(Expression.AndAlso(negExpr.Body, posInvoked), entityParameter);
var filteredEntities = entities.Where(finalExpr);
答案 0 :(得分:0)
此示例可能会对您有所帮助。我想最好是在没有lambdas的情况下构建表达式:
public class Entity
{
public Entity(string someField)
{
SomeField = someField;
}
public string SomeField { get; set; }
}
class Program
{
static void Main(string[] args)
{
var entities = new[] {new Entity("fooBar"), new Entity("barBaz"), new Entity("baz"), new Entity("foo")};
entities.Where(BuildExpression("ar","az").Compile())
.ToList()
.ForEach(e => Console.WriteLine(e.SomeField));
Console.ReadLine();
}
public static Expression<Func<Entity, bool>> BuildExpression(params string[] words)
{
var parameter = Expression.Parameter(typeof (Entity));
var matchs = words.Select(word =>
{
var property = Expression.Property(parameter, "SomeField");
var toLower = Expression.Call(property, "ToLower", new Type[] {});
var contains = Expression.Call(toLower, "Contains",
new Type[]{},
Expression.Constant(word));
return contains;
}).OfType<Expression>();
var body = matchs.Aggregate(Expression.Or);
return Expression.Lambda<Func<Entity, bool>>(body, new[] {parameter});
}
}
如果我要在此答案中添加更多信息,请与我们联系。
答案 1 :(得分:0)
我喜欢使用linq来构建epression树,它让我感觉超级强大,所以我添加了这个,不是作为你的问题的完整答案,而是更多一种优雅的方式来构建表达式树...
var query = ...;
var search = "asdfasdf";
var fields = new Expression<Func<MyEntity,string>>[]{
x => x.Prop1,
x => x.Prop2,
x => x.Prop3
};
var notFields = new Expression<Func<MyEntity,string>>[]{
x => x.Prop4,
x => x.Prop5 };
//-----
var paramx = Expression.Parameter(query.ElementType);
//get fields to search for true
var whereColumnEqualsx = fields
.Select(x => Expression.Invoke(x,paramx))
.Select(x => Expression.Equal(x,Expression.Constant(search)))
//you could change the above to use .Contains(...) || .StartsWith(...) etc.
//you could also make it not case sensitive by
//wraping 'x' with a .ToLower() expression call,
//and setting the search constant to 'search.ToLower()'
.Aggregate((x,y) => Expression.And(x,y));
//get fields to search for false
var whereColumnNotEqualsx = notFields
.Select(x => Expression.Invoke(x,paramx))
.Select(x => Expression.NotEqual(x, Expression.Constant(search)))
//see above for the different ways to build your 'not' expression,
//however if you use a .Contains() you need to wrap it in an Expression.Negate(...)
.Aggregate((x,y) => Expression.Or(x,y));
//you can change Aggregate to use Expression.And(...)
//if you want the query to exclude results only if the
//search string is in ALL of the negated fields.
var lambdax = Expression.Lambda(
Expression.And(whereColumnEqualsx, whereColumnNotEqualsx), paramx);
var wherex = Expression.Call(typeof(Queryable)
.GetMethods()
.Where(x => x.Name == "Where")
.First()
.MakeGenericMethod(query.ElementType),
query.Expression,lambdax);
//create query
var query2 = query.Provider.CreateQuery(wherex).OfType<MyEntity>();