在运行时创建LINQ表达 [英] Runtime creation of LINQ expression

查看:107
本文介绍了在运行时创建LINQ表达的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

说我有这个表达式:

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

这基本上是'分区的一组元素成20个分区,并检索每个设置每个第一和第四元素。

This basically 'partitions' a set of elements into 20 partitions and retrieves from each set each first and fourth element.

这表情传递给 MongoDB的这这是驱动是完全有能力转换成MongoDB的查询中。谓词可以,但是,也可以的对象(LINQ2Objects)名单等。我想这个表达式可重复使用(上使用的 DRY )。不过,我希望能够在 的IEnumerable<通过; INT> 来指定要检索的项目(所以1和4不是硬编码到它):

This expression is passed to MongoDB which it's driver is perfectly capable of translating into a MongoDB "query". The predicate can, however, also be used on a list of objects (LINQ2Objects) etc. I want this expression to be reusable (DRY). However, I want to be able to pass in an IEnumerable<int> to specify which items to retrieve (so 1 and 4 aren't "hardcoded" into it):

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; }



我可以检查表达式:

I can examine the expression:

现在,我希望能够建立这种表达的真实再现,但与整数变量量通过(这样而不是1和4,我可以通过,例如, [1,5,9,11] [8] [1,2,3,4,5,6,...,16] )。

Now, I want to be able to build an exact reproduction of this expression but with a variable amount of integers to pass (so instead of 1 and 4 I could pass, for example, [1, 5, 9, 11] or [8] or [1, 2, 3, 4, 5, 6, ..., 16]).

我曾尝试使用 BinaryExpressions 等,但一直没能正确构造该消息。主要的问题是,我的大多数试图旨意失败传递谓词MongoDB的时候。 的硬编码版本正常工作但不知何故,我所有的努力来传递我的动态表情无法由C#驱动程序转换成MongoDB的查询:

I have tried using BinaryExpressions etc. but haven't been able to construct this message correctly. The main issue is that most of my attempts will fail when passing the predicate to MongoDB. The "hardcoded" version works fine but somehow all my attempts to pass my dynamic expressions fail to be translated into a MongoDB query by the C# driver:

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

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

Basically, I want to dynamically build the expression at runtime in such a way that it exactly replicates what the compiler generates for the 'hardcoded' version.

任何帮助将不胜感激。

修改

< A HREF =http://stackoverflow.com/questions/16583565/runtime-creation-of-linq-expression#comment23832188_16583565>的要求,在评论(和的贴在引擎收录),下面我尝试之一。我张贴在furure参考问题应该引擎收录把它记下来或停止使用MongoRepository他们serivce或...

As requested in the comments (and posted on pastebin), one of my tries below. I'm posting it in the question for furure reference should pastebin take it down or stop their serivce or...

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);
    }
}

这导致:不受支持where子句:< InvocationExpression>

推荐答案

试试这个:

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);






如果您希望它是通用在另外,你需要改变它是这样的:


If you want it to be generic in Foo also, you need change it like this:

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);    
}

现在,呼叫会是这样的:

Now, the call would look like this:

GetExpression(setSize, elements, (Foo x) => x.Seed);

在这种情况下它来指定 X 明确,否则,类型推断将无法正常工作,你必须同时指定和财产作为通用的参数的类型 GetExpression

In this scenario it is important to specify the type of x explicitly, otherwise type-inference won't work and you would have to specify both Foo and the type of the property as generic arguments to GetExpression.

这篇关于在运行时创建LINQ表达的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