2013-05-16 29 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