Compartilhar via


Demonstra Passo a passo: Criando um provedor IQueryable LINQ

Este tópico avançado fornece instruções passo a passo para criar um personalizada LINQ provedor. Quando tiver terminado, você poderá usar o provedor que você cria para gravar LINQ consultas ao serviço Web TerraServer EUA.

serviço Web TerraServer USA fornece uma interface a um banco de dados de imagens aéreas dos Estados Unidos.Ele também expõe um método que retorna informações sobre os locais nos Estados Unidos, determinada parte ou todo um nome de local.Esse método, chamado GetPlaceList, é o método que seu LINQ chamará o provedor. O provedor usará Windows Communication Foundation (WCF) para se comunicar com serviço Web. Para obter mais informações sobre serviço Web TerraServer USA, consulte Visão geral dos serviços da Web do TerraServer EUA.

Esse provedor é um relativamente simples IQueryable provedor. Ele espera informações específicas em consultas que ele manipula e tem um sistema de tipos fechados, expondo um único tipo para representar os dados resultantes.Este provedor examina apenas um tipo de expressão de telefonar de método na árvore de expressão que representa a consulta, que é a mais interna telefonar Where. Ele extrai os dados que ele deve ter para consultar o serviço desta expressão.Em seguida, chama serviço Web e insere os dados retornados na árvore de expressão em vez do inicialIQueryable fonte de dados. A execução da consulta o restante é tratado pelo Enumerable implementações de operadores de consulta padrão.

Os exemplos de código neste tópico são fornecidos no translation from VPE for Csharp e Visual Basic.

Essa explicação passo a passo ilustra as seguintes tarefas:

  • Criando o projeto em Visual Studio.

  • Implementing the interfaces that are required for an IQueryable LINQ provider: IQueryable<T>IOrderedQueryable<T>, and IQueryProvider.

  • Adicionando um tipo .NET personalizado 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.

  • Criar uma subclasse de visitante árvore de expressão que localiza a expressão que representa a telefonar mais interna à Queryable.Where método.

  • Criar uma subclasse de visitante de árvore de expressão extrai informações do LINQ consulta para usar na solicitação de serviço Web.

  • Criar uma subclasse de visitante de árvore de expressão que modifica a árvore de expressão que representa o total LINQ consulta.

  • Usando uma classe avaliador parcialmente avaliar uma árvore de expressão.Essa etapa é necessária porque ele converte todas as referências de variáveis locais no LINQ consulta em valores.

  • Criando um auxiliar de árvore de expressão de classe e uma nova classe de exceção.

  • Teste o LINQ provedor de um aplicativo cliente que contém um LINQ consulta.

  • Adicionando recursos de consulta mais complexos à LINQ provedor.

    Observação:

    O provedor LINQ esta explicação passo a passo cria está disponível sistema autônomo uma amostra.Consulte LINQ to TerraServer Provedor Exemplo Para obter mais informações.

Pré-requisitos

Para completar este passo a passo, são necessários os seguintes componentes:

  • Visual Studio 2008
Observação:

Seu computador pode mostrar diferentes nomes ou localizações para alguns dos elementos de 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 do Visual Studio.

Criando o projeto

Para criar o projeto no Visual Studio

  1. In Visual Studio, crie um novo biblioteca de classes aplicativo. Nomeie o projeto LinqToTerraServerProvider.

  2. In O gerenciador de soluções, selecionar o Class1.cs (or Class1.vb) o arquivo e renomeie-o para QueryableTerraServerData.cs (or QueryableTerraServerData.vb).Na caixa de diálogo pop-up, clicar Sim para renomear todas as referências ao elemento de código.

    Criar o provedor sistema autônomo um biblioteca de classes projeto em Visual Studio porque sistema autônomo aplicativos cliente executável adicionará o assembly do provedor sistema autônomo uma referência ao seu projeto.

