说我有这个表达:
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; }
我可以检查一下表达式:
现在,我希望能够构建这个表达式的精确复制,但是要传递可变数量的整数(因此,我可以通过而不是1和4,例如,[1, 5, 9, 11]
或{{1 }或[8]
)。
我尝试过使用BinaryExpressions等,但无法正确构建此消息。主要问题是,在将谓词传递给MongoDB时,我的大多数attempt都会失败。 “硬编码”版本可以正常运行,但不知怎样,我传递动态表达式的所有尝试都无法通过C#驱动程序转换为MongoDB查询:
[1, 2, 3, 4, 5, 6, ..., 16]
基本上,我想在运行时动态构建表达式,使其完全复制编译器为“硬编码”版本生成的内容。
任何帮助将不胜感激。
修改
As requested in the comments(和posted on pastebin),我在下面尝试之一。我将它发布在问题中,因为如果pastebin将其取下或停止服务或者... ...
{
"$or" : [{
"Seed" : { "$mod" : [20, 1] }
}, {
"Seed" : { "$mod" : [20, 4] }
}]
}
结果为: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);
}
}
。
答案 0 :(得分: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
的通用参数。