随笔 - 89, 评论 - 163, 引用 - 33

导航

关于

标签

每月存档

最新留言

广告

演练:Visual Studio 2008 支持的WCF

[原文作者]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),我们添加指向DataAccessLayerBuisnessObjects工程的引用。

然后我们把默认的服务合同(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 2008WCF最酷的功能之一:代理类型重用。在这之前,我们先看看问题所在。打开生成的代理代码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服务获取数据,我们在Form1OnLoad事件中添加一些代码。

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

posted on 2007-10-30 16:02:00 by VBCTI  评论(14) 阅读(6553)

VB 表达式树–字符串比较

[原文作者]: Timothy Ng

[原文链接]: VB expression trees - string comparisons

 

上次,我简单的谈了表达式树以及它的用途。对于那些想写LINQ provider的人来说,表达式树很有意思,它能对lambda表达式进行推理从而得到LINQ provider。一般来说,任何想了解lambda表达式的人应该对表达式树也感兴趣。

今天,我打算专门为这些正在写代码实现表达式树的人写点东西。即便你现在没写这些代码,我仍然希望你能看下去,因为无论如何这些内容都是非常有趣的。

在这篇博客里我将讨论一下字符串的比较以及他们在表达式树里的表现。Jonathan(我们的PM)和我曾经写过一个LINQ provider的例子,我们认为这段代码非常有用,现在把它共享出来

VB里,通过对option compare的设置就可以用=操作符对区分大小写和不区分大小写的字符串进行比较。

为了实现这些,VB编译器在运行时调用了Microsoft.VisualBasic.CompilerServices.Operators.CompareString,这个方法有三个参数:其中2个是操作数,1个是标志符指示比较类型。

VB编译器为字符串相等操作符生成表达式树的时候,我们通过构造表达式来调用CompareString方法以保证字符串的正确比较。这是因为在默认的情况下,表达式树中“等于”节点的API负责区分大小写。而且,“等于”节点在VB中并未考虑到特殊字符串及字面上的“Nothing”的比较。

这就意味着如果你想通过VB编译器生成的表达式树来写一个visitor,你就一定会期望这些二元操作符如<, <=, =, >=, 以及 >,它们的左边为CompareString方法,右边为0。当你看到这些,你需要根据CompareString中第三个参数的使用方法来正确拆分它里面的参数。

但是,如果你的provider不进行字符串比较的话(比如LINQ to SQL),那么你只要用下面的代码,把方法调用中的二元运算符转换为方法调用参数中的二元运算符。

然后在你的visitor里调用这个方法进行转换,接着通过方法返回的节点运行visitor,将字符串比较作为二元操作符处理。不过这种方法无法区分大小写。

Friend Shared Function ConvertVBStringCompare(ByVal exp As BinaryExpression) As BinaryExpression

    If exp.Left.NodeType = ExpressionType.Call Then

        Dim compareStringCall = CType(exp.Left, MethodCallExpression)

        If compareStringCall.Method.DeclaringType.FullName = "Microsoft.VisualBasic.CompilerServices.Operators" _

            AndAlso compareStringCall.Method.Name = "CompareString" Then

            Dim arg1 = compareStringCall.Arguments(0)

            Dim arg2 = compareStringCall.Arguments(1)

 

            Select Case exp.NodeType

                Case ExpressionType.LessThan

                    Return Expression.LessThan(arg1, arg2)

                Case ExpressionType.LessThanOrEqual

                    Return Expression.GreaterThan(arg1, arg2)

                Case ExpressionType.GreaterThan

                    Return Expression.GreaterThan(arg1, arg2)

                Case ExpressionType.GreaterThanOrEqual

                    Return Expression.GreaterThanOrEqual(arg1, arg2)

                Case Else

                    Return Expression.Equal(arg1, arg2)

            End Select

        End If

    End If

    Return exp

End Function

下面这个例子并未做任何有用的事情,但你可以从中了解到如何使用上面定义的方法。

Sub VisitBinary(ByVal exp As BinaryExpression)

    exp = ConvertVBStringCompare(exp)

    Visit(exp.Left)

    Visit(exp.Right)

End Sub

 

希望这些对你有帮助!

Tim

posted on 2007-10-25 16:58:00 by VBCTI  评论(2) 阅读(4461)

如何实现IQueryable (Kevin Halverson)

[原文作者]: Kevin Halverson

[原文链接]: How to implement IQueryable

 

我刚写了一篇关于如何实现IQueryable的文章(创建一个自己的Linq provider)。

http://blogs.msdn.com/kevin_halverson/archive/2007/07/10/how-to-implement-iqueryable.aspx (http://blog.joycode.com/vbcti/archive/2007/10/19/109501.aspx)

 

通过实现定制的Linq provider,你就能用下面这种方式查询Windows Vista (Desktop) Search engine得到你系统里的文件信息:

        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, file.CreationTime

posted on 2007-10-19 16:27:00 by VBCTI  评论(2) 阅读(4295)

如何实现IQueryable

[原文作者]: Kevin Halverson

[原文链接]: How to implement IQueryable

 

随着OrcasVisual Studio 2008code name)的推出,微软计划为我们经常用到的一些数据应用的场合提供Linq的支持。比如:DLinq用于SQL ServerXlinq则用于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,首先要做的就是实现IQueryableIQueryProvider这两个接口。因为我们要操作的是文件对象(FileInfo),所以要实现IQueryable(Of FileInfo),代码如下:

 