Para adicionar uma referência de serviço para serviço Web

  1. In O gerenciador de soluções, clicar com o botão direito do mouse o LinqToTerraServerProvider projeto e clique em Adicionar referência de serviço.

    The Adicionar referência de serviço caixa de diálogo é aberta.

  2. No Endereço box, type http://TerraServer.Microsoft.com/TerraService2.asmx.

  3. No Namespace box, type TerraServerReference e, em seguida, clicar OK.

    serviço Web TerraServer USA é adicionado sistema autônomo uma referência de serviço para que o aplicativo possa se comunicar com serviço Web por meio de Windows Communication Foundation (WCF). Adicionando uma referência de serviço para o projeto Visual Studio gera um App.config arquivo que contém um proxy e um ponto de extremidade do serviço Web.Para obter mais informações, consulte Introdução ao Windows Communication Foundation Services no Visual Studio.

Agora você tem um projeto que tenha um arquivo chamado App.config, um arquivo chamado QueryableTerraServerData.cs (or QueryableTerraServerData.vb), e uma referência de serviço chamado TerraServerReference.

Implementar as interfaces necessárias

Para criar um LINQ provedor de, no mínimo você deve implementar o IQueryable<T> e IQueryProvider interfaces. IQueryable<T> e IQueryProvider são derivadas das outras interfaces necessárias; portanto, ao implementar essas duas interfaces, também estão implementando as outras interfaces que são necessárias para um LINQ provedor.

Se você deseja oferecer suporte a operadores de consulta de classificação, sistema autônomo OrderBy e ThenBy, você também deve implementar o IOrderedQueryable<T> interface. Porque IOrderedQueryable<T> deriva da IQueryable<T>, você pode implementar ambas essas interfaces em um tipo, que é o que faz esse provedor.

Para implementar sistema.Linq.IQueryable`1 e sistema.Linq.IOrderedQueryable`1

  • No arquivo QueryableTerraServerData.cs (or QueryableTerraServerData.vb), adicione o código a seguir.

    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
        }
    }
    

    The IOrderedQueryable<T> implementação, o QueryableTerraServerData classe implementa três propriedades declaradas no IQueryable e dois métodos de enumeração é declarada no IEnumerable e IEnumerable<T>.

    Essa classe tem dois construtores.O primeiro construtor é chamado de aplicativo cliente para criar o objeto para gravar o LINQ consulta. O segundo construtor é chamado interno para a biblioteca de provedor por código no IQueryProvider implementação.

    Quando o GetEnumerator método for chamado em um objeto do tipo QueryableTerraServerData, a consulta que ela representa é executada e os resultados da consulta são enumerados.

    Esse código, exceto para o nome da classe, não é específico para esse provedor de serviços Web TerraServer EUA.Portanto, pode ser reutilizado para qualquer LINQ provedor.

Para implementar sistema.Linq.IQueryProvider

  • Adicionar o TerraServerQueryProvider classe para 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 nesta classe implementa os quatro métodos que são necessárias para implementar o IQueryProvider interface. Os dois CreateQuery métodos de criam consultas que estão associadas com a fonte de dados. Os dois Execute métodos enviá essas consultas a ser executado.

    O não-genéricas CreateQuery método usa a reflexão para obter o tipo de elemento da sequência de consulta cria retornaria se ele foi executado. Em seguida, ele usa o Activator classe para criar um novo QueryableTerraServerData instância é construída com o tipo de elemento sistema autônomo seu argumento de tipo genérico. O resultado da chamada a não genérica CreateQuery método é o mesmo sistema autônomo se o genérica CreateQuery método tinha sido chamado com o argumento do tipo correto.

    Maior parte da lógica de execução de consulta é manipulado em uma classe diferente, que adicionará posteriormente.Essa funcionalidade é tratada em outro local, porque ele é específico para a fonte de dados que está sendo consultado, ao passo que o código nessa classe é genérico para qualquer LINQ provedor. Para usar este código para um provedor diferente, talvez seja necessário alterar o nome da classe e o nome do tipo de contexto de consulta que é referenciado em dois dos métodos.

