2017-08-18 39 views
0

是否可以使用LambdaExpressions動態生成這樣的謂詞?使用包含多個參數的BinaryExpression創建謂詞

Expression<Func<Test, bool>> predicate = t => 
    t.Levels.Any(l => 
     l.LevelDetails.Any(ld => 
      ld.LevelDate > DbFunctions.AddDays(t.TestDate, 1) 
     ) 
    ); 

只要在內部BinaryExpression的參數是相同的或表達的右側部分是恆定的,是沒有問題的。但示例表達式ld.LevelDate > DbFunctions.AddDays (t.TestDate, 1)包含兩個彼此獨立的不同的ExpressionParameters。我正在尋找的東西是這樣的:

Expression<Func<LevelDetail, DateTime?>> left = 
    ld => ld.LevelDate; 
Expression<Func<Test, DateTime?>> right = 
    t => DbFunctions.AddDays(t.TestDate, 1); 

BinaryExpression expr = 
    Expression.GreaterThan(
     ((LambdaExpression)left).Body, 
     ((LambdaExpression)right).Body 
    ); 
Expression<Func<Test, bool>> predicate = t => 
    t.Levels.Any(l => 
     l.LevelDetails.Any(**expr**) 
    ); 

class Test { 
    public DateTime TestDate { get; set; } 
    public virtual ICollection<Level> Levels { get; set; } 
} 
class Level { 
    public virtual ICollection<LevelDetail> LevelDetails { get; set; } 
} 
class LevelDetail { 
    public DateTime LevelDate { get; set; } 
} 

親切的問候!

+1

