运行时创建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
通用参数。