Adicionando um tipo personalizado para representar os dados de resultados

Você precisará de um tipo .NET para representar os dados obtidos do serviço Web.Esse tipo será usado no cliente LINQ consulta para definir os resultados que deseja. O procedimento a seguir cria um tipo de tal. Esse tipo, chamado Place, contém informações sobre uma única localização geográfica, sistema autônomo uma cidade, um estacionar ou um lago.

Esse código também contém um tipo de enumeração chamado PlaceType, que define os vários tipos de localização geográfica e é usado na Place classe.

Para criar um tipo de resultado personalizado

  • Adicionar o Place classe e o PlaceType enumeração para seu projeto.

    Public Class Place
        ' Properties.
        Private _Name As String
        Private _State As String
        Private _PlaceType As PlaceType
    
        Public Property Name() As String
            Get
                Return _Name
            End Get
            Private Set(ByVal value As String)
                _Name = value
            End Set
        End Property
    
        Public Property State() As String
            Get
                Return _State
            End Get
            Private Set(ByVal value As String)
                _State = value
            End Set
        End Property
    
        Public Property PlaceType() As PlaceType
            Get
                Return _PlaceType
            End Get
            Private Set(ByVal value As PlaceType)
                _PlaceType = value
            End Set
        End Property
    
        ' 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 Place tipo simplifica a criação de um objeto de resultado do tipo retornado pelo serviço Web. Enquanto o provedor pode retornar o tipo de resultado definido pela API do serviço Web diretamente, ele exigiria aplicativos clientes para adicionar uma referência ao serviço da Web.Criando um novo tipo sistema autônomo parte da biblioteca de provedor, o cliente não tem sobre sistema autônomo tipos e métodos que expõe serviço Web.

Adicionando funcionalidade para obter dados da fonte de dados

Essa implementação de provedor pressupõe que a telefonar mais interna para Queryable.Where contém as informações de local para usar para consultar serviço Web. O mais interno Queryable.Where a telefonar é o where (cláusulaWhere cláusula Visual Basic) ou Queryable.Where telefonar de método primeiro ocorre em um LINQconsulta, ou o mais próximo ao "final" da árvore de expressão que representa a consulta.

