2011-04-12 25 views
2

我想構建一個表達式樹,我可以將它們饋入Linq2SQL,以便它會生成一個很好的乾淨的查詢。我的目的是建立一個過濾器,將任意一組單詞與AND和NOT(或OR和NOT)放在一起。因爲我想改變我搜索的字段,所以我最好通過調用各種幫助函數將Expresssion<Func<T, string, bool>>的列表組合在一起(其中T是我正在操作的實體)。然後,我會收到一組單詞並通過它們進行循環,然後構建一個Expresssion<Func<T, bool>>(在必要時否定某些表達式),最終可以將它們提供給.Where語句。C中的currying表達式#

我一直在使用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); 

但因爲我使用的表達式通過>>>>標線顯然是非法的(不能做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

我敢肯定,你見過http://stackoverflow.com/questions/3657843/linq-to-sql-query-help-string-contains-any-string-in-string-array已經但只是以防萬一你沒有。 – 2011-04-12 06:41:33

回答

0

這個例子可能會幫助你。我想最好的是建立無lambda表達式的表達式:

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

請讓我知道如果我應該添加更多信息到這個答案。

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