运行时创建LINQ表达式

说我有这个表达式:

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

这基本上将一组元素“划分”为20个分区,并从每个集合中检索每个第一和第四个元素。

此表达式传递给MongoDB ,它的驱动程序完全能够转换为MongoDB“查询”。 但是,谓词也可以用在对象列表(LINQ2Objects)等上。我希望这个表达式可以重用( DRY )。 但是,我希望能够传入IEnumerable来指定要检索的项目(因此1和4不是“硬编码”到其中):

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

使用LINQPad使用此代码:

 int setsize = 20; Expression<Func> predicate = x => x.Seed % setsize == 1 || x.Seed % setsize == 4; predicate.Dump(); } class Foo { public int Seed { get; set; } 

我可以检查一下表达式:

表达

现在,我希望能够构建这个表达式的精确再现,但是要传递可变数量的整数(因此,我可以通过代替1和4,例如, [1, 5, 9, 11][8][1, 2, 3, 4, 5, 6, ..., 16] )。

我已尝试使用BinaryExpressions等,但无法正确构造此消息。 主要问题是,在将谓词传递给MongoDB时,我的大多数尝试都会失败。 “硬编码”版本工作正常,但不知怎的,我传递动态表达式的所有尝试都无法通过C#驱动程序转换为MongoDB查询:

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

基本上,我想在运行时动态构建表达式,使其完全复制编译器为“硬编码”版本生成的内容。

任何帮助将不胜感激。

编辑

根据评论中的要求 (并发布在pastebin上 ),我在下面尝试了一个。 我将它发布在问题中,如果pastebin将其删除或停止其服务或者…

 using MongoRepository; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; class Program { static void Main(string[] args) { MongoRepository repo = new MongoRepository(); var reporesult = repo.All().Where(IsInSet(new[] { 1, 4 }, 20)).ToArray(); } private static Expression<Func> IsInSet(IEnumerable 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>>(seed => x => x.Seed % setsize == seed).JoinByOr(); } } public class Foo : Entity { public int Seed { get; set; } } public static class Extensions { public static Expression<Func> JoinByOr(this IEnumerable<Expression<Func>> 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>(body, param); } } 

这导致: Unsupported where clause:

试试这个:

 public Expression> GetExpression( int setSize, int[] elements, Expression> 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>(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> GetExpression( int setSize, int[] elements, Expression> 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>(body, parameter); } 

现在,调用将如下所示:

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

在这种情况下,明确指定x的类型很重要,否则类型推断将不起作用,您必须同时指定Foo和属性的类型作为GetExpression通用参数。