Para criar uma classe de contexto de consulta

  • Adicionar o TerraServerQueryContext classe para 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.CopyAndModify(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.CopyAndModify(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 organiza o trabalho de execução de uma consulta.Após localizar a expressão que representa o mais interno Queryable.Where telefonar, este código recupera a expressão lambda que representa o predicado foi passado para Queryable.Where. Ele então passa a expressão de predicado para um método a ser avaliada parcialmente para que todas as referências para as variáveis locais são convertidas em valores.Em seguida, ele chama um método para extrair os locais solicitados o predicado e chama outro método para obter os dados do resultado do serviço Web.

    Na próxima etapa, este código copia a árvore de expressão que representa o LINQ consulta e faz uma modificação à árvore de expressão. O código usa uma subclasse de visitante de árvore de expressão para substituir a fonte de dados que a telefonar de operador de consulta mais interno é aplicada à lista concreta de Place objetos que foram obtidos do serviço Web.

    Antes da lista de Place objetos é inserido na árvore de expressão, seu tipo é alterado de IEnumerable para IQueryable chamando AsQueryable. Essa alterar de tipo é necessária porque quando a árvore de expressão é regravada, o nó que representa a telefonar de método para o método do operador de consulta mais interno é reconstruído.O nó é reconstruído porque um dos argumentos foi alterado (ou seja, a fonte de dados que ela é aplicada a).The Call(Expression, MethodInfo, IEnumerable<Expression>) método, que é usado para reconstruir o nó, será jogada uma exceção se qualquer argumento não pode ser atribuído ao parâmetro correspondente do método que serão passado para. Nesse caso, a IEnumerable lista de Place objetos não seriam pode ser atribuídos para o IQueryable parâmetro do Queryable.Where. Portanto, seu tipo é alterado para IQueryable.

    Alterando seu tipo para IQueryable, a coleção também obtém um IQueryProvider membro, acessado pela Provider propriedade, que pode criar ou executar consultas. O tipo dinâmico do IQueryable°Place coleção é EnumerableQuery, que é um tipo que é interno para o System.Linq API. O provedor de consultas que está associado este tipo executa consultas substituindo Queryable chamadas de operador de consulta padrão com o equivalente a Enumerable operadores, então, que efetivamente a consulta passará a ser um LINQ a consulta de objetos.

    O código final no TerraServerQueryContext classe chama um dos dois métodos de IQueryable lista de Place objetos. Ele chama CreateQuery Se o cliente consulta retorna resultados enumeráveis, ou Execute Se o cliente consulta retorna um resultado não enumerável.

    O código nessa classe é muito específico ao provedor TerraServer EUA.Portanto, ela é encapsulada no TerraServerQueryContext classe em vez de que está sendo inserido diretamente para a mais genérica IQueryProvider implementação.

O provedor que você está criando requer somente as informações no Queryable.Where predicado para consultar serviço Web. Portanto, ele usa LINQ a objetos para o trabalho de executar o LINQ consulta usando o interno EnumerableQuery Digite. Uma maneira alternativa de usar LINQ a objetos para executar a consulta é fazer com que a disposição do cliente a parte da consulta para ser executado pelo LINQ para objetos em um LINQ a consulta de objetos. Isso é realizado chamando AsEnumerable<TSource> no restante da consulta, que é a parte da consulta que requer o provedor para seus propósitos específicos. A vantagem desse tipo de implementação é que a divisão de trabalho entre o provedor personalizado e LINQ para objetos é mais transparente.

Observação:

O provedor apresentado neste tópico é um simples provedor que ofereça suporte a consulta mínima das suas próprias .Portanto, Ele depende muito de LINQ a objetos para executar consultas. Um complexo LINQ provedor sistema autônomo LINQ to SQL pode oferecer suporte a toda a consulta sem entregando qualquer trabalho para LINQ para objetos.

Para criar uma classe para obter dados de serviço Web

  • Adicionar o WebServiceHelper classe (ou módulo em Visual Basic) para 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 a funcionalidade que obtém dados de serviço Web.Esse código usa um tipo chamado TerraServiceSoapClient, que é gerada automaticamente para o projeto por Windows Communication Foundation (WCF), para chamar o método de serviço Web GetPlaceList. Em seguida, cada resultado é convertido do tipo de retorno do método de serviço Web do tipo .NET que define o provedor de dados.

    Esse código contém duas verificações que melhoram a usabilidade da biblioteca do provedor.A primeira verificação limita o time máximo que um aplicativo cliente aguardará uma resposta, limitando o número total de chamadas feitas para serviço Web por consulta cinco.Para cada local que é especificado na consulta de cliente, uma solicitação de serviço Web é gerada.Portanto, o provedor lança uma exceção se a consulta contiver mais de cinco locais.

    A segunda seleção determina se o número de resultados retornados pelo serviço 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.Em vez de retornar uma lista incompleta para o cliente, o provedor lança uma exceção.

Adicionar as classes de visitante árvore de expressão

Para criar o visitante localiza o interno onde expressão de telefonar de método

  1. Adicionar o ExpressionVisitor classe para seu projeto. Este código está disponível em Como: Implementar um visitante de árvore de expressão. Add using () diretivasImports instruções no Visual Basic) o arquivo para os namespaces a seguir: sistema.Collections.Generic sistema.Collections.ObjectModel e sistema.Linq.Expressions.

  2. Adicionar o InnermostWhereFinder classe, que herda a ExpressionVisitor classe para 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;
            }
        }
    }
    

    Esta classe herda a classe de visitante de árvore de expressão base para executar a funcionalidade de localização de uma expressão específica.Classe base expressão árvore visitante foi projetada para herdadas e especializadas para uma tarefa específica que envolve a percorrer uma árvore de expressão.A classe derivada substitui o VisitMethodCall método para buscar a expressão que representa a mais interna telefonar para Where Na árvore de expressão que representa a consulta de cliente. Essa expressão mais interno é a expressão que o provedor extrai os locais de Pesquisar da.