Imports System.IO

Public Class WDSQueryObject

    Implements IQueryable(Of FileInfo), IQueryProvider

End Class

当你在Visual Studio里敲出(估计没有人真的会敲)或是Paste出上面的这些代码,IDE会提示你要实现这两个接口,有几个方法你必须要实现。下面我一一介绍:

注意:

l 完整的代码在末尾的给出的链接里能找到,下载下来并调试运行一下,收获更大

l 我的代码基于Orcas Beta2IQueryable接口在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已经包括了调用者的信息(比如:SelectWhere等等),我们将用到这些信息来处理剩下的内容。在这个文件系统的例子里,Where子句是比较有意思的,我们先来谈谈它。WhereQueryable中定义的一个扩展方法,它的签名如下:

    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>第二层是两个参数:一个是作为sourceWDSQueryObject,还有一个是作为predicatelambda表达式。

 

 

下面是上面代码中' 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 SearchSQL语句,请参考后面给出的关于WDS的链接。这里要说明的是,在第二次调用CreateQuery时才会处理Linq中的Select部分。从上面的Expression tree中我们可以看出,Where的第一个参数(是个常量表达式)是指向我们的WDSQueryObject的一个引用。这个参数也就是实现IQueryable.Expression的返回值。第二个参数是需要被我们转换成SQL语句的lambda表达式,这个也就是我们实现Iqueryable的核心。我们要做的就是,把Linq表达式转换成一组指令用于从制定的数据原理取得数据。在我的代码里,是由方法ExpandExpression具体负责这一转换过程的。它遍历整个expression tree并把它展开转换成相对应的SQL语句。方法ExpandExpression返回的时候,m_query里就包含了与Linqwhere条件相对应的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

在处理Whereexpression tree的过程中,第一个被调用的方法是ExpandBinaryExpandBinary又会调用ConcatBinaryConcatBinary通过合适的操作符来把左右两边连到一块儿(在这儿是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被创建的时候)。你可能会注意到cutoffDateMemberAccessExpression,同样是一个已被提升过的局部变量。这也就是为什么它的成员类型 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')"

 

在我的下一篇博客里,会讲到GetEnumeratorSelect

 

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

posted on 2007-10-18 17:22:00 by VBCTI  评论(1) 阅读(3403)

在微软我们怎样开发软件:一名准项目经理的视角(之一)

[原文作者]

[原文链接]How do we write software at Microsoft: a PM intern’s perspective #1


现在对于我们的团队来说,是时候干点实际的了,我们在这里开发软件,所以让我们来谈谈软件。我答应过要把这里最新的东西展示给大家,我决定以"在微软我们怎么开发软件:一名准项目经理的视角" 为题发表一系列博客, 期望能尽可能的迎合开发以及测试人员的口味。
虽然我们的团队很小并且项目生命周期可能也会相对较短(仅3个月),我相信经过这一系列,你将会对发布一个产品需要投入多少精力有个概念,还有为及时发布正确的产品这里的人又投入了多少激情。
好吧, 让我告诉你一些更多的细节,关于近几周让我一直忙碌的事情来开始这个系列吧


 项目生命周期  
对于每一个项目来说,都会经历一些标准的过程,每个阶段都从前一阶段收益,并且为项目增加价值。毫不奇怪 ,项目要从分析阶段开始,其基本思路是确定项目的范围,确定我们该做哪些,而哪些应该放到下一个版本。 紧接而来的就是规划阶段,项目的发展线路应该如何,(重要里程碑)将会被确定以及划分 这将基本上告诉我们现在项目进行到哪儿了,并且可以在我们和计划的进度不符合的时候,迅速地让我们作出反应。再后来,项目将进入开发等等的阶段。
从最初的两个阶段得到的最重要的两样东西就是项目规格书和项目计划。 没有确定项目范围而进行项目计划是一件很难的事情,所以对于一个项目经理来说首先并且最重要的事情就是确定项目规格书。 因此,这次我将着重介绍项目规格书,并且在以后我将逐渐介绍如何做项目计划。


 分析阶段
