6

说我有这个表达:

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

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

这个表达式被传递给MongoDB,它的驱动程序完全能够转换为 MongoDB“查询”。但是,谓词也可以用于对象列表(LINQ2Objects)等。我希望这个表达式是可重用的(DRY)。但是,我希望能够传入 anIEnumerable<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]or[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<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>

4

1 回答 1

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.

于 2013-05-16T09:37:48.960 回答