[原文作者]:Chris Smith and John Stallo
[原文链接]:A Walkthrough of WCF Support in Visual Studio 2008
在这里, 我想强调一个Visual Studio 2008支持的功能: Windows Communication Foundation(WCF)。在.NET Framework 3.0中,WCF作为下一代的communications API-在以前,如果你希望应用程序间对话,你需要提前确定一些事情:
· 传递速度有多重要?
· 应用程序如何会话?通过互联网,内联网,或者在同一台计算机上?
· 可靠性重要吗?
· 消息加密重要吗?
一旦确定了这些问题并且开始编写程序的时候,你将被迫去学习新的协议[.NET Remoting, ASMS Web Services, MSMQ, 命名管道(Named pipes)等等],并且要使用这些特定的API实现你的解决方案。问题是,在这种方式下,你的代码不会优美的适应变化。如果需求有了很大改动,你就需要用可以支持任何关键功能的API来重新编写整个通讯层。
WCF通过提供统一的编程接口而解决了这个问题,从而你就可以只学一种API和工具了。它通过隔离不同的通讯要素[比如协议(protocols)、消息合同(message contracts)等等]来实现这一点。有了WCF, 就不用担心每一个消息是如何在服务器和客户端之间传递的,你只需把精力集中在服务和消息合同(message contracts)上,也就是说,你在客户端能够调用什么方法,以及数据返回的结构。所有的通讯协议都可以被配置(在App.config中),而不用放在代码里。以上是我们从整体上了解WCF,现在让我们钻研一下它在Visual Studio 2008中能为我们做什么。
我们会创建一个明为“Northwind Traders”的三层架构应用程序。我们的解决方案包括四个工程:数据访问层、WCF服务、WinForm客户端和一个商业逻辑组件。在此过程中,我们会强调Visual Studio 2008一些主要的新功能,并且证明编写一个以数据为中心的应用程序是多么容易。
创建数据访问层
要创建数据访问层,我们首先创建一个新的Class Library 工程,命名为“DataAccessLayer”。
用Visual Studio开发N层应用程序,一个常见的问题是它把表适配器(TableAdapters)和类型化数据集(typed DataSets)放在同一个工程里。它的不幸在于,这样会泄露一些敏感信息,比如连接字符串(connection strings)。那么如何重用类型化数据集(typed DataSets)并保持表适配器私有(TableAdapters)呢?目前相当多的开发工具都不能直接实现,幸运的是,Visual Studio 2008解决了这个问题。
在Visual Studio 2008里,一旦创建了数据访问层,你就可以通过配置数据集设计器(DataSet Designer)来将数据集的代码生成到系统一个单独的工程中。这样你就可以在不暴露连接字符串(connection strings)的情况下,实现类型化数据集(typed DataSets)的共享和重用。
添加另外一个类库(class library)工程到系统中,命名为“NorthwindBuisnessObjects”。