如果你没有多少写项目规格书的经验,你可能会惊讶与整个团队会为了完成项目规格书花费如此多的精力。 没错,整个团队-写规范是一个团队的努力,如果你认为这是一件只项目经理做的,那么请试着考虑下列各点:

  • 开发团队需要项目规格书以便于开始开发,这样他们才能知道要开发什么,他们最终的劳动成果看上去应该是个什么样子 
  • 测试团队需要项目规格书告诉他们所期望的行为是什么,以至于他们可以开始测试
  • 整个团队需要项目规格书用以界定任务,优先级,阶段划分,以及项目的发展蓝图,并且估计出该项目交付的时间
  • 各有关方面要规格书以了解哪些功能将在什么时候交付

正如你看到的,一个项目经理能是不可能独立完成项目规格书的(任何在微软工作的项目经理)。 原因有太多的人参与,你不能独自一个人完成项目规格书,然后希望其他人全都同意。所以,如何在微软写出适当的项目规格书 ? 这里是我的基本步骤。


 第1步.  做足功课
我一开始将对该领域做一些研究。 我尝试不同的东西;分析各种可能性,验证项目中的概念等等。我花了相当一段时间扮演一个潜在用户的角色,看到底我需要什么,我期待的产品是什么样的。 所以,我要说第一步是通过实验我们要了解项目看上去应该怎样,探索未知的区域。 这个阶段是纯理论的研究,此时你只需要尝试该领域的一些东西并取得一些经验。 我在做Visual Studio系统的项目中学到了很多,所有那些不同的东西你都需要做扩展,VB运行时包括什么,它是如何实现的,还有许多令人兴奋的事情。 事实证明了当我做为项目的项目经理的时候,我学到了多新东西!
 第2步.  多和别人交流
我跟很多人交谈过。 在过去几个星期里,我几乎每天都开几个小会议,为了使项目规格书更加完善,我每天要送出数十份电子邮件。 我已花了不少时间与开发人员,测试人员,项目经理以及其他这个项目相关的人。当我需要一些我自己并不在行的技术方面的支持的时候,我也会和VB组之外的很多人进行交流。 我认为这是一个整个项目最酷的部分-实际了解了不同人对于项目的感受,什么是技术上的可行性,等等
这个阶段很有趣,但并不是写文档本身让我感到高兴。 当然我乐于看到一份文档变得越来越充实,更多细节以及内容被充实进去。 更令人振奋的是,事实上在你完成项目规格书的同时,你真正明白了整个项目在做什么。 你将知道每一个功能细节,因为这是你的工作。 这是不是不可思议? 你其实有责任了解这一切,所以,当有人有问题时,你知道答案。 我不知道别人怎么想,但我确实喜欢这种感觉。 在某些点上,你对整件事是如何工作有个完整的理解,如何将不同的模块整合在一起,他们将如何互动,有什么潜在的问题,等等。我想让我开始参与软件开发的想最重要的原因,是有机会去创造,建立,创新。
 第3步.  让所有人快乐
好了,我花了一段时间写项目规格书,我终于完成了一个说明项目是做什么的文档。。 最好的部分来了-让所有的人同意我写的东西。
基本上,为了使所有人同意,你可以做两件事:

  • 要么用枪瞄准他们的头:)
  • 或和他们进行会议,这样你就能看到是什么困扰他们,并整理每一个你得到的评论,这样你就能另大家都对产品满意,并且相信项目规格书包含了可使他们开始工作所需要的所有信息。

