运行时创建LINQ表达(Runtime creation of LINQ expression)

2019-09-03 15:26发布

说我有这个表达式:

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

这基本上'分区的一组元素到20个分区的,并且从每个检索设定的每个第一和第四元件。

这个表达式传递到MongoDB中 ,它的驱动程序是完全能够翻译成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][8][1, 2, 3, 4, 5, 6, ..., 16]

我已经尝试使用BinaryExpressions等,但一直没能正确地构建该消息。 主要的问题是,大多数我的企图传递谓词MongoDB的当s将失败。 在“硬编码”版本工作正常 ,但不知何故我所有试图通过我的动态表情无法被翻译成由C#驱动程序的MongoDB查询:

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

基本上,我要动态地构建在运行时表达的这样一种方式,它究竟是什么复制编译器生成的“硬编码”的版本。

任何帮助将不胜感激。

编辑

按照要求在评论 (和张贴在引擎收录 ),下面我尝试之一。 我张贴在furure参考问题应该引擎收录把它记下来或停止其serivce或...

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>

Answer 1:

试试这个:

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



文章来源: Runtime creation of LINQ expression