Passo a passo: Criando um provedor IQueryable LINQ
Este tópico avançada fornece instruções passo a passo para criar um provedor personalizado de LINQ . Quando você terminar, você será capaz de usar o provedor que você cria para escrever consultas de LINQ contra o serviço Web TerraServer- EUA.
O serviço da Web TerraServer- EUA fornece uma interface a um banco de dados de imagens aéreas dos Estados Unidos.Também expõe um método que retorna informações sobre locais em Estados Unidos, dada a parte ou todo o nome local.Esse método, que é chamado GetPlaceList, é o método que seu provedor de LINQ chamará. O provedor Windows Communication Foundation (WCF) usará para se comunicar com o serviço Web.Para obter mais informações sobre o serviço Web TerraServer- EUA, consulte Visão geral dos serviços Web TerraServer- EUA.
Esse provedor está um provedor relativamente simples de IQueryable .Espera que informação específica em consultas que tratam e tem um sistema de tipo fechado, expõe um único tipo para representar os dados do resultado.Este provedor examina somente um tipo de expressão na árvore de expressão que representa a consulta, que da chamada de método é interna a chamada ao Where.Extrai os dados que ele precisa para ver o serviço Web de esta expressão.Então chama o serviço Web e inserir os dados retornados na árvore de expressão no lugar da fonte de dados inicial de IQueryable .O restante de execução da consulta é tratado pelas implementações de Enumerable dos operadores de consulta padrão.
Os exemplos de código em este tópico são fornecidos em C# e em Visual Basic.
Essa explicação passo a passo mostra as seguintes tarefas:
criando o projeto em Visual Studio.
Implementando as interfaces que são necessárias para um provedor de IQueryableLINQ : IQueryable<T>, IOrderedQueryable<T>, e IQueryProvider.
Adicionando um tipo personalizado .NET para representar os dados do serviço Web.
Criando uma classe de contexto de consulta e uma classe que obtém dados de serviço Web.
Criando uma subclasse do visitante da árvore de expressão encontrar a expressão que representa a chamada interno para o método de Queryable.Where .
Criando uma subclasse do visitante da árvore de expressão que extrai informações de consulta de LINQ para usar na solicitação de serviço Web.
Criando uma subclasse do visitante da árvore de expressão que altera a árvore de expressão que representa a consulta completo de LINQ .
Usando uma classe de avaliador para avaliar parcialmente uma árvore de expressão.Essa etapa é necessária porque converte todas as referências de variável local na consulta de LINQ em valores.
Criando uma classe auxiliar de árvore de expressão e uma nova classe de exceção.
Testando o provedor de LINQ de um aplicativo cliente que contém uma consulta de LINQ .
Adicionando recursos mais complexos de consulta para o provedor de LINQ .
Observação O provedor LINQ que essa explicação passo a passo cria está disponível como um exemplo.Para obter mais informações, Amostras do LINQ.
Pré-requisitos
Essa explicação passo a passo requer os recursos que são apresentados em Visual Studio 2008.
Observação |
---|
Seu computador pode mostrar nomes ou locais diferentes para alguns dos elementos da interface do usuário do Visual Studio nas instruções a seguir. A edição do Visual Studio que você possui e as configurações que você usa determinam esses elementos. Para obter mais informações, consulte Configurações de Visual Studio. |
Criando o projeto
Para criar o projeto no Visual Studio
em Visual Studio, crie um novo aplicativo de Biblioteca de Classes . nomeie o projeto LinqToTerraServerProvider.
Em Gerenciador de Soluções, selecione o arquivo de Class1.cs (ou Class1.vb) e renomeá-lo a QueryableTerraServerData.cs (ou a QueryableTerraServerData.vb).Em a caixa de diálogo que aparece acima, clique em Sim para renomear todas as referências para o elemento de código.
Você cria o provedor como um projeto de Biblioteca de Classes em Visual Studio como aplicativos cliente executáveis irão aumentar o assembly do provedor como uma referência ao seu projeto.
Para adicionar uma referência de serviço para o serviço da Web
Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto de LinqToTerraServerProvider e clique em Adicionar referência de serviço.
a caixa de diálogo de Adicionar referência de serviço abre.
Em a caixa de Endereço , digite http://terraserver.microsoft.com/TerraService2.asmx.
Em a caixa de Namespace , o tipo TerraServerReference e clique em OK.
O serviço da Web TerraServer- EUA é adicionado como uma referência de serviço para que o aplicativo se comunicar com o serviço Web por Windows Communication Foundation (WCF).Adicionando uma referência de serviço para o projeto, Visual Studio gera um arquivo de app.config que contém um proxy e um ponto de extremidade para o serviço Web.Para obter mais informações, consulte Os serviços do Windows Communication Foundation e serviços de dados do WCF em Visual Studio.
Agora você tem um projeto que tem um arquivo chamado app.config, um arquivo denominado QueryableTerraServerData.cs (ou QueryableTerraServerData.vb), e uma referência de serviço TerraServerReferencechamada.
implementando as interfaces necessárias
Para criar um provedor de LINQ , no mínimo você deve implementar IQueryable<T> e IQueryProvider interfaces. IQueryable<T> e IQueryProvider são derivados das interfaces necessários; portanto, ao implementar essas duas interfaces, você também estiver implementando as interfaces que são necessárias para um provedor de LINQ .
Se você deseja oferecer suporte operadores de consulta de classificação como o OrderBy e o ThenBy, você também deve implementar a interface de IOrderedQueryable<T> .Porque IOrderedQueryable<T> deriva de IQueryable<T>, você pode implementar ambas essas interfaces em um tipo, que é o que torna esse provedor.
Para implementar o 'System.Linq.IQueryable e 1 de 1 System.Linq.IOrderedQueryable do '
Em o arquivo QueryableTerraServerData.cs (ou) QueryableTerraServerData.vb, adicione o seguinte código.
Imports System.Linq.Expressions Public Class QueryableTerraServerData(Of TData) Implements IOrderedQueryable(Of TData) #Region "Private members" Private _provider As TerraServerQueryProvider Private _expression As Expression #End Region #Region "Constructors" ''' <summary> ''' This constructor is called by the client to create the data source. ''' </summary> Public Sub New() Me._provider = New TerraServerQueryProvider() Me._expression = Expression.Constant(Me) End Sub ''' <summary> ''' This constructor is called by Provider.CreateQuery(). ''' </summary> ''' <param name="_expression"></param> Public Sub New(ByVal _provider As TerraServerQueryProvider, ByVal _expression As Expression) If _provider Is Nothing Then Throw New ArgumentNullException("provider") End If If _expression Is Nothing Then Throw New ArgumentNullException("expression") End If If Not GetType(IQueryable(Of TData)).IsAssignableFrom(_expression.Type) Then Throw New ArgumentOutOfRangeException("expression") End If Me._provider = _provider Me._expression = _expression End Sub #End Region #Region "Properties" Public ReadOnly Property ElementType( ) As Type Implements IQueryable(Of TData).ElementType Get Return GetType(TData) End Get End Property Public ReadOnly Property Expression( ) As Expression Implements IQueryable(Of TData).Expression Get Return _expression End Get End Property Public ReadOnly Property Provider( ) As IQueryProvider Implements IQueryable(Of TData).Provider Get Return _provider End Get End Property #End Region #Region "Enumerators" Public Function GetGenericEnumerator( ) As IEnumerator(Of TData) Implements IEnumerable(Of TData).GetEnumerator Return (Me.Provider. Execute(Of IEnumerable(Of TData))(Me._expression)).GetEnumerator() End Function Public Function GetEnumerator( ) As IEnumerator Implements IEnumerable.GetEnumerator Return (Me.Provider. Execute(Of IEnumerable)(Me._expression)).GetEnumerator() End Function #End Region End Class
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace LinqToTerraServerProvider { public class QueryableTerraServerData<TData> : IOrderedQueryable<TData> { #region Constructors /// <summary> /// This constructor is called by the client to create the data source. /// </summary> public QueryableTerraServerData() { Provider = new TerraServerQueryProvider(); Expression = Expression.Constant(this); } /// <summary> /// This constructor is called by Provider.CreateQuery(). /// </summary> /// <param name="expression"></param> public QueryableTerraServerData(TerraServerQueryProvider provider, Expression expression) { if (provider == null) { throw new ArgumentNullException("provider"); } if (expression == null) { throw new ArgumentNullException("expression"); } if (!typeof(IQueryable<TData>).IsAssignableFrom(expression.Type)) { throw new ArgumentOutOfRangeException("expression"); } Provider = provider; Expression = expression; } #endregion #region Properties public IQueryProvider Provider { get; private set; } public Expression Expression { get; private set; } public Type ElementType { get { return typeof(TData); } } #endregion #region Enumerators public IEnumerator<TData> GetEnumerator() { return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return (Provider.Execute<System.Collections.IEnumerable>(Expression)).GetEnumerator(); } #endregion } }
A implementação de IOrderedQueryable<T> pela classe implementa a QueryableTerraServerData de três propriedades declaradas em IQueryable e em dois métodos de enumeração declarados em IEnumerable e em IEnumerable<T>.
Essa classe possui dois construtores.O primeiro construtor é chamado de aplicativo cliente criar o objeto para gravar a consulta de LINQ contra.O segundo construtor é chamado interno para a biblioteca de provedor pelo código na implementação de IQueryProvider .
Quando o método de GetEnumerator é chamado um objeto do tipo QueryableTerraServerData, que representa a consulta é executada e os resultados da consulta são enumerados.
Esse código, exceto para o nome da classe, não é específico para este provedor de serviço Web TerraServer- EUA.Portanto, pode ser reutilizado para qualquer provedor de LINQ .
para implementar System.Linq.IQueryProvider
Adicione a classe de TerraServerQueryProvider ao seu projeto.
Imports System.Linq.Expressions Imports System.Reflection Public Class TerraServerQueryProvider Implements IQueryProvider Public Function CreateQuery( ByVal expression As Expression ) As IQueryable Implements IQueryProvider.CreateQuery Dim elementType As Type = TypeSystem.GetElementType(expression.Type) Try Dim qType = GetType(QueryableTerraServerData(Of )).MakeGenericType(elementType) Dim args = New Object() {Me, expression} Dim instance = Activator.CreateInstance(qType, args) Return CType(instance, IQueryable) Catch tie As TargetInvocationException Throw tie.InnerException End Try End Function ' Queryable's collection-returning standard query operators call this method. Public Function CreateQuery(Of TResult)( ByVal expression As Expression ) As IQueryable(Of TResult) Implements IQueryProvider.CreateQuery Return New QueryableTerraServerData(Of TResult)(Me, expression) End Function Public Function Execute( ByVal expression As Expression ) As Object Implements IQueryProvider.Execute Return TerraServerQueryContext.Execute(expression, False) End Function ' Queryable's "single value" standard query operators call this method. ' It is also called from QueryableTerraServerData.GetEnumerator(). Public Function Execute(Of TResult)( ByVal expression As Expression ) As TResult Implements IQueryProvider.Execute Dim IsEnumerable As Boolean = (GetType(TResult).Name = "IEnumerable`1") Dim result = TerraServerQueryContext.Execute(expression, IsEnumerable) Return CType(result, TResult) End Function End Class
using System; using System.Linq; using System.Linq.Expressions; namespace LinqToTerraServerProvider { public class TerraServerQueryProvider : IQueryProvider { public IQueryable CreateQuery(Expression expression) { Type elementType = TypeSystem.GetElementType(expression.Type); try { return (IQueryable)Activator.CreateInstance(typeof(QueryableTerraServerData<>).MakeGenericType(elementType), new object[] { this, expression }); } catch (System.Reflection.TargetInvocationException tie) { throw tie.InnerException; } } // Queryable's collection-returning standard query operators call this method. public IQueryable<TResult> CreateQuery<TResult>(Expression expression) { return new QueryableTerraServerData<TResult>(this, expression); } public object Execute(Expression expression) { return TerraServerQueryContext.Execute(expression, false); } // Queryable's "single value" standard query operators call this method. // It is also called from QueryableTerraServerData.GetEnumerator(). public TResult Execute<TResult>(Expression expression) { bool IsEnumerable = (typeof(TResult).Name == "IEnumerable`1"); return (TResult)TerraServerQueryContext.Execute(expression, IsEnumerable); } } }
O código do provedor de consulta em esta classe implementa os quatro métodos que são necessários para implementar a interface de IQueryProvider .Os dois métodos de CreateQuery criar consultas que estão associadas com a fonte de dados.Os dois métodos de Execute eles enviarem essas consultas da ser executadas.
O método não genéricos de CreateQuery usa reflexão para obter o tipo da seqüência que a consulta que cria retornaria se foi executada.Ele usa a classe de Activator para criar uma nova instância de QueryableTerraServerData que é construída com o tipo de elemento como seu argumento de tipo genérico.O resultado de chamar o método não genéricos de CreateQuery é o mesmo como se o método genérico de CreateQuery tivesse sido chamado com o argumento do tipo correto.
A maior parte da lógica de execução da consulta é tratada em uma classe diferente que você irá adicionar posteriormente.Essa funcionalidade é tratada em outro lugar porque é específica para a fonte de dados que está sendo consultada, enquanto o código em essa classe é genérico para o provedor de LINQ .Para usar este código para um provedor diferente, você pode ter que alterar o nome da classe e o nome do tipo do contexto de consulta que é referenciado em dois métodos.
Adicionando um tipo personalizado para representar os dados do resultado
Você precisará de um tipo .NET de representar os dados que são obtidos de serviço Web.Este tipo de consulta será usado em LINQ de cliente para definir os resultados que deseja.O procedimento a seguir cria tal tipo. Este tipo, Placechamado, contém informações sobre um único local geográfico como uma cidade, um parque, ou um lago.
Esse código também contém um tipo de enumeração, PlaceTypechamado, que define vários tipos de geográfico local e é usado na classe de Place .
Para criar um tipo personalizado de resultado
Adicione a classe de Place e a enumeração de PlaceType ao seu projeto.
Public Class Place ' Properties. Public Property Name As String Public Property State As String Public Property PlaceType As PlaceType ' Constructor. Friend Sub New(ByVal name As String, ByVal state As String, ByVal placeType As TerraServerReference.PlaceType) Me.Name = name Me.State = state Me.PlaceType = CType(placeType, PlaceType) End Sub End Class Public Enum PlaceType Unknown AirRailStation BayGulf CapePeninsula CityTown HillMountain Island Lake OtherLandFeature OtherWaterFeature ParkBeach PointOfInterest River End Enum
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace LinqToTerraServerProvider { public class Place { // Properties. public string Name { get; private set; } public string State { get; private set; } public PlaceType PlaceType { get; private set; } // Constructor. internal Place(string name, string state, LinqToTerraServerProvider.TerraServerReference.PlaceType placeType) { Name = name; State = state; PlaceType = (PlaceType)placeType; } } public enum PlaceType { Unknown, AirRailStation, BayGulf, CapePeninsula, CityTown, HillMountain, Island, Lake, OtherLandFeature, OtherWaterFeature, ParkBeach, PointOfInterest, River } }
O construtor para o tipo de Place simplifica a criação de um objeto do resultado do tipo que é retornado pelo serviço da Web.Quando o provedor pode retornar o resultado do tipo definido pelo serviço Web API diretamente, aplicativos cliente pode adicionar uma referência para o serviço Web.Criando um novo tipo como parte da biblioteca do provedor, o cliente não precisa saber sobre os tipos e métodos que expõe de serviço Web.
Adicionando funcionalidade para obter dados da fonte de dados
Essa implementação do provedor pressupõe que a chamada a Queryable.Where interno contém informações sobre o local para usar para ver o serviço Web.A chamada interno de Queryable.Where é a cláusula de where (cláusula deWhere em Visual Basic) ou a chamada de método Queryable.Where que ocorrem primeiro em uma consulta de LINQ , ou que o mais próximo à parte inferior “” da árvore de expressão que representa a consulta.
Para criar uma classe de contexto de consulta
Adicione a classe de TerraServerQueryContext ao seu projeto.
Imports System.Linq.Expressions Public Class TerraServerQueryContext ' Executes the expression tree that is passed to it. Friend Shared Function Execute(ByVal expr As Expression, ByVal IsEnumerable As Boolean) As Object ' The expression must represent a query over the data source. If Not IsQueryOverDataSource(expr) Then Throw New InvalidProgramException("No query over the data source was specified.") End If ' Find the call to Where() and get the lambda expression predicate. Dim whereFinder As New InnermostWhereFinder() Dim whereExpression As MethodCallExpression = whereFinder.GetInnermostWhere(expr) Dim lambdaExpr As LambdaExpression lambdaExpr = CType(CType(whereExpression.Arguments(1), UnaryExpression).Operand, LambdaExpression) ' Send the lambda expression through the partial evaluator. lambdaExpr = CType(Evaluator.PartialEval(lambdaExpr), LambdaExpression) ' Get the place name(s) to query the Web service with. Dim lf As New LocationFinder(lambdaExpr.Body) Dim locations As List(Of String) = lf.Locations If locations.Count = 0 Then Dim s = "You must specify at least one place name in your query." Throw New InvalidQueryException(s) End If ' Call the Web service and get the results. Dim places() = WebServiceHelper.GetPlacesFromTerraServer(locations) ' Copy the IEnumerable places to an IQueryable. Dim queryablePlaces = places.AsQueryable() ' Copy the expression tree that was passed in, changing only the first ' argument of the innermost MethodCallExpression. Dim treeCopier As New ExpressionTreeModifier(queryablePlaces) Dim newExpressionTree = treeCopier.Visit(expr) ' This step creates an IQueryable that executes by replacing ' Queryable methods with Enumerable methods. If (IsEnumerable) Then Return queryablePlaces.Provider.CreateQuery(newExpressionTree) Else Return queryablePlaces.Provider.Execute(newExpressionTree) End If End Function Private Shared Function IsQueryOverDataSource(ByVal expression As Expression) As Boolean ' If expression represents an unqueried IQueryable data source instance, ' expression is of type ConstantExpression, not MethodCallExpression. Return (TypeOf expression Is MethodCallExpression) End Function End Class
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace LinqToTerraServerProvider { class TerraServerQueryContext { // Executes the expression tree that is passed to it. internal static object Execute(Expression expression, bool IsEnumerable) { // The expression must represent a query over the data source. if (!IsQueryOverDataSource(expression)) throw new InvalidProgramException("No query over the data source was specified."); // Find the call to Where() and get the lambda expression predicate. InnermostWhereFinder whereFinder = new InnermostWhereFinder(); MethodCallExpression whereExpression = whereFinder.GetInnermostWhere(expression); LambdaExpression lambdaExpression = (LambdaExpression)((UnaryExpression)(whereExpression.Arguments[1])).Operand; // Send the lambda expression through the partial evaluator. lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression); // Get the place name(s) to query the Web service with. LocationFinder lf = new LocationFinder(lambdaExpression.Body); List<string> locations = lf.Locations; if (locations.Count == 0) throw new InvalidQueryException("You must specify at least one place name in your query."); // Call the Web service and get the results. Place[] places = WebServiceHelper.GetPlacesFromTerraServer(locations); // Copy the IEnumerable places to an IQueryable. IQueryable<Place> queryablePlaces = places.AsQueryable<Place>(); // Copy the expression tree that was passed in, changing only the first // argument of the innermost MethodCallExpression. ExpressionTreeModifier treeCopier = new ExpressionTreeModifier(queryablePlaces); Expression newExpressionTree = treeCopier.Visit(expression); // This step creates an IQueryable that executes by replacing Queryable methods with Enumerable methods. if (IsEnumerable) return queryablePlaces.Provider.CreateQuery(newExpressionTree); else return queryablePlaces.Provider.Execute(newExpressionTree); } private static bool IsQueryOverDataSource(Expression expression) { // If expression represents an unqueried IQueryable data source instance, // expression is of type ConstantExpression, not MethodCallExpression. return (expression is MethodCallExpression); } } }
Essa classe organizar o trabalho de executar uma consulta.Após ter localizado a expressão que representa a chamada interno de Queryable.Where , esse código recupera a expressão lambda que representa o predicado que foi passado para Queryable.Where.Então passa a expressão de predicado a um método a ser avaliado parcialmente, de modo que todas as referências às variáveis locais são traduzidas em valores.Então chama um método para extrair os locais de aplicativos predicado, e chama outro método para obter os dados do resultado de serviço Web.
Em a próxima etapa, esse código copia a árvore de expressão que representa a consulta de LINQ e fizer uma alteração na árvore de expressão.O código usa uma subclasse do visitante da árvore de expressão para substituir a fonte de dados que a chamada interno do operador de consulta é aplicado com concreta à lista de objetos de Place que tiverem sido obtidos de serviço Web.
Antes da lista de objetos de Place é inserido na árvore de expressão, seu tipo é modificado de IEnumerable a IQueryable chamando AsQueryable.Essa alteração de tipo é necessária porque quando a árvore de expressão é reescrita, o nó que representa a chamada de método ao método interno do operador de consulta é reconstruído.O nó é reconstruído como um dos argumentos foi alterado (ou seja, a fonte de dados que é aplicada.)O método de Call(Expression, MethodInfo, IEnumerable<Expression>) , que é usado para recriar o nó, irá lançar uma exceção se nenhum argumento não é assinalável para o parâmetro correspondente do método que será transmitida a.Em esse caso, a lista de IEnumerable de objetos de Place não seria assinalável para o parâmetro de IQueryable de Queryable.Where.Portanto, o tipo é alterado para IQueryable.
Alterando seu tipo a IQueryable, a coleção também obtém um membro de IQueryProvider , acessado pela propriedade de Provider , que pode criar ou executar consultas.O tipo dinâmica da coleção dePlace de ° de IQueryableé EnumerableQuery, que é um tipo que é interna a System.Linq API.O provedor de consulta que é associado com esse tipo executa consultas substituindo chamadas padrão de operador de consulta de Queryable com os operadores equivalentes de Enumerable , de modo que efetivamente a consulta se torna LINQ a consulta de objetos.
O código final na classe de TerraServerQueryContext chama um dos métodos na lista de IQueryable de objetos de Place .Chama CreateQuery se a consulta retorna resultados de cliente enumeráveis, ou Execute se a consulta retorna um resultado de cliente são enumeráveis.
O código em essa classe é muito específico para este provedor TerraServer- EUA.Portanto, é encapsulado na classe de TerraServerQueryContext em vez de serem inseridos diretamente na implementação de IQueryProvider mais genérica.
O provedor que você está criando requer somente as informações no predicado de Queryable.Where para ver o serviço Web. portanto, ele usa LINQ a objetos para fazer o trabalho de executar a consulta de LINQ usando o tipo interno de EnumerableQuery .Uma maneira alternativa para usar LINQ a objetos para executar a consulta é que o cliente envolver a parte da consulta seja executada por LINQ para objetos em LINQ a consulta de objetos.Isso é conseguido chamando AsEnumerable<TSource> no restante de consulta, que é a parte de consulta que o provedor requer para as finalidades específicas.A vantagem de esse tipo de implementação que é a divisão de trabalho entre o provedor personalizado e LINQ a objetos é mais transparente.
Observação |
---|
O provedor apresentado em este tópico é um provedor simples que tenha suporte mínimo de consulta de sua preferência.Portanto, se baseia intensamente em LINQ a objetos para executar consultas.Um provedor complexo de LINQ como LINQ to SQL pode oferecer suporte a consulta inteira sem fornecer nenhum trabalho fora a LINQ a objetos. |
Para criar uma classe para obter dados do serviço Web
Adicione a classe de WebServiceHelper (ou o módulo em Visual Basic) ao seu projeto.
Imports System.Collections.Generic Imports LinqToTerraServerProvider.TerraServerReference Friend Module WebServiceHelper Private numResults As Integer = 200 Private mustHaveImage As Boolean = False Friend Function GetPlacesFromTerraServer(ByVal locations As List(Of String)) As Place() ' Limit the total number of Web service calls. If locations.Count > 5 Then Dim s = "This query requires more than five separate calls to the Web service. Please decrease the number of places." Throw New InvalidQueryException(s) End If Dim allPlaces As New List(Of Place) ' For each location, call the Web service method to get data. For Each location In locations Dim places = CallGetPlaceListMethod(location) allPlaces.AddRange(places) Next Return allPlaces.ToArray() End Function Private Function CallGetPlaceListMethod(ByVal location As String) As Place() Dim client As New TerraServiceSoapClient() Dim placeFacts() As PlaceFacts Try ' Call the Web service method "GetPlaceList". placeFacts = client.GetPlaceList(location, numResults, mustHaveImage) ' If we get exactly 'numResults' results, they are probably truncated. If (placeFacts.Length = numResults) Then Dim s = "The results have been truncated by the Web service and would not be complete. Please try a different query." Throw New Exception(s) End If ' Create Place objects from the PlaceFacts objects returned by the Web service. Dim places(placeFacts.Length - 1) As Place For i = 0 To placeFacts.Length - 1 places(i) = New Place(placeFacts(i).Place.City, placeFacts(i).Place.State, placeFacts(i).PlaceTypeId) Next ' Close the WCF client. client.Close() Return places Catch timeoutException As TimeoutException client.Abort() Throw Catch communicationException As System.ServiceModel.CommunicationException client.Abort() Throw End Try End Function End Module
using System; using System.Collections.Generic; using LinqToTerraServerProvider.TerraServerReference; namespace LinqToTerraServerProvider { internal static class WebServiceHelper { private static int numResults = 200; private static bool mustHaveImage = false; internal static Place[] GetPlacesFromTerraServer(List<string> locations) { // Limit the total number of Web service calls. if (locations.Count > 5) throw new InvalidQueryException("This query requires more than five separate calls to the Web service. Please decrease the number of locations in your query."); List<Place> allPlaces = new List<Place>(); // For each location, call the Web service method to get data. foreach (string location in locations) { Place[] places = CallGetPlaceListMethod(location); allPlaces.AddRange(places); } return allPlaces.ToArray(); } private static Place[] CallGetPlaceListMethod(string location) { TerraServiceSoapClient client = new TerraServiceSoapClient(); PlaceFacts[] placeFacts = null; try { // Call the Web service method "GetPlaceList". placeFacts = client.GetPlaceList(location, numResults, mustHaveImage); // If there are exactly 'numResults' results, they are probably truncated. if (placeFacts.Length == numResults) throw new Exception("The results have been truncated by the Web service and would not be complete. Please try a different query."); // Create Place objects from the PlaceFacts objects returned by the Web service. Place[] places = new Place[placeFacts.Length]; for (int i = 0; i < placeFacts.Length; i++) { places[i] = new Place( placeFacts[i].Place.City, placeFacts[i].Place.State, placeFacts[i].PlaceTypeId); } // Close the WCF client. client.Close(); return places; } catch (TimeoutException timeoutException) { client.Abort(); throw; } catch (System.ServiceModel.CommunicationException communicationException) { client.Abort(); throw; } } } }
Essa classe contém funcionalidade que obtém dados de serviço Web.Esse código usa um tipo nomeado TerraServiceSoapClient, que é gerado automaticamente para o projeto por Windows Communication Foundation (WCF), para chamar o método GetPlaceListde serviço Web.Em seguida, cada resultado é convertido do tipo de retorno do método de serviço Web para o tipo .NET que o provedor define para os dados.
Esse código verifica que contém duas aprimoram a usabilidade da biblioteca do provedor.Os primeiros limites de verificação o tempo máximas que um aplicativo cliente esperará uma resposta limitando o número total de chamadas que são feitas para o serviço da Web, pela consulta, como cinco.Para cada local especificado na consulta de cliente, uma solicitação de serviço Web é gerada.Portanto, o provedor gera uma exceção se a consulta contém mais de cinco locais.
A segunda verificação determina se o número de resultados retornados pelo serviço da Web é igual ao número máximo de resultados que pode retornar.Se o número de resultados é o número máximo, é provável que os resultados do serviço Web são truncados.A o invés de retornar uma lista incompleta para o cliente, o provedor gera uma exceção.
Adicionando as classes do visitante da árvore de expressão
Para criar o visitante que localiza o interno onde expressão de chamada de método
Adicione a classe de InnermostWhereFinder , que herda da classe de ExpressionVisitor , ao seu projeto.
Imports System.Linq.Expressions Class InnermostWhereFinder Inherits ExpressionVisitor Private innermostWhereExpression As MethodCallExpression Public Function GetInnermostWhere(ByVal expr As Expression) As MethodCallExpression Me.Visit(expr) Return innermostWhereExpression End Function Protected Overrides Function VisitMethodCall(ByVal expr As MethodCallExpression) As Expression If expr.Method.Name = "Where" Then innermostWhereExpression = expr End If Me.Visit(expr.Arguments(0)) Return expr End Function End Class
using System; using System.Linq.Expressions; namespace LinqToTerraServerProvider { internal class InnermostWhereFinder : ExpressionVisitor { private MethodCallExpression innermostWhereExpression; public MethodCallExpression GetInnermostWhere(Expression expression) { Visit(expression); return innermostWhereExpression; } protected override Expression VisitMethodCall(MethodCallExpression expression) { if (expression.Method.Name == "Where") innermostWhereExpression = expression; Visit(expression.Arguments[0]); return expression; } } }
Essa classe herda da classe base do visitante da árvore de expressão para executar a funcionalidade de localizar uma expressão específica.A classe base do visitante da árvore de expressão é projetado para ser herdada e especializada para uma tarefa específica que envolve atravessar uma árvore de expressão.A classe derivada substitui o método de VisitMethodCall para procurar a expressão que representa a chamada interno a Where na árvore de expressão que representa a consulta de cliente.Esta expressão é interna a expressão que o provedor extrai os locais de pesquisa de.
Adicione as políticas de using (instruções deImports no Visual Basic) para o arquivo para os seguintes namespaces: System.Collections.Generic, System.Collections.ObjectModel e System.Linq.Expressions.
Para criar o visitante que extrai dados para ver o serviço da Web
Adicione a classe de LocationFinder ao seu projeto.
Imports System.Linq.Expressions Imports ETH = LinqToTerraServerProvider.ExpressionTreeHelpers Friend Class LocationFinder Inherits ExpressionVisitor Private _expression As Expression Private _locations As List(Of String) Public Sub New(ByVal exp As Expression) Me._expression = exp End Sub Public ReadOnly Property Locations() As List(Of String) Get If _locations Is Nothing Then _locations = New List(Of String)() Me.Visit(Me._expression) End If Return Me._locations End Get End Property Protected Overrides Function VisitBinary(ByVal be As BinaryExpression) As Expression ' Handles Visual Basic String semantics. be = ETH.ConvertVBStringCompare(be) If be.NodeType = ExpressionType.Equal Then If (ETH.IsMemberEqualsValueExpression(be, GetType(Place), "Name")) Then _locations.Add(ETH.GetValueFromEqualsExpression(be, GetType(Place), "Name")) Return be ElseIf (ETH.IsMemberEqualsValueExpression(be, GetType(Place), "State")) Then _locations.Add(ETH.GetValueFromEqualsExpression(be, GetType(Place), "State")) Return be Else Return MyBase.VisitBinary(be) End If Else Return MyBase.VisitBinary(be) End If End Function End Class
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace LinqToTerraServerProvider { internal class LocationFinder : ExpressionVisitor { private Expression expression; private List<string> locations; public LocationFinder(Expression exp) { this.expression = exp; } public List<string> Locations { get { if (locations == null) { locations = new List<string>(); this.Visit(this.expression); } return this.locations; } } protected override Expression VisitBinary(BinaryExpression be) { if (be.NodeType == ExpressionType.Equal) { if (ExpressionTreeHelpers.IsMemberEqualsValueExpression(be, typeof(Place), "Name")) { locations.Add(ExpressionTreeHelpers.GetValueFromEqualsExpression(be, typeof(Place), "Name")); return be; } else if (ExpressionTreeHelpers.IsMemberEqualsValueExpression(be, typeof(Place), "State")) { locations.Add(ExpressionTreeHelpers.GetValueFromEqualsExpression(be, typeof(Place), "State")); return be; } else return base.VisitBinary(be); } else return base.VisitBinary(be); } } }
Essa classe é usada para extrair informações sobre o local de predicado que o cliente passa a Queryable.Where.Deriva da classe de ExpressionVisitor e substitui apenas o método de VisitBinary .
A classe de ExpressionVisitor envia expressões binários, como expressões de igualdade como place.Name == "Seattle"(place.Name = "Seattle" em Visual Basic), o método de VisitBinary .Em esse método, substituindo de VisitBinary se a expressão corresponda ao padrão de expressão de igualdade que pode fornecer informações sobre localização, que as informações são extraída e armazenadas em uma lista de locais.
Essa classe usa um visitante da árvore de expressão para localizar informações sobre localização na árvore de expressão como um visitante é criado para árvores de expressão de atravessamento e de auditoria.O código resultante é mais puro e menos a probabilidade de erros do que se tivesse sido implementado sem usar o visitante.
Em esse estágio da explicação passo a passo, o provedor oferece suporte apenas formas limitadas de fornecer informações sobre localização na consulta.Mais tarde em esse tópico, você irá adicionar funcionalidade para ativar mais meios para fornecer informações sobre a localização.
para criar o visitante que altera a árvore de expressão
Adicione a classe de ExpressionTreeModifier ao seu projeto.
Imports System.Linq.Expressions Friend Class ExpressionTreeModifier Inherits ExpressionVisitor Private queryablePlaces As IQueryable(Of Place) Friend Sub New(ByVal places As IQueryable(Of Place)) Me.queryablePlaces = places End Sub Protected Overrides Function VisitConstant(ByVal c As ConstantExpression) As Expression ' Replace the constant QueryableTerraServerData arg with the queryable Place collection. If c.Type Is GetType(QueryableTerraServerData(Of Place)) Then Return Expression.Constant(Me.queryablePlaces) Else Return c End If End Function End Class
using System; using System.Linq; using System.Linq.Expressions; namespace LinqToTerraServerProvider { internal class ExpressionTreeModifier : ExpressionVisitor { private IQueryable<Place> queryablePlaces; internal ExpressionTreeModifier(IQueryable<Place> places) { this.queryablePlaces = places; } protected override Expression VisitConstant(ConstantExpression c) { // Replace the constant QueryableTerraServerData arg with the queryable Place collection. if (c.Type == typeof(QueryableTerraServerData<Place>)) return Expression.Constant(this.queryablePlaces); else return c; } } }
Esta classe deriva da classe de ExpressionVisitor e substitui o método de VisitConstant .Em esse método, substitui o objeto que a chamada padrão interno do operador de consulta é aplicado com uma lista de objetos concreta de Place .
Essa classe modificadoras de árvore de expressão usa o visitante da árvore de expressão porque o visitante é criado para atravessar, revise, e copiar árvores de expressão.Derivando da classe base do visitante da árvore de expressão, essa classe requer o mínimo de código para executar a função.
adicionando o avaliador de expressão
O predicado que é passado para o método de Queryable.Where na consulta de cliente pode conter as subpropriedades e expressões que não dependem de parâmetros de expressões lambda.Estas subpropriedades e expressões podem isolado e devem ser avaliadas imediatamente.Poderiam ser referências às variáveis locais ou a variáveis de membro que devem ser traduzidos em valores.
A classe expõe um método a seguir, PartialEval(Expression), que determina qual, se houver, subelemento em árvores de expressão pode ser avaliado imediatamente.Avalia as expressões em criando uma expressão lambda, criar um, e invocando o delegado retornado.Finalmente, substitui a subárvore com um novo nó que representa um valor constante.Isso é conhecido como a avaliação parcial.
Para adicionar uma classe parcial para executar a avaliação de uma árvore de expressão
Adicione a classe de Evaluator ao seu projeto.
Imports System.Linq.Expressions Public Module Evaluator ''' <summary>Performs evaluation and replacement of independent sub-trees</summary> ''' <param name="expr">The root of the expression tree.</param> ''' <param name="fnCanBeEvaluated">A function that decides whether a given expression node can be part of the local function.</param> ''' <returns>A new tree with sub-trees evaluated and replaced.</returns> Public Function PartialEval( ByVal expr As Expression, ByVal fnCanBeEvaluated As Func(Of Expression, Boolean) ) As Expression Return New SubtreeEvaluator(New Nominator(fnCanBeEvaluated).Nominate(expr)).Eval(expr) End Function ''' <summary> ''' Performs evaluation and replacement of independent sub-trees ''' </summary> ''' <param name="expression">The root of the expression tree.</param> ''' <returns>A new tree with sub-trees evaluated and replaced.</returns> Public Function PartialEval(ByVal expression As Expression) As Expression Return PartialEval(expression, AddressOf Evaluator.CanBeEvaluatedLocally) End Function Private Function CanBeEvaluatedLocally(ByVal expression As Expression) As Boolean Return expression.NodeType <> ExpressionType.Parameter End Function ''' <summary> ''' Evaluates and replaces sub-trees when first candidate is reached (top-down) ''' </summary> Class SubtreeEvaluator Inherits ExpressionVisitor Private candidates As HashSet(Of Expression) Friend Sub New(ByVal candidates As HashSet(Of Expression)) Me.candidates = candidates End Sub Friend Function Eval(ByVal exp As Expression) As Expression Return Me.Visit(exp) End Function Public Overrides Function Visit(ByVal exp As Expression) As Expression If exp Is Nothing Then Return Nothing ElseIf Me.candidates.Contains(exp) Then Return Me.Evaluate(exp) End If Return MyBase.Visit(exp) End Function Private Function Evaluate(ByVal e As Expression) As Expression If e.NodeType = ExpressionType.Constant Then Return e End If Dim lambda = Expression.Lambda(e) Dim fn As [Delegate] = lambda.Compile() Return Expression.Constant(fn.DynamicInvoke(Nothing), e.Type) End Function End Class ''' <summary> ''' Performs bottom-up analysis to determine which nodes can possibly ''' be part of an evaluated sub-tree. ''' </summary> Class Nominator Inherits ExpressionVisitor Private fnCanBeEvaluated As Func(Of Expression, Boolean) Private candidates As HashSet(Of Expression) Private cannotBeEvaluated As Boolean Friend Sub New(ByVal fnCanBeEvaluated As Func(Of Expression, Boolean)) Me.fnCanBeEvaluated = fnCanBeEvaluated End Sub Friend Function Nominate(ByVal expr As Expression) As HashSet(Of Expression) Me.candidates = New HashSet(Of Expression)() Me.Visit(expr) Return Me.candidates End Function Public Overrides Function Visit(ByVal expr As Expression) As Expression If expr IsNot Nothing Then Dim saveCannotBeEvaluated = Me.cannotBeEvaluated Me.cannotBeEvaluated = False MyBase.Visit(expr) If Not Me.cannotBeEvaluated Then If Me.fnCanBeEvaluated(expr) Then Me.candidates.Add(expr) Else Me.cannotBeEvaluated = True End If End If Me.cannotBeEvaluated = Me.cannotBeEvaluated Or saveCannotBeEvaluated End If Return expr End Function End Class End Module
using System; using System.Collections.Generic; using System.Linq.Expressions; namespace LinqToTerraServerProvider { public static class Evaluator { /// <summary> /// Performs evaluation & replacement of independent sub-trees /// </summary> /// <param name="expression">The root of the expression tree.</param> /// <param name="fnCanBeEvaluated">A function that decides whether a given expression node can be part of the local function.</param> /// <returns>A new tree with sub-trees evaluated and replaced.</returns> public static Expression PartialEval(Expression expression, Func<Expression, bool> fnCanBeEvaluated) { return new SubtreeEvaluator(new Nominator(fnCanBeEvaluated).Nominate(expression)).Eval(expression); } /// <summary> /// Performs evaluation & replacement of independent sub-trees /// </summary> /// <param name="expression">The root of the expression tree.</param> /// <returns>A new tree with sub-trees evaluated and replaced.</returns> public static Expression PartialEval(Expression expression) { return PartialEval(expression, Evaluator.CanBeEvaluatedLocally); } private static bool CanBeEvaluatedLocally(Expression expression) { return expression.NodeType != ExpressionType.Parameter; } /// <summary> /// Evaluates & replaces sub-trees when first candidate is reached (top-down) /// </summary> class SubtreeEvaluator : ExpressionVisitor { HashSet<Expression> candidates; internal SubtreeEvaluator(HashSet<Expression> candidates) { this.candidates = candidates; } internal Expression Eval(Expression exp) { return this.Visit(exp); } public override Expression Visit(Expression exp) { if (exp == null) { return null; } if (this.candidates.Contains(exp)) { return this.Evaluate(exp); } return base.Visit(exp); } private Expression Evaluate(Expression e) { if (e.NodeType == ExpressionType.Constant) { return e; } LambdaExpression lambda = Expression.Lambda(e); Delegate fn = lambda.Compile(); return Expression.Constant(fn.DynamicInvoke(null), e.Type); } } /// <summary> /// Performs bottom-up analysis to determine which nodes can possibly /// be part of an evaluated sub-tree. /// </summary> class Nominator : ExpressionVisitor { Func<Expression, bool> fnCanBeEvaluated; HashSet<Expression> candidates; bool cannotBeEvaluated; internal Nominator(Func<Expression, bool> fnCanBeEvaluated) { this.fnCanBeEvaluated = fnCanBeEvaluated; } internal HashSet<Expression> Nominate(Expression expression) { this.candidates = new HashSet<Expression>(); this.Visit(expression); return this.candidates; } public override Expression Visit(Expression expression) { if (expression != null) { bool saveCannotBeEvaluated = this.cannotBeEvaluated; this.cannotBeEvaluated = false; base.Visit(expression); if (!this.cannotBeEvaluated) { if (this.fnCanBeEvaluated(expression)) { this.candidates.Add(expression); } else { this.cannotBeEvaluated = true; } } this.cannotBeEvaluated |= saveCannotBeEvaluated; } return expression; } } } }
Adicionando as classes auxiliares
Esta seção contém o código para três classes auxiliares para o provedor.
Para adicionar o auxiliar classe que é usado pela implementação de System.Linq.IQueryProvider
Adicione a classe de TypeSystem (ou o módulo em Visual Basic) ao seu projeto.
Imports System.Collections.Generic Friend Module TypeSystem Friend Function GetElementType(ByVal seqType As Type) As Type Dim ienum As Type = FindIEnumerable(seqType) If ienum Is Nothing Then Return seqType End If Return ienum.GetGenericArguments()(0) End Function Private Function FindIEnumerable(ByVal seqType As Type) As Type If seqType Is Nothing Or seqType Is GetType(String) Then Return Nothing End If If (seqType.IsArray) Then Return GetType(IEnumerable(Of )).MakeGenericType(seqType.GetElementType()) End If If (seqType.IsGenericType) Then For Each arg As Type In seqType.GetGenericArguments() Dim ienum As Type = GetType(IEnumerable(Of )).MakeGenericType(arg) If (ienum.IsAssignableFrom(seqType)) Then Return ienum End If Next End If Dim ifaces As Type() = seqType.GetInterfaces() If ifaces IsNot Nothing And ifaces.Length > 0 Then For Each iface As Type In ifaces Dim ienum As Type = FindIEnumerable(iface) If (ienum IsNot Nothing) Then Return ienum End If Next End If If seqType.BaseType IsNot Nothing AndAlso seqType.BaseType IsNot GetType(Object) Then Return FindIEnumerable(seqType.BaseType) End If Return Nothing End Function End Module
using System; using System.Collections.Generic; namespace LinqToTerraServerProvider { internal static class TypeSystem { internal static Type GetElementType(Type seqType) { Type ienum = FindIEnumerable(seqType); if (ienum == null) return seqType; return ienum.GetGenericArguments()[0]; } private static Type FindIEnumerable(Type seqType) { if (seqType == null || seqType == typeof(string)) return null; if (seqType.IsArray) return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType()); if (seqType.IsGenericType) { foreach (Type arg in seqType.GetGenericArguments()) { Type ienum = typeof(IEnumerable<>).MakeGenericType(arg); if (ienum.IsAssignableFrom(seqType)) { return ienum; } } } Type[] ifaces = seqType.GetInterfaces(); if (ifaces != null && ifaces.Length > 0) { foreach (Type iface in ifaces) { Type ienum = FindIEnumerable(iface); if (ienum != null) return ienum; } } if (seqType.BaseType != null && seqType.BaseType != typeof(object)) { return FindIEnumerable(seqType.BaseType); } return null; } } }
A implementação de IQueryProvider que você adicionou anteriormente usa essa classe auxiliar.
TypeSystem.GetElementType usa reflexão para obter o argumento de tipo genérico de uma coleção de IEnumerable<T> (IEnumerable(Of T) em Visual Basic).Este método é chamado de método não genéricos de CreateQuery na implementação do provedor de consulta para fornecer o tipo de elemento da coleção do resultado da consulta.
Esta classe auxiliar não são específicas para este provedor de serviço Web TerraServer- EUA.Portanto, pode ser reutilizado para qualquer provedor de LINQ .
Para criar uma classe auxiliar de árvore de expressão
Adicione a classe de ExpressionTreeHelpers ao seu projeto.
Imports System.Linq.Expressions Friend Class ExpressionTreeHelpers ' Visual Basic encodes string comparisons as a method call to ' Microsoft.VisualBasic.CompilerServices.Operators.CompareString. ' This method will convert the method call into a binary operation instead. ' Note that this makes the string comparison case sensitive. 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 Friend Shared Function IsMemberEqualsValueExpression( ByVal exp As Expression, ByVal declaringType As Type, ByVal memberName As String) As Boolean If exp.NodeType <> ExpressionType.Equal Then Return False End If Dim be = CType(exp, BinaryExpression) ' Assert. If IsSpecificMemberExpression(be.Left, declaringType, memberName) AndAlso IsSpecificMemberExpression(be.Right, declaringType, memberName) Then Throw New Exception("Cannot have 'member' = 'member' in an expression!") End If Return IsSpecificMemberExpression(be.Left, declaringType, memberName) OrElse IsSpecificMemberExpression(be.Right, declaringType, memberName) End Function Friend Shared Function IsSpecificMemberExpression( ByVal exp As Expression, ByVal declaringType As Type, ByVal memberName As String) As Boolean Return (TypeOf exp Is MemberExpression) AndAlso (CType(exp, MemberExpression).Member.DeclaringType Is declaringType) AndAlso (CType(exp, MemberExpression).Member.Name = memberName) End Function Friend Shared Function GetValueFromEqualsExpression( ByVal be As BinaryExpression, ByVal memberDeclaringType As Type, ByVal memberName As String) As String If be.NodeType <> ExpressionType.Equal Then Throw New Exception("There is a bug in this program.") End If If be.Left.NodeType = ExpressionType.MemberAccess Then Dim mEx = CType(be.Left, MemberExpression) If mEx.Member.DeclaringType Is memberDeclaringType AndAlso mEx.Member.Name = memberName Then Return GetValueFromExpression(be.Right) End If ElseIf be.Right.NodeType = ExpressionType.MemberAccess Then Dim mEx = CType(be.Right, MemberExpression) If mEx.Member.DeclaringType Is memberDeclaringType AndAlso mEx.Member.Name = memberName Then Return GetValueFromExpression(be.Left) End If End If ' We should have returned by now. Throw New Exception("There is a bug in this program.") End Function Friend Shared Function GetValueFromExpression(ByVal expr As expression) As String If expr.NodeType = ExpressionType.Constant Then Return CStr(CType(expr, ConstantExpression).Value) Else Dim s = "The expression type {0} is not supported to obtain a value." Throw New InvalidQueryException(String.Format(s, expr.NodeType)) End If End Function End Class
using System; using System.Linq.Expressions; namespace LinqToTerraServerProvider { internal class ExpressionTreeHelpers { internal static bool IsMemberEqualsValueExpression(Expression exp, Type declaringType, string memberName) { if (exp.NodeType != ExpressionType.Equal) return false; BinaryExpression be = (BinaryExpression)exp; // Assert. if (ExpressionTreeHelpers.IsSpecificMemberExpression(be.Left, declaringType, memberName) && ExpressionTreeHelpers.IsSpecificMemberExpression(be.Right, declaringType, memberName)) throw new Exception("Cannot have 'member' == 'member' in an expression!"); return (ExpressionTreeHelpers.IsSpecificMemberExpression(be.Left, declaringType, memberName) || ExpressionTreeHelpers.IsSpecificMemberExpression(be.Right, declaringType, memberName)); } internal static bool IsSpecificMemberExpression(Expression exp, Type declaringType, string memberName) { return ((exp is MemberExpression) && (((MemberExpression)exp).Member.DeclaringType == declaringType) && (((MemberExpression)exp).Member.Name == memberName)); } internal static string GetValueFromEqualsExpression(BinaryExpression be, Type memberDeclaringType, string memberName) { if (be.NodeType != ExpressionType.Equal) throw new Exception("There is a bug in this program."); if (be.Left.NodeType == ExpressionType.MemberAccess) { MemberExpression me = (MemberExpression)be.Left; if (me.Member.DeclaringType == memberDeclaringType && me.Member.Name == memberName) { return GetValueFromExpression(be.Right); } } else if (be.Right.NodeType == ExpressionType.MemberAccess) { MemberExpression me = (MemberExpression)be.Right; if (me.Member.DeclaringType == memberDeclaringType && me.Member.Name == memberName) { return GetValueFromExpression(be.Left); } } // We should have returned by now. throw new Exception("There is a bug in this program."); } internal static string GetValueFromExpression(Expression expression) { if (expression.NodeType == ExpressionType.Constant) return (string)(((ConstantExpression)expression).Value); else throw new InvalidQueryException( String.Format("The expression type {0} is not supported to obtain a value.", expression.NodeType)); } } }
Essa classe contém os métodos que pode ser usado para determinar informações sobre e para extrair dados dos tipos específicos das árvores de expressão.Em este provedor, esses métodos são usados pela classe de LocationFinder para extrair informações sobre o local da árvore de expressão que representa a consulta.
para adicionar um tipo de exceção para consultas inválidos
Adicione a classe de InvalidQueryException ao seu projeto.
Public Class InvalidQueryException Inherits Exception Private _message As String Public Sub New(ByVal message As String) Me._message = message & " " End Sub Public Overrides ReadOnly Property Message() As String Get Return "The client query is invalid: " & _message End Get End Property End Class
using System; namespace LinqToTerraServerProvider { class InvalidQueryException : System.Exception { private string message; public InvalidQueryException(string message) { this.message = message + " "; } public override string Message { get { return "The client query is invalid: " + message; } } } }
Essa classe define um tipo de Exception que seu provedor pode acionar quando não entende a consulta de LINQ de cliente.Definindo este tipo de exceção inválido de consulta, o provedor pode lançar uma exceção mais específica do que apenas Exception de vários locais no código.
Agora você adicionou todas as partes que são necessárias para compilar o provedor.Compile o projeto de LinqToTerraServerProvider e verifique que haja compila erros.
Testando o provedor LINQ
Você pode testar seu provedor de LINQ criando um aplicativo cliente que contém uma consulta de LINQ com sua fonte de dados.
para criar um aplicativo cliente testar seu provedor
Adicione um novo projeto de Aplicativo de Console a sua solução e denomine-o ClientApp.
Em o novo projeto, adicione uma referência ao assembly do provedor.
Arraste o arquivo de app.config do seu projeto provedor para o projeto do cliente.(Esse arquivo é necessário para se comunicar com o serviço Web.)
Observação Em Visual Basic, talvez você precise clicar no botão de Mostrar todos os arquivos para consultar o arquivo de app.config em Gerenciador de Soluções.
Adicione as seguintes declarações de using (declaração deImports em Visual Basic) para o arquivo de Module.vb (ou Module1.vb em Visual Basic):
using System; using System.Linq; using LinqToTerraServerProvider;
Imports LinqToTerraServerProvider
Em o método de Main no arquivo Module.vb (ou Module1.vb em Visual Basic), insira o seguinte código:
QueryableTerraServerData<Place> terraPlaces = new QueryableTerraServerData<Place>(); var query = from place in terraPlaces where place.Name == "Johannesburg" select place.PlaceType; foreach (PlaceType placeType in query) Console.WriteLine(placeType);
Dim terraPlaces As New QueryableTerraServerData(Of Place) Dim query = From place In terraPlaces Where place.Name = "Johannesburg" Select place.PlaceType For Each placeType In query Console.WriteLine(placeType.ToString()) Next
Esse código cria uma nova instância do tipo de IQueryable<T> que você definiu em seu provedor, e então consultas que objetos usando LINQ.A consulta especificar um local para obter sobre dados usando uma expressão de igualdade.Como a fonte de dados implementa IQueryable, o compilador converte a sintaxe de expressões de consulta em chamadas para operadores de consulta padrão definidos em Queryable.Internamente, esses métodos padrão de operador de consulta cria uma árvore de expressão e chamam os métodos de Execute ou de CreateQuery que você tenha implementado como parte de sua implementação de IQueryProvider .
Compilação ClientApp.
Defina este aplicativo cliente como o projeto de inicialização “” para sua solução.Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto de ClientApp e selecione Definir como projeto de inicialização.
executar o programa e exibir os resultados.Deve haver de três resultados.
Adicionando recursos mais complexos de consulta
O provedor que você tem que este ponto fornece uma maneira muito limitada para que os clientes especifiquem informações sobre localização na consulta de LINQ .Especificamente, o provedor pode apenas obter informações sobre o local de expressões de igualdade como Place.Name == "Seattle" ou Place.State == "Alaska" (Place.Name = "Seattle" ou Place.State = "Alaska" em Visual Basic).
O procedimento a seguir mostra como adicionar suporte adicional para uma maneira de especificar informações sobre a localização.Quando você adicionar esse código, seu provedor poderá extrair informações sobre a localização das expressões de chamada de método como place.Name.StartsWith("Seat").
Para adicionar suporte para os predicados que contêm String.StartsWith
Em o projeto de LinqToTerraServerProvider , adicione o método de VisitMethodCall a definição de classe de LocationFinder .
Protected Overrides Function VisitMethodCall(ByVal m As MethodCallExpression) As Expression If m.Method.DeclaringType Is GetType(String) And m.Method.Name = "StartsWith" Then If ETH.IsSpecificMemberExpression(m.Object, GetType(Place), "Name") OrElse ETH.IsSpecificMemberExpression(m.Object, GetType(Place), "State") Then _locations.Add(ETH.GetValueFromExpression(m.Arguments(0))) Return m End If End If Return MyBase.VisitMethodCall(m) End Function
protected override Expression VisitMethodCall(MethodCallExpression m) { if (m.Method.DeclaringType == typeof(String) && m.Method.Name == "StartsWith") { if (ExpressionTreeHelpers.IsSpecificMemberExpression(m.Object, typeof(Place), "Name") || ExpressionTreeHelpers.IsSpecificMemberExpression(m.Object, typeof(Place), "State")) { locations.Add(ExpressionTreeHelpers.GetValueFromExpression(m.Arguments[0])); return m; } } return base.VisitMethodCall(m); }
Recompile o projeto de LinqToTerraServerProvider .
Para testar o novo recurso do seu provedor, abra o arquivo Module.vb (ou Module1.vb em Visual Basic) no projeto de ClientApp .Substitua o código no método de Main com o seguinte código:
QueryableTerraServerData<Place> terraPlaces = new QueryableTerraServerData<Place>(); var query = from place in terraPlaces where place.Name.StartsWith("Lond") select new { place.Name, place.State }; foreach (var obj in query) Console.WriteLine(obj);
Dim terraPlaces As New QueryableTerraServerData(Of Place) Dim query = From place In terraPlaces Where place.Name.StartsWith("Lond") Select place.Name, place.State For Each obj In query Console.WriteLine(obj) Next
executar o programa e exibir os resultados.deve haver aproximadamente 29 resultados.
O procedimento a seguir mostra como adicionar funcionalidade ao seu provedor para ativar a consulta de cliente para especificar informações sobre o local usando dois métodos adicionais, especificamente Enumerable.Contains e List<T>.Contains.Quando você adicionar esse código, seu provedor poderá extrair informações sobre a localização das expressões de chamada de método na consulta de cliente como placeList.Contains(place.Name), onde a coleção de placeList é uma lista concreta fornecida pelo cliente.A vantagem de permitir que clientes usar o método de Contains é que pode especificar qualquer número de locais apenas adicionando-os a placeList.Variar o número de locais não altera a sintaxe da consulta.
Para adicionar suporte para consultas que têm contém o método no seu “” cláusula where
Em o projeto de LinqToTerraServerProvider , na definição de classe de LocationFinder , substituir o método de VisitMethodCall com o seguinte código:
Protected Overrides Function VisitMethodCall(ByVal m As MethodCallExpression) As Expression If m.Method.DeclaringType Is GetType(String) And m.Method.Name = "StartsWith" Then If ETH.IsSpecificMemberExpression(m.Object, GetType(Place), "Name") OrElse ETH.IsSpecificMemberExpression(m.Object, GetType(Place), "State") Then _locations.Add(ETH.GetValueFromExpression(m.Arguments(0))) Return m End If ElseIf m.Method.Name = "Contains" Then Dim valuesExpression As Expression = Nothing If m.Method.DeclaringType Is GetType(Enumerable) Then If ETH.IsSpecificMemberExpression(m.Arguments(1), GetType(Place), "Name") OrElse ETH.IsSpecificMemberExpression(m.Arguments(1), GetType(Place), "State") Then valuesExpression = m.Arguments(0) End If ElseIf m.Method.DeclaringType Is GetType(List(Of String)) Then If ETH.IsSpecificMemberExpression(m.Arguments(0), GetType(Place), "Name") OrElse ETH.IsSpecificMemberExpression(m.Arguments(0), GetType(Place), "State") Then valuesExpression = m.Object End If End If If valuesExpression Is Nothing OrElse valuesExpression.NodeType <> ExpressionType.Constant Then Throw New Exception("Could not find the location values.") End If Dim ce = CType(valuesExpression, ConstantExpression) Dim placeStrings = CType(ce.Value, IEnumerable(Of String)) ' Add each string in the collection to the list of locations to obtain data about. For Each place In placeStrings _locations.Add(place) Next Return m End If Return MyBase.VisitMethodCall(m) End Function
protected override Expression VisitMethodCall(MethodCallExpression m) { if (m.Method.DeclaringType == typeof(String) && m.Method.Name == "StartsWith") { if (ExpressionTreeHelpers.IsSpecificMemberExpression(m.Object, typeof(Place), "Name") || ExpressionTreeHelpers.IsSpecificMemberExpression(m.Object, typeof(Place), "State")) { locations.Add(ExpressionTreeHelpers.GetValueFromExpression(m.Arguments[0])); return m; } } else if (m.Method.Name == "Contains") { Expression valuesExpression = null; if (m.Method.DeclaringType == typeof(Enumerable)) { if (ExpressionTreeHelpers.IsSpecificMemberExpression(m.Arguments[1], typeof(Place), "Name") || ExpressionTreeHelpers.IsSpecificMemberExpression(m.Arguments[1], typeof(Place), "State")) { valuesExpression = m.Arguments[0]; } } else if (m.Method.DeclaringType == typeof(List<string>)) { if (ExpressionTreeHelpers.IsSpecificMemberExpression(m.Arguments[0], typeof(Place), "Name") || ExpressionTreeHelpers.IsSpecificMemberExpression(m.Arguments[0], typeof(Place), "State")) { valuesExpression = m.Object; } } if (valuesExpression == null || valuesExpression.NodeType != ExpressionType.Constant) throw new Exception("Could not find the location values."); ConstantExpression ce = (ConstantExpression)valuesExpression; IEnumerable<string> placeStrings = (IEnumerable<string>)ce.Value; // Add each string in the collection to the list of locations to obtain data about. foreach (string place in placeStrings) locations.Add(place); return m; } return base.VisitMethodCall(m); }
Este método adiciona cada cadeia de caracteres na coleção que Contains é aplicado, a lista de locais para ver o serviço Web com.Um método chamado Contains é definido em Enumerable e em List<T>.Portanto, o método de VisitMethodCall deve verificar se ambos esses tipos de declaração.Enumerable.Contains é definido como um método de extensão; portanto a coleção que é aplicado a é realmente o primeiro argumento para o método.List.Contains é definido como um método de instância; portanto a coleção que é aplicado a é o objeto de recebimento do método.
Recompile o projeto de LinqToTerraServerProvider .
Para testar o novo recurso do seu provedor, abra o arquivo Module.vb (ou Module1.vb em Visual Basic) no projeto de ClientApp .Substitua o código no método de Main com o seguinte código:
QueryableTerraServerData<Place> terraPlaces = new QueryableTerraServerData<Place>(); string[] places = { "Johannesburg", "Yachats", "Seattle" }; var query = from place in terraPlaces where places.Contains(place.Name) orderby place.State select new { place.Name, place.State }; foreach (var obj in query) Console.WriteLine(obj);
Dim terraPlaces As New QueryableTerraServerData(Of Place) Dim places = New String() {"Johannesburg", "Yachats", "Seattle"} Dim query = From place In terraPlaces Where places.Contains(place.Name) Order By place.State Select place.Name, place.State For Each obj In query Console.WriteLine(obj) Next
executar o programa e exibir os resultados.deve haver aproximadamente 5 resultados.
Próximas etapas
Este tópico passo a passo mostrou como criar um provedor de LINQ para um método de um serviço Web.Se você desejar considerar ao cabo desenvolvimento adicional de um provedor de LINQ , considere essas possibilidades:
Habilitar o provedor de LINQ para manipular outras maneiras de especificar um local na consulta de cliente.
Investigar os outros métodos que expõe de serviço Web TerraServer- EUA, e criar um provedor de LINQ que faz interface com um de esses métodos.
Localizar um serviço Web diferente que você está interessado, e criar um provedor de LINQ para ele.
Criar um provedor de LINQ para uma fonte de dados diferente um serviço Web.
Para obter mais informações sobre como criar seu próprio provedor LINQ, consulte LINQ: Criando um provedor de IQueryable em blogs do MSDN.
Consulte também
Tarefas
Como: modificar árvores de expressão (C# e Visual Basic)
Referência
Conceitos
A ativação de uma fonte de dados para a consulta do LINQ
Os serviços do Windows Communication Foundation e serviços de dados do WCF em Visual Studio