Para criar o visitante que extrai dados para consultar serviço Web

  • Adicionar o LocationFinder classe para 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 para Queryable.Where. Ele deriva da base de árvore de expressão classe visitante e substitui apenas a VisitBinary método.

    A classe de base do visitante de árvore de expressão envia expressões binário, sistema autônomo expressões de igualdade sistema autônomo place.Name == "Seattle" (place.Name = "Seattle" em Visual Basic), para o VisitBinary método. Essa substituição VisitBinary método, se a expressão corresponde ao padrão da expressão igualdade pode fornecer informações de local, as informações são extraídas e armazenadas em uma lista de locais.

    Essa classe usa um visitante da árvore de expressão para localizar as informações sobre o local na árvore de expressão como um visitante é projetado para percorrer e examinando a árvores de expressão.O código resultante é melhor e menos propensa que se tivesse sido implementado sem usar o visitante.

    Nesse preparar da explicação passo a passo, o seu provedor oferece suporte apenas de forma limitada de fornecer informações sobre o local na consulta.Posteriormente no tópico, você adicionará funcionalidade para permitir que outras maneiras de fornecer informações sobre o local.

Para criar o visitante que modifica a árvore de expressão

  • Adicionar o ExpressionTreeModifier classe para 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
    
        Friend Function CopyAndModify(ByVal expression As Expression) As Expression
            Return Me.Visit(expression)
        End Function
    
        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;
            }
    
            internal Expression CopyAndModify(Expression expression)
            {
                return this.Visit(expression);
            }
    
            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;
            }
        }
    }
    

    Essa classe deriva da base de árvore de expressão classe visitante e substitui o VisitConstant método. Nesse método, ele substituirá o objeto que a telefonar de operador de consulta mais interno padrão é aplicada a uma lista de concreto de Place objetos.

    The CopyAndModify método chama a implementação da classe base das Visit método. Este CopyAndModify método é necessário porque o Visit método, que é protected (Protected em Visual Basic), não pode ser chamadas diretamente a partir da classe de contexto da consulta.

    Essa classe de modificador de árvore de expressão usa o visitante de árvore de expressão porque o visitante é projetado para atravessar, examinar e copiar árvores de expressão.Derivando da classe base expressão árvore visitante, essa classe requer código mínimo para executar sua função.

Adicionando o avaliador de expressão

O predicado é passado para o Queryable.Where método da consulta de cliente pode conter subexpressões que não dependem do parâmetro da expressão lambda. Esses subexpressões isolados podem e devem ser avaliados imediatamente.Ele pode estar referências às variáveis locais ou variáveis de membros que devem ser traduzidos em valores.

A próxima classe expõe um método, PartialEval(Expression), que determina que, se houver, de subárvores na expressão podem ser avaliadas imediatamente. Em seguida, ele avalia as expressões Criando uma expressão lambda, compilando-lo e invocar o delegado retornado.Por fim, ele substituirá a subárvore com um novo nó que representa um valor constante.Isso é conhecido sistema autônomo avaliação parcial.

Para adicionar uma classe para realizar avaliação parcial de uma árvore de expressão

  • Adicionar o Evaluator classe para 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
    
            Protected 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
    
            Protected 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);
                }
    
                protected 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;
                }
    
                protected 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 classes auxiliar