然后重新打开NorthwindDataSet并且在属性窗口中把“DataSet Project’”的值设为“NorthwindBuisnessObjects”。现在,生成一下工程(Build the Project),NorthwindBuisnessObjects工程就会自动产生一个NorthwindDataSet.DataSet.Designer.vb文件。
接下来我们往数据集(DataSet)里加一些简单的验证逻辑。双击数据集设计器(DataSet Designer),并把一下的代码加进去(注意:在正确的工程中,DataSet.vb会自动生成)。
Partial Class NorthwindDataSet
Partial Class OrdersDataTable
Private Sub OrdersDataTable_OrdersRowChanging(ByVal sender As System.Object, ByVal e As OrdersRowChangeEvent) Handles Me.OrdersRowChanging
End Sub
Private Sub OrdersDataTable_ColumnChanged(ByVal sender As Object, ByVal e As System.Data.DataColumnChangeEventArgs) Handles Me.ColumnChanged
If e.Column.ColumnName = Me.OrderDateColumn.ColumnName Or e.Column.ColumnName = Me.ShippedDateColumn.ColumnName Then
ValidateDates(e.Row)
End If
End Sub
'*********************************************
'我的数据集的客户验证逻辑
'*********************************************
Private Sub ValidateDates(ByVal row As OrdersRow)
If row.OrderDate > row.ShippedDate Then
row.SetColumnError(Me.OrderDateColumn, "Can't ship before it's been ordered")
row.SetColumnError(Me.ShippedDateColumn, "Can't ship before it's been ordered")
Else
row.SetColumnError(Me.OrderDateColumn, "")
row.SetColumnError(Me.ShippedDateColumn, "")
End If
End Sub
End Class
End Class
最后再加入一个命名为“NorthwindManager”的类(class),它的作用是连接数据库(database)并且返回表(tables)。这样,我们的数据访问层就完成了。
Imports NorthwindBuisnessObjects
Public Class NorthwindManager
Public Shared Function GetCustomers() As NorthwindDataSet.CustomersDataTable
Dim customerAdapter As New NorthwindDataSetTableAdapters.CustomersTableAdapter()
Return customerAdapter.GetData()
End Function
Public Shared Function GetOrders() As NorthwindDataSet.OrdersDataTable
Dim orderAdapter As New NorthwindDataSetTableAdapters.OrdersTableAdapter()
Return orderAdapter.GetData()
End Function
End Class
创建WCF服务
下面创建一个WCF服务。这和创建ASMX Web服务很类似。我们创建一个WCF Web服务工程(WCF Web Service Project),也就是创建一个新的由IIS管理(host)的WCF服务。

注意,WCF并不一定是基于Web的服务。Visual Studio 2008也为服务库(service libraries)提供了工程模板(project templates),这是WCF服务就被包装在类库(class libraries)里。另外我们有一个可以调试WCF服务的“Test Form”,这里不作讨论。
建立好服务工程(service project),我们添加指向DataAccessLayer和BuisnessObjects工程的引用。
然后我们把默认的服务合同(contract)改为我们自己的。<ServiceContract()> 和 <OperationContract()>说明这是一个WCF服务合同(contract)。
Imports NorthwindBuisnessObjects
<ServiceContract()> _
Public Interface IService
<OperationContract()> _
Function GetCustomers() As NorthwindDataSet.CustomersDataTable
<OperationContract()> _
Function GetOrders() As NorthwindDataSet.OrdersDataTable
End Interface
然后对服务合同(service contract)的实现进行相应的改变。
Imports NorthwindBuisnessObjects
Imports DataAccessLayer
Public Class Service
Implements IService
Public Function GetCustomers() As NorthwindDataSet.CustomersDataTable Implements IService.GetCustomers
Return DataAccessLayer.NorthwindManager.GetCustomers()
End Function
Public Function GetOrders() As NorthwindDataSet.OrdersDataTable Implements IService.GetOrders
Return DataAccessLayer.NorthwindManager.GetOrders()
End Function
End Class
创建WCF客户端
截止到目前,我们的服务器端已经完成了,现在我们创建一个Windows窗体程序(WinForm Applicaition)来使用它。然后再解决方案资源管理器(Solution Explorer)中右击窗体程序(Winform)工程,选中“添加服务引用…”(Add Service Reference…),这时就会弹出以下对话框:

在图中,服务已经被列出来了。对于已知的Web服务(Web Services),不论ASMX 还是WCF, 都可以将其地址输入到服务URL,然后点击”Go”;对于本解决方案(Solution)中的服务,只需点击“发现”(Discover),所有的服务就会被列出来。
点击“高级”(Advanced),“服务引用设置对话框”(Service Reference Settings dialog)就会被弹出:

我们可以在这个对话框中配置WCF服务引用,但是在80%的情况下用默认选项就可以了。也可以在添加服务应用完成后,在打开这个对话框进行配置。在Visual Studio 2005中,为了改变Web引用的一些高级属性,我们得用命令行和开发工具包(SDK)来重新生成代理(Proxy);而在Visual Studio 208中,我们就可以在集成开发环境(IDE)中直接配置服务引用。
代理类型重用
现在我们谈一下Visual Studio 2008中WCF最酷的功能之一:代理类型重用。在这之前,我们先看看问题所在。打开生成的代理代码(proxy code)我们看到服务返回“NorthwindDataSet’” 类型,但它不是指“BuisnessObjects.NorthwindDataset”类型,而是“Client.ServiceReference.NorthwindDataSet”。这个生成的类型是个假像:看起来可以序列化(serialized)/反序列化(deserialized),但他并不包含数据集(DataSet)的实际方法,也就是我们所说的验证逻辑。

这就引出了“面向服务的构架”:客户端和服务端可以把一个对象序列化(serialize)为同一种格式,而不考虑对象本身是什么类型(甚至不必考虑是什么语言,实际上,JAVA程序员就可以使用WCF服务)。
但是我们不希望使用那个“假的” NorthwindDataSet,因为那样意味着要在两个地方维护验证逻辑的两个版本。因为我们可以同时控制客户端和服务器端,代理类型重用允许我们重用结合了验证逻辑的基本数据类型。在ASMX Web服务中,必须用命令行工具来实现这种代码重用,而Visual Studio 2008默认实现了它。
要实现代理类型重用,只要在我们的客户端添加一个应用到NorthwindBuisnessObjects。然后再解决方案资源管理器(Solution Explorer)右击“服务引用”(ServiceReference)并选中“更新”(Update)。这样服务引用就被更新,而服务代理业被重新生成。默认情况下,我们扫描所有的工程引用并重用它们,所以生成的代理并不会暴露NorthwindBuisnessObjects工程中的数据表(datatables)。
下面我想谈一下WCF服务引用生成的配置文件。打开客户端的app.config文件,你会发现一个新的Section“<system.serviceModel >”,它包含了WCF服务引用的所用设置。同时,服务器端也有一个类似的web.config文件。
考虑到安全角度,这个客户端配置文件默认设定最大接受信息长度(MaxRecievedMessageSize)为65536:如果在运行时(runtime)服务器端向客户端发送的数据超过它的最到值的,就会抛出异常。因为要传输很大的数据集(DataSets),我们打开app.config文件并把元素(element)“<bindings>\<wsHttpBinding>\<binding>”的值设为5000000。如果你不想在配置文件中手动改,就用工具(Tools)菜单下的微软服务配置编辑器(Microsoft Service Configuration Editor)。
目前,我们已经正确的添加和配置了我们的服务。接下来我们对服务返回的数据表(data tables)做一下数据邦定(data binding)。打开数据源窗口(Data Sources window),把表(table)“Orders”拖拽到Form1上。

最后,为了从我们的WCF服务获取数据,我们在Form1的OnLoad事件中添加一些代码。
Public Class Form1
Private m_serviceClient As New ServiceReference.ServiceClient()
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.OrdersBindingSource.DataSource = m_serviceClient.GetOrders()
End Sub
End Class
一切就绪!按“F5”运行我们的程序!

现在我们有了一个三层构架的程序,它通过一个孤立的数据访问层,从WCF服务端获取数据。还记得我说过代理类型重用很酷吗?这就是原因:由于在重用同一个数据集类型,我们可以借用BusinessObjects层验证逻辑的优势。试一下改变ShippedDate 的值,让它小于OrderData。

