6

次の式があるとします。

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

これは基本的に、要素のセットを 20 個のパーティションに「分割」し、各セットから最初と 4 番目の要素を取得します。

この式は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]or[8]や orを渡すことができ[1, 2, 3, 4, 5, 6, ..., 16]ます)。

BinaryExpressionsなどを使用してみましたが、このメッセージを正しく作成できませんでした。主な問題は、述語を MongoDB に渡すときにほとんどの試みが失敗することです。「ハードコードされた」バージョンは正常に動作しますが、動的式を渡そうとするすべての試みが、C# ドライバーによって MongoDB クエリに変換されません。

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

基本的に、「ハードコードされた」バージョンに対してコンパイラが生成するものを正確に複製するように、実行時に式を動的に構築したいと考えています。

どんな助けでも大歓迎です。

編集

コメントでリクエストされた(およびpastebin に投稿された)ように、以下の私の試みの1つ。ペーストビンがそれを削除するか、サービスを停止するか、または...

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 に答える