Esta seção contém código para três classes auxiliares para seu provedor.

Para adicionar a classe auxiliar usada pela implementação sistema.Linq.IQueryProvider

  • Adicionar o TypeSystem classe (ou módulo em Visual Basic) para 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 And _
               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;
            }
        }
    }
    

    The IQueryProvider implementação de que você adicionou anteriormente usa essa classe auxiliar.

    TypeSystem.GetElementType usa a reflexão para obter o argumento de tipo genérico de um IEnumerable<T> (IEnumerable(Of T) em Visual Basic) coleção. Este método é chamado da não genérica CreateQuery método na implementação de provedor de consulta para fornecer o tipo de elemento da coleção de resultado de consulta.

    Essa classe auxiliar não é específico para esse provedor de serviços Web TerraServer EUA.Portanto, pode ser reutilizado para qualquer LINQ provedor.

Para criar uma classe do auxiliar de árvore de expressão

  • Adicionar o ExpressionTreeHelpers classe para 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 métodos que podem ser usados para determinar informações sobre e extrair dados de tipos específicos de árvores de expressão.Nesse provedor, esses métodos são usados pelo LocationFinder classe extrair informações de localização de árvore de expressão que representa a consulta.

Para adicionar um tipo de exceção para consultas inválidos

  • Adicionar o InvalidQueryException classe para 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 Exception tipo de seu provedor pode lançar quando não entende o LINQ consulta do cliente. Ao definir esse tipo de exceção de consulta inválido, o provedor pode lançar uma exceção mais específica que apenas Exception de vários lugares no código.

Agora você adicionou todas as peças necessárias para compilar o provedor.Construir o LinqToTerraServerProviderprojeto e verifique se estão sem erros de compilar.

Testando o provedor LINQ

Você pode teste seu LINQ provedor criando um aplicativo cliente que contém um LINQ Consultar fonte de dados.

Para criar um aplicativo cliente para testar o seu provedor

  1. Adicionar um novo Aplicativo de console projeto à sua solução e nomeie-ClientApp.

  2. No novo projeto, adicione uma referência ao assembly do provedor.

  3. arrastar o App.config o arquivo de projeto de seu provedor para o projeto de cliente.(Esse arquivo é necessário para a comunicação com serviço Web.)

    Observação:

    Em Visual Basic, você talvez tenha que clicar a Mostrar todos os arquivos botão para ver o App.config arquivo in O gerenciador de soluções.

  4. Adicione o seguinte using () instruçõesImports demonstrativo no Visual Basic) para o programa.cs (or Module1.vb in Visual Basic) arquivo:

    using System;
    using System.Linq;
    using LinqToTerraServerProvider;
    
    Imports LinqToTerraServerProvider
    
  5. No Main método no arquivo programa.cs (or Module1.vb in Visual Basic), insira o código a seguir:

    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 IQueryable<T> tipo que você definiu no seu provedor e, em seguida, consultas que o objeto usando LINQ. A consulta Especifica um local para obter dados usando uma expressão de igualdade.Como a fonte de dados implementa IQueryable, o compilador converte a sintaxe da expressão de consulta em chamadas para os operadores de consulta padrão definidos Queryable. Internamente, esses métodos operadores de consulta padrão compilação uma árvore de expressões e chame o Execute ou CreateQuery métodos implementada sistema autônomo parte de seu IQueryProvider implementação.

  6. Criar ClientApp.

  7. Defina este aplicativo cliente sistema autônomo o projeto "Inicializar" para sua solução.In O gerenciador de soluções, clicar com o botão direito do mouse o ClientApp projeto e selecionar conjunto sistema autônomo projeto de inicialização.

  8. Execute o programa e exibir os resultados.Deve haver aproximadamente três resultados.

Adicionando recursos de consulta mais complexos

