【问题标题】:Runtime creation of LINQ expressionLINQ 表达式的运行时创建
【发布时间】:2013-05-16 09:22:00
【问题描述】:

假设我有这个表达式:

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

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

这个表达式被传递给MongoDB,它的driver 完全能够翻译成MongoDB“查询”。但是,谓词也可以用于对象列表(LINQ2Objects)等。我希望这个表达式是可重用的(DRY)。但是,我希望能够传入 IEnumerable&lt;int&gt; 来指定要检索的项目(因此 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] 或 @ 987654339@)。

我曾尝试使用BinaryExpressions 等,但无法正确构造此消息。主要问题是我的大部分attempts 在将谓词传递给 MongoDB 时都会失败。 “硬编码”版本运行良好,但不知何故,我所有尝试传递动态表达式的尝试都未能被 C# 驱动程序转换为 MongoDB 查询:

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

基本上,我想在运行时动态构建表达式,使其完全复制编译器为“硬编码”版本生成的内容。

任何帮助将不胜感激。

编辑

As requested in the comments(和posted on 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: &lt;InvocationExpression&gt;

【问题讨论】:

  • 请展示一些 - 或至少一个 - 你的尝试。
  • 给你:pastebin.com/qDwXGGit。这导致:Unsupported where clause: &lt;InvocationExpression&gt;.

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


【解决方案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 的通用参数.

【讨论】:

  • 天哪!这样可行!谢谢!现在,不要成为一个混蛋或其他东西,有什么办法可以避免使用"Seed" 字符串,以便在重构等时也会被拾取?更具体地说:我不能“只是”通过“兰巴”或其他东西来使它更“通用”吗?无论哪种方式:非常感谢这个答案!
  • 你,先生,是一场史诗般的胜利!非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-19
  • 1970-01-01
  • 1970-01-01
  • 2016-02-28
  • 2014-01-31
  • 2017-02-22
  • 1970-01-01
相关资源
最近更新 更多