8

データベースにStuffという名前のプロパティがあり、Idというプロパティがあるとします。ユーザーから、選択したRangeオブジェクトのシーケンスを取得します(または、入力から作成します)。必要なIDを使用します。その構造体の簡略版は次のようになります。

public struct Range<T> : IEquatable<Range<T>>, IEqualityComparer<Range<T>>
{
    public T A;
    public T B;
    public Range(T a, T b)
    {
        A = a;
        B = b;
    }
    ...
}

したがって、たとえば、次のようになります。

var selectedRange = new List<Range<int>>
    {
        new Range(1, 4),
        new Range(7,11),
    };

次に、それを使用して、それらの間に値を持つものだけを選択する述語を作成したいと思います。たとえば、PredicateBuilderを使用すると、たとえば次のように実行できます。

var predicate = PredicateBuilder.False<Stuff>();
foreach (Range<int> r in selectedRange)
{
    int a = r.A;
    int b = r.B;
    predicate = predicate.Or(ø => ø.Id >= a && ø.Id <= b);
}

その後:

var stuff = datacontext.Stuffs.Where(predicate).ToList();

どちらがうまくいくか!私が今やりたいのは、これらの述語を作成するためのジェネリック拡張メソッドを作成することです。このようなもの:

public static Expression<Func<T,bool>> ToPredicate<T>(this IEnumerable<Range<int>> range, Func<T, int> selector)
{
    Expression<Func<T, bool>> p = PredicateBuilder.False<T>();
    foreach (Range<int> r in range)
    {
        int a = r.A;
        int b = r.B;
        p = p.Or(ø => selector(ø) >= a && selector(ø) <= b);
    }
    return p;
}

ここでの問題は、selector(ø)呼び出しが原因でNotSupportedExceptionでクラッシュすることです。Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.

それは理解できると思います。しかし、これを回避する方法はありますか?私が最終的にやりたいのは、私ができるようにすることです。

var stuff = datacontext.Stuffs.Where(selectedRange.ToPredicate<Stuff>(ø => ø.Id));

または、さらに良いことに、IQueryableを返すものを作成して、次のことを実行できるようにします。

var stuff = datacontext.Stuffs.WhereWithin<Stuff>(selectedRange, ø => ø.Id); // Possibly without having to specify Stuff as type there...

それで、何かアイデアはありますか?私は本当にこれを機能させたいと思っています。そうでない場合は、コードのforeachブロックをたくさん取得して、述語を作成します...


注1:もちろん、DateTimeなどのようにint以上に拡張できればいいのですが、>=および<=演算子を使用するとどうなるかわかりません...CompareToはlinq-to-で機能しますかsql?そうでない場合は、2つ作成しても問題ありません。1つはint用、もう1つはDateTime用です。これは、ほとんどの場合、これが使用される型であるためです。

注2:レポートに使用され、ユーザーはさまざまなことに基づいて、何が出るかを絞り込むことができます。同様に、私はそれらの人々とそれらの日付のためにこのレポートが欲しいです。

4

2 に答える 2

7

C# はジェネリックの演算子をサポートしていないため、ジェネリックでの使用には問題があります。つまり、式を手動で記述する必要があります。そして、すでに見てきたように、string は異なる働きをします。しかし、残りについては、(未テスト)のようなものはどうですか:

(複数の範囲で編集)

    public static IQueryable<TSource> WhereBetween<TSource, TValue>(
        this IQueryable<TSource> source,
        Expression<Func<TSource, TValue>> selector,
        params Range<TValue>[] ranges)
    {
        return WhereBetween<TSource,TValue>(source, selector,
            (IEnumerable<Range<TValue>>) ranges);
    }

    public static IQueryable<TSource> WhereBetween<TSource, TValue>(
        this IQueryable<TSource> source,
        Expression<Func<TSource, TValue>> selector,
        IEnumerable<Range<TValue>> ranges)
    {
        var param = Expression.Parameter(typeof(TSource), "x");
        var member = Expression.Invoke(selector, param);
        Expression body = null;
        foreach(var range in ranges)
        {
            var filter = Expression.AndAlso(
                Expression.GreaterThanOrEqual(member,
                     Expression.Constant(range.A, typeof(TValue))),
                Expression.LessThanOrEqual(member,
                     Expression.Constant(range.B, typeof(TValue))));
            body = body == null ? filter : Expression.OrElse(body, filter);
        }            
        return body == null ? source : source.Where(
            Expression.Lambda<Func<TSource, bool>>(body, param));
    }

ノート; Expression.Invoke の使用は、おそらく LINQ-to-SQL では機能しますが、EF では機能しないことを意味します (現時点では、うまくいけば 4.0 で修正されます)。

使用法 (Northwind でテスト済み):

Range<decimal?> range1 = new Range<decimal?>(0,10),
                range2 = new Range<decimal?>(15,20);
var qry = ctx.Orders.WhereBetween(order => order.Freight, range1, range2);

TSQL の生成 (再フォーマット):

SELECT -- (SNIP)
FROM [dbo].[Orders] AS [t0]
WHERE (([t0].[Freight] >= @p0) AND ([t0].[Freight] <= @p1))
OR (([t0].[Freight] >= @p2) AND ([t0].[Freight] <= @p3))

ちょうど私たちが望んでいたもの;-p

于 2009-02-16T14:27:09.973 に答える
0

LINQ to SQLのすべてが式の形式である必要があるため、このエラーが発生します。これを試して

public static Expression<Func<T,bool>> ToPredicate<T>(
    this IEnumerable<Range<int>> range, 
    Expression<Func<T, int>> selector
) {
    Expression<Func<T, bool>> p = PredicateBuilder.False<T>();
    Func<T, int> selectorFunc = selector.Compile();
    foreach (Range<int> r in range)
    {
        int a = r.A;
        int b = r.B;
        p = p.Or(ø => selectorFunc(ø) >= a && selectorFunc(ø) <= b);
    }
    return p;
}

セレクターを使用する前にコンパイルしていることに注意してください。これは問題なく機能するはずです。私は過去にそのようなものを使用しました。

于 2009-02-16T14:29:37.877 に答える