运行时创建LINQ表达式

时间:2013-05-16 09:22:00

标签: c# linq lambda linq-to-objects mongodb-.net-driver

说我有这个表达:

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

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

此表达式传递给MongoDBdriver完全能够转换为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]或{{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); } }

1 个答案:

答案 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的通用参数。