你不能這樣做,* *在一個表達式樹,但你可以* *構建表達式樹來做到這一點。 't.Levels.Any(...)'是''''上''MemberExpression'上的'MethodCallExpression' ...等等。您可以使用LINQPad作爲獲取表達式樹的結構而不用替換表達式的幫助,然後編寫代碼來構建帶有替換的表達式樹。 –

回答

0

正如@Matt Warren的答案中指出的,如果yow想要組合lambda表達式,您需要手動完成並需要設置正確的表達參數。

Firstlly,您將需要一個ExpressionVisitor,可替換的節點要:

private class SwapVisitor : ExpressionVisitor 
    { 
     public readonly Expression _from; 
     public readonly Expression _to; 

     public SwapVisitor(Expression from, Expression to) 
     { 
      _from = from; 
      _to = to; 
     } 

     public override Expression Visit(Expression node) => node == _from ? _to : base.Visit(node); 
    } 

其次,你將需要手工lambda表達式組合:

private static Expression<Func<Test, bool>> CreatePredicate() 
    { 
     Expression<Func<LevelDetail, DateTime?>> left = ld => ld.LevelDate; 
     // I didn't include EF, so I did test it just use directly Test.TestDate 
     //Expression<Func<Test, DateTime?>> right = t => t.TestDate; 
     Expression<Func<Test, DateTime?>> right = t => DbFunctions.AddDays(t.TestDate, 1); 

     var testParam = Expression.Parameter(typeof(Test), "test_par"); 
     var levelParam = Expression.Parameter(typeof(Level), "level_par"); 
     var detailParam = Expression.Parameter(typeof(LevelDetail), "detail_par"); 

     // Swap parameters for right and left operands to the correct parameters 
     var swapRight = new SwapVisitor(right.Parameters[0], testParam); 
     right = swapRight.Visit(right) as Expression<Func<Test, DateTime?>>; 

     var swapLeft = new SwapVisitor(left.Parameters[0], detailParam); 
     left = swapLeft.Visit(left) as Expression<Func<LevelDetail, DateTime?>>; 

     BinaryExpression comparer = Expression.GreaterThan(left.Body, right.Body); 
     var lambdaComparer = Expression.Lambda<Func<LevelDetail, bool>>(comparer, detailParam); 

     // Well, we created here the lambda for ld => ld.LevelDate > DbFunctions.AddDays(t.TestDate, 1) 

     var anyInfo = typeof(Enumerable).GetMethods().Where(info => info.Name == "Any" && info.GetParameters().Length == 2).Single(); 

     // Will create **l.LevelDetails.Any(...)** in the code below 

     var anyInfoDetail = anyInfo.MakeGenericMethod(typeof(LevelDetail)); 
     var anyDetailExp = Expression.Call(anyInfoDetail, Expression.Property(levelParam, "LevelDetails"), lambdaComparer); 
     var lambdaAnyDetail = Expression.Lambda<Func<Level, bool>>(anyDetailExp, levelParam); 

     // Will create **t.Levels.Any(...)** in the code below and will return the finished lambda 

     var anyInfoLevel = anyInfo.MakeGenericMethod(typeof(Level)); 
     var anyLevelExp = Expression.Call(anyInfoLevel, Expression.Property(testParam, "Levels"), lambdaAnyDetail); 
     var lambdaAnyLevel = Expression.Lambda<Func<Test, bool>>(anyLevelExp, testParam); 

     return lambdaAnyLevel; 
    } 

和下面的代碼包含使用其中:

var predicate = CreatePredicate(); 

    var levelDetail = new LevelDetail { LevelDate = new DateTime(2017, 08, 19) }; 
    var level = new Level { LevelDetails = new List<LevelDetail> { levelDetail } }; 
    var test = new Test { TestDate = new DateTime(2027, 08, 19), Levels = new List<Level> { level } }; 

    var result = predicate.Compile()(test); 
0

當您使用嵌套lambda構建表達式時,內部lambda的表達式將能夠訪問外部lambda的參數。它與表達式<T> lambdas一樣,與常規C#lambda一樣工作。

如果你被與表達式<牛逼工作> lambda表達式,並試圖將它們結合起來,你需要在API級別(用手做)與他們合作,而不是指望自動C#語言的語法來表達<T>轉換,以幫助你。需要注意的一件事:當您創建兩個原始lambda表達式(通過轉換爲表達式<T>)時,它們每個都有自己的ParameterExpression實例,這將使它們無法組合,因爲兩個實體都需要引用(除非您使用ExpressionVisitor替換另一個)

0

我會推薦使用nein-linq結合起來,建立和撰寫謂詞(和許多其他表達拼圖) 或LinqKit

兩者都支持實體框架

例如,使用nein-LINQ

鑑於:

public static class TestExpressions 
{ 
    [InjectLambda] 
    public static bool IsTestDateEarlierThan(this Test test, DateTime? dateTime, int numberOfDays) 
    { 
     return dateTime > test.TestDate.AddDays(numberOfDays); 
    } 

    public static Expression<Func<Test, DateTime?, int, bool>> IsTestDateEarlierThan() 
    { 
     return (test, dateTime, numberOfDays) => dateTime > DbFunctions.AddDays(test.TestDate, numberOfDays); 
    } 

    // Simple caching... 
    private static readonly Func<Test, int, bool> _hasAnyLevelDateAfterTestDays = HasAnyLevelDateAfterTestDays().Compile(); 

    [InjectLambda] 
    public static bool HasAnyLevelDateAfterTestDays(this Test test, int numberOfDays) 
    { 
     return _hasAnyLevelDateAfterTestDays(test, numberOfDays); 
    } 

    public static Expression<Func<Test, int, bool>> HasAnyLevelDateAfterTestDays() 
    { 
     return (test, numberOfDays) => test.Levels.Any(l => l.LevelDetails.Any(ld => test.IsTestDateEarlierThan(ld.LevelDate, numberOfDays))); 
    }  
} 

何時:

var testList = new List<Test> 
{ 
    new Test { 
     Levels = new List<Level> { 
      new Level { 
       LevelDetails = new List<LevelDetail> { 
        new LevelDetail { 
         LevelDate = DateTime.Today 
        } 
       } 
      } 
     }, 
     // Not matched 
     TestDate = DateTime.Today 
    }, 
    new Test { 
     Levels = new List<Level> { 
      new Level { 
       LevelDetails = new List<LevelDetail> { 
        new LevelDetail { 
         LevelDate = DateTime.Today 
        } 
       } 
      } 
     }, 
     // Not matched 
     TestDate = DateTime.Today.AddDays(-1) 
    }, 
    new Test { 
     Levels = new List<Level> { 
      new Level { 
       LevelDetails = new List<LevelDetail> { 
        new LevelDetail { 
         LevelDate = DateTime.Today 
        } 
       } 
      } 
     }, 
     // Matched 
     TestDate = DateTime.Today.AddDays(-2) 
    } 
}; 

然後:

var testQuery = testList.AsQueryable(); 

// Alternative one 
var result1 = testQuery 
    .ToInjectable() // Don't forget!! 
    .Where(test => test.Levels.Any(l => l.LevelDetails.Any(ld => test.IsTestDateEarlierThan(ld.LevelDate, 1)))) 
    .ToList(); 

// Alternative two: You get the point :) 
var result2 = testQuery 
    .ToInjectable() // Don't forget!! 
    .Where(test => test.HasAnyLevelDateAfterTestDays(1)) 
    .ToList();