在微软我们很人性化,所以我们偏向于第二种办法。 我们进行了一系列对项目规格书草案的校阅,这些基本上都是和同事们在项目规格书会议上面进行的。 人们给出他们的意见,对不同的问题进行讨论,并最终得到一个由多数人同意的方案来指引我们将走向何方。
让所有人幸福的最后一步就是来一次项目规格书的总校阅。 这是一次所有同事都会参加,在此你的项目规格书将得到正式批准。 大家都坐着看项目规格书,并且如果你把你的工作已经做的足够好,人在本次会议上不应该有太多的反馈,因为他们应该已经在非正式的时候提出过了。 我们的正式项目规格书校阅是周一,祝我们好运吧!
有趣的是在我来到这里之前,我以为微软(作为一个大公司) 有着大量文档和繁琐的流程,怪异模板等。这不能责怪我,其实许多即使小于微软的公司也有那些流程。 所以当我问: "那么我应该遵循什么样的模板? "得到的答案是"好了,这些就是你要去做的"时, 我是真的感到很吃惊。当然,基本准则还是有的,但你不一定非要遵照他们不可。 虽然他们是真的很有用,我从它们学到了很多新东西…但事实证明,每一步都严格遵守规范并不比只遵守其中正确的部分好多少。 不错,得到正确的东西,是不是太容易了--对于每一个特性的开发你必须付出很多精力以确保安全性,本地化,性能等,因为人们对这些东西是非常谨慎的。 但只要你有那些,并且大家都同意这里有那些就足够了,你就可以用你喜欢的布局或风格。 我不知道别人怎样,但我真的很喜欢这种自由!
 第4步.  不断完善项目规格书
经过对项目规格书的审查,项目规格书将被打印并且文档将被标记为只读的,从此它永远不能改变… …这你只是期望罢了!  :) 规格书是一个动态的文件,并随着时间的推移,要不断改变和更新,项目经理的工作就是让其不断完善。 项目经理有责任了解每一个变化及其影响,让项目组做出正确的决定,这个是否应该改变
哇,这次已经说了很多,有那么多东西写… … 所以我想我为大家开了个头,如果大家有什么问题,不要犹豫! 下周见!


 

posted on 2007-10-12 13:33:00 by VBCTI  评论(11) 阅读(6326)

VB中的Closure:第1部分

[原文作者]Jared Parsons

[原文链接]Closures in VB: Part 1

我叫Jared Parsons,是微软VB编译和调试组的软件开发人员。我为VB9.0开发的一个新特性叫做“lexical closure支持。这个特性是对VB语言的一个重要扩充,因此我将通过几篇博客来解释这一特性是如何影响你的代码。

LexicalClosures(通常被简称为Closures)是Visual Basic 9.0几个新特性的内在支持,是Lambda和查询表达式的内在实质。我将针对VB9.0的Closures工作机制,局限性以及使用时易犯的错误将做系列性介绍。

首先,我们从一个基本的结论:什么是closure开始说起吧。在Wikipedia的定义是“...用于关联方法与环境的语法概念...”,但我更喜欢这样来描述它:一个Closure是一个特性,该特性允许用户从多个函数访问某一个环境(局部变量,参数和方法)。举例说明可能更易说明清楚:)

    Class C1
        Sub Test()
            Dim x = 5
            Dim f = Function(ByVal y As Integer) x + y
            Dim result = f(42)
        End Sub 
    End Class

在这段代码中,包含了一个Lambda表达式。它有一个方法参数并将这个参数与“Test”的局部变量做累加。在VB中,Lambda表达式被作为函数来实现的(C#也一样)。所以,现在我们有2个方法,它们是“Test”“f”方法。“f“方法将访问“Test”方法的局部变量xclosure将在这里发挥其功效了。Closures负责使变量x在同一个进程中对于2个方法都可用(这被称为提升变量)。

要做到这点,编译器将执行4个步骤的操作:

1.        创建一个类,用于包含x以便在2个方法之间共享这个变量。这里,这个类称为Closure

2.        编译器将在这个Closure类中为Lambda表达式创建一个新方法。这里,这个方法名叫f

3.        编译器将在Test方法内部创建一个该Closure类的实例

4.        在“Test“方法中,将所有对于x的访问都重新改写为访问Closure类的成员变量x.

    Class Closure
        Public x As Integer 
        Function f(ByVal y As Integer) As Integer
            Return x + y
        End Function
    End Class 
    Class C1
        Sub Test()
            Dim c As New Closure()
            c.x = 5
            Dim f As Func(Of Integer, Integer) = AddressOf c.f
            Dim result = f(42)
        End Sub 
    End Class

现在,两个方法都可以共享变量x了。同时,用户也无需了解任何有关编译器实际产生的代码。你可以从这个简单例子中了解到closure和所有其它VB9.0新特性(类型推断,Lambda表达式)是如何为你节省代码量的了。

注意:这个例子只是模拟了当使用Closure时编译器所产生的代码,但是实际生成的代码却看起来不是这么“优雅”,反而还用了许多难以理解的名字,如“$Lambda_1”等。

下次,我将深入地阐述一些closure更多的使用(多变量,方法访问,术语等)。

Jared Parsons

posted on 2007-10-11 14:18:00 by VBCTI  评论(2) 阅读(4275)

Powered by: Joycode.MVC引擎 0.5.2.0