代码改变世界

[翻译]使用LINQ实现动态搜索(Implementing Dynamic Searching Using LINQ)

2008-08-29 03:11  G yc {Son of VB.NET}  阅读(1352)  评论(0编辑  收藏  举报

            原文链接:http://blogs.msdn.com/vbteam/archive/2007/08/29/implementing-dynamic-searching-using-linq.aspx

                作者: Jonathan

 

  

 

在数据窗体应用程序中一个常见的需求是使用户能够按照由任意字段组合的动态查询搜索。例如,程序中的搜索功能允许用户查找所有满足在多个列上定义了不同规则的记录。

 

        

 

 

LINQ 使得编写跨越多种数据源的(强大)查询变得简单。例如,我们可以使用下面的查询来查找在用户指定的时间段内所有运往指定国家的所有订单(Orders

 

Dim query = From order In db.Orders _

            Where order.ShipCountry = txtCountry.Text _

            And order.ShippedDate >= dtpStartDate.Value _

            And order.ShippedDate <= dtpEndDate.Value

 

这可以很容易的在编译时做到,但是如果我们希望检查订单日期而不是出货(Shipped)日期?这使得我们不得不编写另一个使用order.OrderDate的查询。如果你仅仅生成SQL字符串,那么运行时动态的生成不全是困难的,但LINQ如何做这个?LINQ 是否要求我在编译时指定条件为了建立查询?幸运的是,答案是肯定的。LINQ支持在运行时通过表达式 API 和表达式编译器(Expression Tree API and the Expression Compiler)来建立动态查询。

 

Visual Studio 2008中,任何有效的VB表达式都被解释成表达式树(Expression Tree )。我们要做的是建立一个表达式树(Expression Tree )来表示用户条件,然后在传递它到LINQ to SQLLINQ to SQL运行时将会把它转换成SQL语句。那么我们上面说的 Where语句第一个部分看起来像这个样子:

 

  

Dim p = Expression.Parameter(GetType(Order), "")

Dim order = GetType(Order).GetProperty("ShipCountry")

Dim expr = Expression.Equal(Expression.PropertyOrField(p, order.Name), Expression.Constant("Germany"))

Dim predicate = Expression.Lambda(Of Func(Of Order, Boolean))(expr, New ParameterExpression() {p})

 

Yikes,这只是一小部分,我们目前只完成了1/3的Where语句!我可不想在写12行代码去来在运行时建立Where语句。我想做的是写一个CreateCondition 扩展方法,允许我用一行简单的代码建立表达式树(Expression Tree),像是这个样:

 

 

Dim condition1 = db.Orders.CreateCondition("ShipCountry", Compare.Equal, "Germany")

 

 那么对于ShippedDate 我们可以建立2个条件:

 

Dim startDate? = #1/1/1997#

Dim endDate? = #1/31/1997#

 

Dim condition2 = db.Orders.CreateCondition("ShippedDate", Compare.GreaterThanOrEqual, startDate)

Dim condition3 = db.Orders.CreateCondition("ShippedDate", Compare.LessThanOrEqual, endDate)

 

 

注意我们第一次传递一个字符串,之后又传入2个可为空的日期 Literal ,我们可以这么做是因为CreateCondition 方法是泛型的并可以根据传入的参数推断类型。我们现在仅需要组合条件到一个条件中:

 

 

Dim c = Condition.Combine(condition1, Compare.And, condition2, condition3)

 

或者我们也可以用操作符重载(Operator Overloading )来完成这个(等价于上面的代码):

 

Dim c = condition1 And condition2 And condition3

 

 

 OK现在我们可以建立 我们的 Condition  对象来过滤数据:

 

'过滤所有不符合条件的Orders

 

'注意 这个查询还没有执行,他将延期执行 

 

Dim filteredQuery = db.Orders.Where(c)

 

'我们还可以执行其他操作(像是排序)在 filteredQuery 

 

 

Dim query = From row In filteredQuery _

            Order By row.OrderDate, row.OrderID _

            Select row

 

'执行查询并显示结构到DataGridView1

 

 

DataGridView1.DataSource = query 

 

到现在为止,还不错。我们使用动态构造条件和强类型等 LINQ上。 我们还有一个到数据库问题,由于使用了延期执行它将确保直到我们列举数据(通过数据绑定)之前都不会执行。条件APICondition API )是用来简单的构造和编译表达式树,但是我们还需要写一点代码来构建基于用户输入的条件。 ConditionBuilder 控件
 
     
 
 
 
 
它允许用户在运行时动态指定条件
 
 
           
 
注意那个日期字段我们将自动使用DateTimePicker 代替TextBox,还有布尔值 我们将结果放入Checkbox
 
    
 
OK,我们隐藏了在运行时的用户体验,但是我们怎么实际创建Condition API?文档中以的代码在下面,我不打算在全部解释,但这里只解释一下基础部分:
 
    
    1、有3个主要类的: Condition, Condition(Of T), and Condition(Of T, S)
 
         
    1. Condition 是一个抽象类用来构造一般版本(generic versions)通过构造这个,可以为一般版本参数推断带来好处,即我们不用担心传入的一般版本参数到方法,工厂方法将帮助我们解决。
    2. Condition(Of T) 使用合并多个conditions 到一起。T是元素类型(即上面例子中的 Order
    3. Condition(Of T, S)是一种简单类型,它表示“object.propery <comparison> 表达式。类型参数 S将被推断为相应的值类型传入(即 String, Date, Boolean ...
 
 
2、执行本地查询 我们将 Lambda 表达式 编译成一个代理,所以它可以在内存中执行(executed in-memory)。用户可以调用这个代理通过调用匹配方法。
 
 
 
            '编译lambda 表达式成委托
 
del = DirectCast(LambdaExpr.Compile(), Func(Of T, Boolean))   
   
     3、扩展方法实际上 是定义在 IQueryable(Of T) 对于远程执行,和IEnumerable(Of T) 对于本地执行