2013-05-16 64 views
6

的說我有以下表達式:運行時創建LINQ表達

int setsize = 20; 
Expression<Func<Foo, bool>> predicate = x => x.Seed % setsize == 1 
              || x.Seed % setsize == 4; 

這基本上'分區的一組元素到20個分區的,並且從每個設置的每個第一和第四元件檢索。

該表達式被傳遞給MongoDB,它是driver完全能夠翻譯成MongoDB「查詢」。但是,謂詞也可以用於對象列表(LINQ2Objects)等。我希望此表達式可以重用(DRY)。不過,我希望能夠在IEnumerable<int>傳遞給指定要檢索的項目(所以1和4不「硬編碼」到它):

public Expression<Func<Foo, bool>> GetPredicate(IEnumerable<int> items) { 
    //Build expression here and return it 
} 

隨着LINQPad使用此代碼:

int setsize = 20; 
Expression<Func<Foo, bool>> predicate = x => x.Seed % setsize == 1 || x.Seed % setsize == 4; 
predicate.Dump(); 

} 

class Foo 
{ 
    public int Seed { get; set; } 

我可以檢查表達:

Expression

現在,我希望能夠建立這種表達的真實再現,但與可變數量的整數通過(所以,而不是1和4我可以通過,例如,[1, 5, 9, 11][8][1, 2, 3, 4, 5, 6, ..., 16])。

我試過使用BinaryExpressions等,但一直無法正確構造此消息。主要問題是我的attempt的大多數在將謂詞傳遞給MongoDB時會失敗。 的「硬編碼」版本工作正常但不知何故,所有我試圖通過我的動態表情無法被翻譯成由C#驅動程序的MongoDB查詢:

{ 
    "$or" : [{ 
     "Seed" : { "$mod" : [20, 1] } 
    }, { 
     "Seed" : { "$mod" : [20, 4] } 
    }] 
} 

基本上,我要動態地構建在運行時的表達以這種方式,它完全複製了編譯器爲「硬編碼」版本生成的內容。

任何幫助將不勝感激。

編輯

As requested in the comments(和posted on pastebin),下面我嘗試之一。我張貼在furure參考問題應該引擎收錄把它記下來或停止其serivce或...

using MongoRepository; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Linq.Expressions; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     MongoRepository<Foo> repo = new MongoRepository<Foo>(); 
     var reporesult = repo.All().Where(IsInSet(new[] { 1, 4 }, 20)).ToArray(); 
    } 

    private static Expression<Func<Foo, bool>> IsInSet(IEnumerable<int> seeds, int setsize) 
    { 
     if (seeds == null) 
      throw new ArgumentNullException("s"); 

     if (!seeds.Any()) 
      throw new ArgumentException("No sets specified"); 

     return seeds.Select<int, Expression<Func<Foo, bool>>>(seed => x => x.Seed % setsize == seed).JoinByOr(); 
    } 
} 

public class Foo : Entity 
{ 
    public int Seed { get; set; } 
} 

public static class Extensions 
{ 
    public static Expression<Func<T, bool>> JoinByOr<T>(this IEnumerable<Expression<Func<T, bool>>> filters) 
    { 
     var firstFilter = filters.First(); 
     var body = firstFilter.Body; 
     var param = firstFilter.Parameters.ToArray(); 
     foreach (var nextFilter in filters.Skip(1)) 
     { 
      var nextBody = Expression.Invoke(nextFilter, param); 
      body = Expression.Or(body, nextBody); 
     } 
     return Expression.Lambda<Func<T, bool>>(body, param); 
    } 
} 

這導致:Unsupported where clause: <InvocationExpression>

+0

請顯示一些 - 或者至少一個 - 你的嘗試。 –

+0

這裏你去:http://pastebin.com/qDwXGGit。這導致:'Unsupported where clause:'。 – RobIII

回答

3

試試這個:

public Expression<Func<Foo, bool>> GetExpression<T>(
    int setSize, int[] elements, 
    Expression<Func<Foo, T>> property) 
{ 
    var seedProperty = GetPropertyInfo(property); 
    var parameter = Expression.Parameter(typeof(Foo)); 
    Expression body = null; 

    foreach(var element in elements) 
    { 
     var condition = GetCondition(parameter, seedProperty, setSize, element); 
     if(body == null) 
      body = condition; 
     else 
      body = Expression.OrElse(body, condition); 
    } 

    if(body == null) 
     body = Expression.Constant(false);   

    return Expression.Lambda<Func<Foo, bool>>(body, parameter);  
} 

public Expression GetCondition(
    ParameterExpression parameter, PropertyInfo seedProperty, 
    int setSize, int element) 
{ 
    return Expression.Equal(
     Expression.Modulo(Expression.Property(parameter, seedProperty), 
          Expression.Constant(setSize)), 
     Expression.Constant(element)); 
} 

public static PropertyInfo GetPropertyInfo(LambdaExpression propertyExpression) 
{ 
    if (propertyExpression == null) 
     throw new ArgumentNullException("propertyExpression"); 

    var body = propertyExpression.Body as MemberExpression; 
    if (body == null) 
    { 
     throw new ArgumentException(
      string.Format(
       "'propertyExpression' should be a member expression, " 
       + "but it is a {0}", propertyExpression.Body.GetType())); 
    } 

    var propertyInfo = body.Member as PropertyInfo; 
    if (propertyInfo == null) 
    { 
     throw new ArgumentException(
      string.Format(
       "The member used in the expression should be a property, " 
       + "but it is a {0}", body.Member.GetType())); 
    } 

    return propertyInfo; 
} 

你會這樣稱呼它:

GetExpression(setSize, elements, x => x.Seed); 

如果你希望它是通用在Foo另外,你需要改變這樣的:

public static Expression<Func<TEntity, bool>> GetExpression<TEntity, TProperty>(
    int setSize, int[] elements, 
    Expression<Func<TEntity, TProperty>> property) 
{ 
    var propertyInfo = GetPropertyInfo(property); 
    var parameter = Expression.Parameter(typeof(TEntity)); 
    Expression body = null; 

    foreach(var element in elements) 
    { 
     var condition = GetCondition(parameter, propertyInfo , setSize, element); 
     if(body == null) 
      body = condition; 
     else 
      body = Expression.OrElse(body, condition); 
    } 

    if(body == null) 
     body = Expression.Constant(false); 

    return Expression.Lambda<Func<TEntity, bool>>(body, parameter);  
} 

現在,呼叫是這樣的:

GetExpression(setSize, elements, (Foo x) => x.Seed); 

在這種情況下明確指定的x的類型是很重要的,否則,類型推斷不會工作,你就必須同時指定Foo和的類型屬性作爲GetExpression的一般參數。

+0

聖盃!這樣可行!謝謝!現在,不要成爲一個屁股或什麼的,有什麼辦法可以避免使用''種子''字符串,以便重構等,這也將被拿起?更具體地說:我不能「僅僅」傳遞一個「lamba」或其他東西來使這個更「通用」?無論哪種方式:非常感謝這個答案! – RobIII

+0

@RobIII:我更新了我的答案是重構保存。 –

+0

你,先生,是純粹的史詩般的勝利!非常感謝! – RobIII