2009-02-16 18 views
8

可以說我的數據庫中有一些名爲Stuff的東西,名爲Id。從用戶中,我得到了一系列選定的Range對象(或者我從他們的輸入中創建它們)和他們想要的ID。該結構的精簡版本是這樣的:C#,Linq2SQL:創建謂詞以查找多個範圍內的元素

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

問題,是它與NotSupportedException異常崩潰,因爲選擇(O)調用: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:當然,如果我能會很好擴展到比int更多,如DateTime等,但不知道如何最終使用> =和< =運算符... CompareTo是否與linq-to-sql一起使用?如果沒有,創建兩個沒有問題。一個用於int,另一個用於DateTime,因爲這主要是用於這個類型的類型。

注2:它將用於報告,用戶將能夠根據不同的事情縮小出現的範圍。比如,我希望這份報告適合那些人和那些日期。

回答

7

使用泛型是有問題的,因爲C#不支持泛型操作符 - 這意味着您必須手動編寫表達式。正如我們已經看到的,字符串的工作方式不同。但對於剩下的,怎麼樣像(未經測試):

編輯多個範圍)

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

隨着使用(上羅斯文測試):

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

+0

這將如何與整個系列的範圍對象? – Svish 2009-02-16 14:36:28

+0

你可以用OrElse做同樣的...我會更新... – 2009-02-16 14:42:53

0

你得到這個錯誤,因爲LINQ to SQL的所有內容都需要採用Expression的形式。試試這個

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

請注意,我在使用它之前編譯選擇器。這應該很順利,我過去曾經使用過類似的東西。