注意,DataGrid会显示一个错误并给出提示信息。
总结
我们论证了如何利用WCF、代理类型重用(ProxyTypeSharing)和其他的新功能来组织数据层。最后的结果是:我们可以用最短的时间创建一个强大的应用程序。不过,这只是WCF最普通的应用而已。
[有关WCF的更多信息请参考:]
MSDN上的WCF资料:http://msdn2.microsoft.com/en-us/library/ms735119.aspx
关于WCF的博客“That Indigo girl”:http://www.thatindigogirl.com/default.aspx
[原文作者]: Kevin Halverson
[原文链接]: How to implement IQueryable
随着Orcas(Visual Studio 2008的code name)的推出,微软计划为我们经常用到的一些数据应用的场合提供Linq的支持。比如:DLinq用于SQL Server,Xlinq则用于XML相关的处理。事实上还有其他数不清的数据读取的场合,用户也希望能用上Linq这个方便快捷的工具。多数情况下,我们只需要把数据放到CLR Collection里,用Linq基本的支持足以。举个例子来说:要是你想找到“我的文档”里的所有新放进来的.exe文件,可以用下边这句:
Dim newExe = From fileName In Directory.GetFiles( _
My.Computer.FileSystem.SpecialDirectories.MyDocuments, _
"*.exe", SearchOption.AllDirectories) _
Where (New FileInfo(fileName)).CreationTime > #6/30/2007# _
Select fileName
很简单,是吧?
这样看起来很好,唯一的缺点是没有涉及到我们这篇文章的主题“实现IQueryable”。下边还是看看怎么通过实现IQueryable达到我们的目的,也就是为Linq提供我们自已的Linq Provider。
在当今的开发领域,已经存在很多的程序接口和对象模型提供对各式各样的数据的读取和操作。比如:Windows Desktop Search (关于它的详细内容参见http://www.microsoft.com/windows/desktopsearch/default.mspx)
它就提供了一个OLE DB Provider让你能查询系统已经建立好的各种文件信息的索引。那么,我们可不可以不用写SQL语句而是使用Linq来访问这些内容呢?答案是:能,但我们要实现Linq Provider。对相关背景知识不太了解而又有兴趣的朋友来说,本文末尾的参考资源很有必要,不妨一看。
写一个自己的Linq provider,首先要做的就是实现IQueryable和IQueryProvider这两个接口。因为我们要操作的是文件对象(FileInfo),所以要实现IQueryable(Of FileInfo),代码如下:
Imports System.IO
Public Class WDSQueryObject
Implements IQueryable(Of FileInfo), IQueryProvider
End Class
当你在Visual Studio里敲出(估计没有人真的会敲)或是Paste出上面的这些代码,IDE会提示你要实现这两个接口,有几个方法你必须要实现。下面我一一介绍:
注意:
l 完整的代码在末尾的给出的链接里能找到,下载下来并调试运行一下,收获更大。
l 我的代码基于Orcas Beta2。IQueryable接口在Beta1版本后有过重构。
CreateQuery
IQueryProvider 接口里面定义了两个CreateQuery方法. 一个返回泛型的 IQueryable(Of TElement)。另外一个返回非泛型的 Iqueryable。大多数情况下你可能只需要在非泛型的里面调用泛型的那个。就象下面这样:
Public Function CreateQuery(ByVal expression As Expression) As IQueryable Implements IQueryProvider.CreateQuery
Return CreateQuery1(Of FileInfo)(expression)
End Function
对一个简单查询来说,每一个“Where”子句中的过滤条件都会调用一次CreatQuery,每一个“Select”会调用一次CreatQuery 。像下面这一句:
Dim r = From file In index _
Where file.Name Like "%.exe" _
Select file.FullName
表达式“file.Name Like "%.exe"”和“file.FullName”分别会调用一次CreateQuery方法。下面是我实现处理这两个语句的代码的框架:
Public Function CreateQuery1(Of TElement)(ByVal expression As Expression) As IQueryable(Of TElement) Implements IQueryProvider.CreateQuery
Dim querySource As IQueryable(Of TElement) = Nothing
Dim nodeType = expression.NodeType
Select Case nodeType
Case ExpressionType.Call
Dim m As MethodCallExpression = expression
Dim methodName = m.Method.Name
Select Case methodName
Case "Select"
' insert Select processing code
Case "Where"
' insert Where processing code
Case Else
Throw New NotSupportedException("Queries using '" & methodName & "' are not supported for this collection.")
End Select
Case Else
Throw New NotSupportedException("Creating a query from an expression of type '" & nodeType & "' is supported.")
End Select
Return querySource
End Function
你可能注意到了我们在CreateQuery里得到的expression已经包括了调用者的信息(比如:Select,Where等等),我们将用到这些信息来处理剩下的内容。在这个文件系统的例子里,“Where”子句是比较有意思的,我们先来谈谈它。Where是Queryable中定义的一个扩展方法,它的签名如下:
Public Shared Function Where(Of TSource)( _
ByVal source As IQueryable(Of TSource), _
ByVal predicate As Expression(Of System.Func(Of TSource, Boolean)) _
) As System.Linq.IQueryable(Of TSource)
你要是察看它的expression tree里的详细信息(下图),你会发现关于上面签名的信息被处理过了。按层次展开下面的expression tree,就能得到上面签名的对应结构。第一层:方法名是Where<FileInfo>,返回类型是IQueryable<FileInfo>。第二层是两个参数:一个是作为source的WDSQueryObject,还有一个是作为predicate的lambda表达式。
下面是上面代码中“' insert Where processing code”部分的内容,我来详细介绍一下:
m_query = New StringBuilder()
m_funclets = New List(Of KeyValuePair(Of String, Func(Of String)))()
Dim lambda As LambdaExpression = CType(m.Arguments(1), UnaryExpression).Operand
ExpandExpression(lambda.Body)
m_query.Insert(0, "SELECT System.ItemPathDisplay FROM SystemIndex WHERE NOT CONTAINS(System.ItemType, 'folder') AND (")
m_query.Append(")")
querySource = Me
想必大家都了解,上面的“SELECT”那一句是用来拼接SQL字符串用的。关于如何写用于Windows Desktop Search的SQL语句,请参考后面给出的关于WDS的链接。这里要说明的是,在第二次调用CreateQuery时才会处理Linq中的Select部分。从上面的Expression tree中我们可以看出,Where的第一个参数(是个常量表达式)是指向我们的WDSQueryObject的一个引用。这个参数也就是实现IQueryable.Expression的返回值。第二个参数是需要被我们转换成SQL语句的lambda表达式,这个也就是我们实现Iqueryable的核心。我们要做的就是,把Linq表达式转换成一组指令用于从制定的数据原理取得数据。在我的代码里,是由方法ExpandExpression具体负责这一转换过程的。它遍历整个expression tree并把它展开转换成相对应的SQL语句。方法ExpandExpression返回的时候,m_query里就包含了与Linq中where条件相对应的SQL语句。然后调用
m_query.Insert(0, "SELECT System.ItemPathDisplay FROM SystemIndex WHERE NOT CONTAINS(System.ItemType, 'folder') AND (")
m_query.Append(")")
就构造出了完整的SQL句子。
下面是ExpandExpression的代码:
Private Sub ExpandExpression(ByVal e As Expression)
Select Case e.NodeType
Case ExpressionType.And
ExpandBinary(e, "AND")
Case ExpressionType.Equal
ExpandBinary(e, "=")
Case ExpressionType.GreaterThan
ExpandBinary(e, ">")
Case ExpressionType.GreaterThanOrEqual
ExpandBinary(e, ">=")
Case ExpressionType.LessThan
ExpandBinary(e, "<")
Case ExpressionType.LessThanOrEqual
ExpandBinary(e, "<=")
Case ExpressionType.NotEqual
ExpandBinary(e, "!=")
Case ExpressionType.Not
ExpandUnary(e, "NOT")
Case ExpressionType.Or
ExpandBinary(e, "OR")
Case ExpressionType.Call
ExpandCall(e)
Case ExpressionType.MemberAccess
ExpandMemberAccess(e)
Case ExpressionType.Constant
ExpandConstant(e)
Case Else
Throw New NotSupportedException("Expressions of type '" & e.NodeType.ToString() & "' are not supported.")
End Select
End Sub
我们通过判断expression tree的节点类型,对希望能够支持的操作,调用了相应的处理方法。虽然在这儿我们没有支持所有的表达式类型,可最常用的基本都包括了(凑合够用了)。下面我们看看一个简单的Linq 语句是怎样得到处理的。如下:
Dim index As New WDSQueryObject
Dim cutoffDate = #6/28/2007#
Dim r = From file In index _
Where file.CreationTime > cutoffDate And _
file.Name Like "%.exe" _
Select file.FullName
在处理Where的expression tree的过程中,第一个被调用的方法是ExpandBinary。ExpandBinary又会调用ConcatBinary,ConcatBinary通过合适的操作符来把左右两边连到一块儿(在这儿是“AND”)。
Private Sub ExpandBinary(ByVal b As BinaryExpression, ByVal op As String)
ConcatBinary(b.Left, b.Right, op)
End Sub
Private Sub ConcatBinary(ByVal left As Expression, ByVal right As Expression, ByVal op As String)
ExpandExpression(left)
m_query.Append(" ")
m_query.Append(op)
m_query.Append(" ")
ExpandExpression(right)
End Sub
处理“And”语句左边部分会再次调用ConcatBinary (这次是处理“>”),接着会调用ExpandMemberAccess ,如下:
Private Sub ExpandMemberAccess(ByVal m As MemberExpression)
Dim member = m.Member
Dim e = m.Expression
Select Case e.NodeType
Case ExpressionType.Parameter
' Parameter processing code
Case ExpressionType.Constant
' Constant processing code
Case Else
Throw New NotSupportedException("Accessing member '" & member.Name & "' is not supported in this context.")
End Select
End Sub
先来看看代码中的‘Parameter processing code’。此处,‘Parameter‘就是整个查询中用到的迭代器,也就是指“From file In index”中的“file”。我们要做的就是把FileInfo 类型的属性的名称(如:file.CreationTime)转换成.net中对应的windows文件系统的属性名称。如下:
Private Function GetAttributeName(ByVal m As MemberInfo) As String
Dim name As String
Dim memberName = m.Name
Select Case memberName
Case "CreationTime"
name = "System.DateCreated"
Case "Name"
name = "System.FileName"
Case Else
Throw New NotSupportedException("Using the property '" & memberName & "' in filter expressions is not supported.")
End Select
Return name
End Function
跟前面一样,目前我们对属性的支持并不完整,但这并不妨碍我们的理解和简单的使用。完整的支持请参考本文末尾的链接。
下面介绍一下 ‘Constant processing code’. 在这儿我们把对变量cutoffDate的访问“翻译” 成SQL语言。 如下:
Dim valueName = "[value" & m_funclets.Count & "]"
Dim valueFunc As Func(Of String) = Nothing
Dim memberType = member.MemberType
If m.Type Is GetType(String) OrElse m.Type Is GetType(Date) Then
m_query.Append("'")
m_query.Append(valueName)
m_query.Append("'")
Else
m_query.Append(valueName)
End If
Dim funclet As Func(Of String) = Nothing
Select Case memberType
Case MemberTypes.Field
Dim f As FieldInfo = member
Dim c As ConstantExpression = e
If m.Type Is GetType(Date) Then
funclet = Function() CDate(f.GetValue(c.Value)).ToString("yyyy-MM-dd")
Else
funclet = Function() CStr(f.GetValue(c.Value))
End If
Case Else
Throw New NotSupportedException("Accessing member of type'" & memberType & "' is not supported.")
End Select
m_funclets.Add(New KeyValuePair(Of String, Func(Of String))(valueName, funclet))
看到上面的代码,很多朋友会问里面那个“funclet”是什么东东?用来做啥?这个就涉及到了Linq架构中一个很重要的特点”延迟执行”。换句话说,在我们建立一个query时,我们只是定义了它,并没有运行它(废话)。而有很多信息只有运行时才能被获知,比如说cutoffDate的值#6/28/2007#。这就是说我们没有办法在执行query前验证这个query(拿到#6/28/2007#,并查询底层的数据源)。因此,我们想存储关于如何得到cutoffDate 的值的信息,而不是它的具体的值。我在这所做的就是,在查询字符串中放了一个占位符([value*]),并建了一个函数,让这个函数在可以拿到查询结果的时候返回cutoffDate的值。
我在这儿用到了lambda表达式,当你想创建一个inline函数或匿名代理的时候,用lambda表达式很方便。它同时会自动创建一个closure类来保存我在当前block里读到的所有变量的信息。比如上面:当代码进入“Case” 后,就会生成一个新的closure类,变量 ‘f’ 和‘c’的值都会存到里面。编译器会自动把对这些局部变量的读取转换成对相应的closure类的字段的读取。执行的query时候,就会执行上面的“funclet”来替换占位符[value*],这要就能在运行query时拿到变量的值(而不是在query被创建的时候)。你可能会注意到cutoffDate的MemberAccessExpression,同样是一个已被提升过的局部变量。这也就是为什么它的成员类型是 “Field”,正因为是在query中用到cutoffDate ,他的值其实是存到了closure 类中的一个字段里。
关于Closures类的相关内容请参考:http://blogs.msdn.com/vbteam/search.aspx?q=closure+&p=1
接下来谈谈“file.Name Like "%.exe"”。你可能会奇怪为什么我们在ExpandCall 里处理这一部分,而不是ExpandBinary。事实上,VB编译器把一些的二元操作符直接转换成对VB运行库中相应方法的调用。这给VB添加了一些CLR没有的功能。比如:LikeString (由VB中的“Like”操作生成) 和CompareString (由VB中的字符串比较的表达式例如““a” = “A” ”生成)。下面是我实现的ExpandCall 中处理LikeString的一段:
Private Sub ExpandCall(ByVal m As MethodCallExpression, Optional ByVal op As String = "")
Dim methodName = m.Method.Name
Select Case methodName
Case "LikeString"
ConcatBinary(m.Arguments(0), m.Arguments(1), "LIKE")
Case Else
Throw New NotSupportedException("Using method '" & methodName & "' in a filter expression is not supported.")
End Select
End Sub
处理“Where”语句所做的最后一件事就是处理常量字符串值"%.exe"。这一步很简单,值得在此一提的是,一些数据类型默认的转换操作不一定适用于你的数据源。比如下面:WDS 就要求日期必须是指定的格式。
Private Sub ExpandConstant(ByVal c As ConstantExpression)
Dim value = c.Value
If value.GetType() Is GetType(String) Then
m_query.Append("'")
m_query.Append(CStr(value))
m_query.Append("'")
ElseIf value.GetType() Is GetType(Date) Then
m_query.Append("'")
m_query.Append(CDate(value).ToString("yyyy-MM-dd"))
m_query.Append("'")
Else
m_query.Append(value.ToString())
End If
End Sub
处理完“Where”语句后,最终我们得到的传给WDS的字符串如下:
"SELECT System.ItemPathDisplay FROM SystemIndex WHERE NOT CONTAINS(System.ItemType, 'folder') AND (System.DateCreated > '[value0]' AND System.FileName LIKE '%.exe')"
在我的下一篇博客里,会讲到GetEnumerator和Select。
Resources
Full source code for this project:
http://hresult.members.winisp.net/FileSystemQuery.zip
Bart De Smet’s excellent blog on Implementing IQueryable for Linq to LDAP:
http://community.bartdesmet.net/blogs/bart/archive/2007/04/05/the-iqueryable-tales-linq-to-ldap-part-0.aspx
Fabrice Marguerie’s blog in implementing Linq to Amazon:
http://weblogs.asp.net/fmarguerie/archive/2006/06/26/Introducing-Linq-to-Amazon.aspx
Catherine Heller’s blog on Windows Desktop (Vista) Search:
http://blogs.msdn.com/cheller/archive/2006/06/21/642220.aspx
List of query attributes supported by the Windows filesystem
http://msdn2.microsoft.com/en-us/library/aa830600.aspx