O provedor que você precisa deste ponto fornece uma maneira muito limitada de clientes especificar informações de localização no LINQ consulta. Especificamente, o provedor só é capaz de obter informações sobre o local das expressões de igualdade, sistema autônomo 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 para uma outra maneira de especificar informações de local.Quando você tiver adicionado esse código, seu provedor poderá extrair informações de localização de expressões de telefonar de método, sistema autônomo place.Name.StartsWith("Seat").

Para adicionar suporte a predicados que contêm String.StartsWith

  1. No LinqToTerraServerProvider projeto , adicione o VisitMethodCall método para o LocationFinder definição de classe.

    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") Or _
               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);
    }
    
  2. Recompilar o LinqToTerraServerProviderprojeto .

  3. Para testar o novo recurso do seu provedor, abra o arquivo programa.cs (or Module1.vb in Visual Basic) na ClientAppprojeto .substituir o código a Main método com o código a seguir:

    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
    
  4. Execute o programa e exibir os resultados.Deve haver aproximadamente 29 resultados.

O procedimento a seguir mostra como adicionar funcionalidade ao seu provedor para permitir que a consulta de cliente para especificar informações de localização usando dois métodos adicionais, especificamente Enumerable.Contains e List<T>.Contains. Quando você tiver adicionado esse código, seu provedor poderá extrair informações de localização de método telefonar expressões da consulta de cliente, sistema autônomo placeList.Contains(place.Name), onde o placeList coleção é uma lista de concreta fornecida pelo cliente. A vantagem de permitindo que clientes usam o Contains método é que eles podem especificar qualquer número de localizações apenas adicionando-as para placeList. O número de locais de variáveis não altera a sintaxe da consulta.

Para adicionar suporte para consultas que tenham o método contém na sua cláusula 'onde'

  1. No LinqToTerraServerProvider projeto , no LocationFinder definição de classe, substitua o VisitMethodCall método com o código a seguir:

    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") Or _
               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") Or _
                   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") Or _
                   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 seqüência da coleção que Contains é aplicada a, à lista de locais para consultar serviço Web com. Um método chamado Contains é definido em ambos Enumerable e List<T>. Portanto, a VisitMethodCall método deve fazer para ambos esses tipos de declaração. Enumerable.Contains é definido sistema autônomo um método de extensão, portanto, a coleção será aplicada a é na verdade, o primeiro argumento do método. List.Contains é definido sistema autônomo um método de instância; portanto a coleção será aplicada a é o objeto de recebimento do método.

  2. Recompilar o LinqToTerraServerProviderprojeto .

  3. Para testar o novo recurso do seu provedor, abra o arquivo programa.cs (or Module1.vb in Visual Basic) na ClientAppprojeto .substituir o código a Main método com o código a seguir:

    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
    
  4. Execute o programa e exibir os resultados.Deve haver aproximadamente 5 resultados.

Próximas etapas

Este tópico de explicação passo a passo mostrou como criar um LINQ provedor de um método de um serviço Web. Se você deseja buscar desenvolvimento adicional de um LINQ provedor, considere essas possibilidades:

  • Habilitar o LINQ provedor para lidar com outras maneiras de especificar um local na consulta de cliente.

  • Investigue os outros métodos que serviço Web TerraServer USA expõe e criar um LINQ provedor que faz interface com um desses métodos.

  • Localizar um serviço Web diferente que você está interessado e cria um LINQ provedor para ele.

  • Criar um LINQ provedor para uma fonte de dados Outros que um serviço Web.

Consulte também

Tarefas

LINQ to TerraServer Provedor Exemplo

Como: Implementar um visitante de árvore de expressão

Como: Modificar árvores de expressão

Conceitos

Ativando uma fonte de dados para LINQ consultar

Referência

IQueryable<T>

IOrderedQueryable<T>

Outros recursos

Windows Communication Foundation Services e serviços de dados ADO.NET

Date

History

Motivo

Julho de 2008

Link adicionado a amostra TerraServer.

Correção de bug do conteúdo.