C#,Linq2SQL:创建谓词以查找多个范围内的元素

假设我的数据库中有一个名为Stuff的东西,名为Id。 从用户那里我得到了一系列选定的Range对象(或者我是从输入中创建它们)和他们想要的ID。 该结构的精简版本如下所示:

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

例如,人们可以得到:

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

然后我想用它来创建一个谓词,只选择那些之间具有值的东西。 例如,使用PredicateBuilder ,我可以这样做:

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

然后:

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

哪个有用! 我现在想做的是创建一个通用扩展方法来为我创建这些谓词。 有点像这样:

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

这里的问题是,由于选择器(ø)调用它与NotSupportedException崩溃: Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.

我想这是可以理解的。 但有没有办法解决这个问题? 我想最终得到的是我可以做到:

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

或者甚至更好,创建一些返回IQueryable的东西,这样我就可以做到:

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

那么,有什么想法吗? 我真的想让这个工作,因为如果不是我将获得大量的foreach代码块,创建谓词…


注1:当然,如果我可以扩展到更多int,比如DateTime等,但不确定如何使用> =和<=运算符,那将会很好… CompareTo是否可以与linq-to一起使用的sql? 如果没有,创建两个没有问题。 一个用于int,一个用于DateTime,因为这主要是将用于的类型。

注2:它将用于报告,用户将能够根据不同的事情缩小出来的范围。 就像,我希望这份报告适合那些人和那些日期。

使用generics是有问题的,因为C#不支持generics运算符 – 这意味着您必须手动编写表达式。 正如我们已经看到的,字符串的工作方式不同。 但对于其他人来说,如何(未经测试):

(针对多个范围编辑

  public static IQueryable WhereBetween( this IQueryable source, Expression> selector, params Range[] ranges) { return WhereBetween(source, selector, (IEnumerable>) ranges); } public static IQueryable WhereBetween( this IQueryable source, Expression> selector, IEnumerable> 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>(body, param)); } 

注意; Expression.Invoke的使用意味着它可能适用于LINQ-to-SQL但不适用于EF(目前;希望在4.0中修复)。

使用(在Northwind上测试):

 Range range1 = new Range(0,10), range2 = new Range(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

您收到该错误,因为LINQ to SQL的所有内容都需要以Expression的forms出现。 试试这个

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

请注意,我在使用之前编译选择器。 这应该可以解决问题,我过去曾经使用过类似的东西。