Názorný postup: Vytváření poskytovatele IQueryable LINQ

Toto rozšířené téma obsahuje podrobné pokyny pro vytvoření vlastního LINQ zprostředkovatele. Po dokončení bude možné použít zprostředkovatele vytvořit zápis LINQ dotazy vůči TerraServer-USA webové služby.

TerraServer-USA webová služba poskytuje rozhraní k databázi antén obrazů Spojených států.Také poskytuje metodu, která vrátí informace o umístění ve Spojených státech danou část nebo celý název umístění.Tuto metodu, která se nazývá GetPlaceList, metoda, vaše LINQ bude volat poskytovatele. Zprostředkovatel použije Windows Communication Foundation (WCF) komunikaci s webovou službou.Další informace o TerraServer-USA webové služby naleznete v Přehled webových služeb TerraServer-USA.

Tento zprostředkovatel je relativně jednoduché IQueryable zprostředkovatele.V dotazech, které zpracovává očekává konkrétní informace a má uzavřené typu systému, jeden typ představující výsledek data vystavení.Tento zprostředkovatel zkoumá pouze jeden typ výraz volání metody ve stromu výraz, který představuje dotaz, který je vnitřní volání Where.Extrahuje data, musí mít pro webové služby z tohoto výrazu dotazu.Potom zavolá webovou službu a vloží do stromu výrazů namísto počáteční vrácená data IQueryable zdroje dat.Zbytek spuštění dotazu je zpracována Enumerable implementace standardních operátorů pro dotazování.

Příklady kódu v tomto tématu jsou uvedeny v jazyce C# a Visual Basic.

Tento návod ilustruje následující úkoly:

  • Vytváření projektu v Visual Studio.

  • Implementace rozhraní, které jsou požadovány pro IQueryableLINQ zprostředkovatele: IQueryable<T>, IOrderedQueryable<T>, a IQueryProvider.

  • Přidání vlastního typu .NET k reprezentaci dat z webové služby.

  • Vytvoření třídy kontextu dotazu a třídu, která získává data z webové služby.

  • Vytváření podtřídy návštěvník stromu výraz, který vyhledá výraz, který představuje vnitřní volání Queryable.Where metoda.

  • Vytváření podtřídy návštěvník stromu výraz, který extrahuje informace z LINQ dotazu použít požadavek webové služby.

  • Vytváření podtřídy návštěvník stromu výraz, který upravuje stromu výraz, který představuje úplnou LINQ dotazu.

  • Třída vyhodnocení použití částečně vyhodnotit strom výrazů.Tento krok je nezbytný, protože převádí všechny místní proměnné odkazy v LINQ dotazu do hodnot.

  • Vytvoření helper stromu výraz a novou třídu výjimek.

  • Testování LINQ z klientské aplikace, která obsahuje zprostředkovatele LINQ dotazu.

  • Přidání funkce složitější dotaz LINQ zprostředkovatele.

    [!POZNÁMKA]

    Poskytovateli LINQ, který vytváří tento návod je k dispozici jako vzorek.Více informací naleznete v tématu Vzorky LINQ.

Požadavky

Tento návod vyžaduje funkce, které jsou zavedeny v Visual Studio 2008.

[!POZNÁMKA]

Ve vašem počítači se pro některé z prvků uživatelského rozhraní sady Visual Studio mohou zobrazit jiné názvy a umístění, než jsou uvedeny v následujících pokynech. Tyto prvky jsou určeny verzí aplikace Visual Studio a použitým nastavením. Další informace naleznete v tématu Nastavení aplikace Visual Studio.

Vytváření projektu

V aplikaci Visual Studio vytvořte projekt

  1. V Visual Studio, vytvořte novou Knihovny tříd aplikace. Název projektu LinqToTerraServerProvider.

  2. V Průzkumníku, vyberte Class1.cs (nebo Class1.vb) soubor a přejmenujte jej na QueryableTerraServerData.cs (nebo QueryableTerraServerData.vb).Klepněte v dialogovém okně vysunutém Ano k přejmenování všech odkazů na element kódu.

    Vytvořit poskytovatele jako Knihovny tříd projektu v Visual Studio protože spustitelný soubor klientské aplikace přidá poskytovatele sestavení jako odkaz na jejich projektu.

Přidat odkaz na službu webové služby

  1. V Průzkumníku, klepněte pravým tlačítkem myši LinqToTerraServerProvider projektu a klepněte na Přidat odkaz na službu.

    Přidat odkaz na službu dialogové okno.

  2. V adresa zadejte http://terraserver.microsoft.com/TerraService2.asmx.

  3. V obor názvů zadejte TerraServerReference a potom klepněte na tlačítko OK.

    TerraServer-USA webové služby je přidána jako odkaz na službu tak, aby aplikace mohou komunikovat prostřednictvím webové služby Windows Communication Foundation (WCF).Přidat odkaz na službu do projektu Visual Studio generuje app.config soubor, který obsahuje proxy a koncový bod pro webové služby.Další informace naleznete v tématu Služby Windows Communication Foundation a datové služby WCF v sadě Visual Studio.

Nyní máte projekt, který obsahuje soubor s názvem app.config, soubor s názvem QueryableTerraServerData.cs (nebo QueryableTerraServerData.vb) a odkaz na službu s názvem TerraServerReference.

Implementace rozhraní nezbytná

Vytvoření LINQ zprostředkovatel musí implementovat minimálně IQueryable<T> a IQueryProvider rozhraní. IQueryable<T>a IQueryProvider jsou odvozeny z jiné požadované rozhraní; proto implementací těchto dvou rozhraní také implementujete rozhraní, které jsou požadovány pro LINQ zprostředkovatele.

Pokud chcete podporují řazení operátory dotazů, jako například OrderBy a ThenBy, musíte také implementovat IOrderedQueryable<T> rozhraní.Protože IOrderedQueryable<T> je odvozen z IQueryable<T>, obě tato rozhraní můžete implementovat do jednoho typu, který je tento zprostředkovatel nepodporuje.

Provádění System.Linq.IQueryable'1 a System.Linq.IOrderedQueryable'1

  • V souboru QueryableTerraServerData.cs (nebo QueryableTerraServerData.vb), přidejte následující kód.

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

    IOrderedQueryable<T> Provedení QueryableTerraServerData třída implementuje tři vlastnosti deklarované v IQueryable a dvě metody výčtu deklarována v IEnumerable a IEnumerable<T>.

    Tato třída má dva konstruktory.První konstruktor je volána z klientské aplikace vytvořit objekt zapisovat LINQ proti dotazu.Druhý konstruktor se nazývá interní zprostředkovatele knihovny kódu v IQueryProvider provedení.

    Když GetEnumerator je volána metoda objektu typu QueryableTerraServerDataa je proveden dotaz, který představuje výsledky dotazu jsou vyjmenovány.

    Tento kód kromě názvu třídy, je specifické pro tento Web TerraServer-USA poskytovatel metadat.Proto je lze znovu použít pro všechny LINQ zprostředkovatele.

K provedení System.Linq.IQueryProvider

  • Přidat TerraServerQueryProvider třídy do projektu.

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

    Kód poskytovatele dotaz do této třídy implementuje čtyři metody, které jsou nutné k provedení IQueryProvider rozhraní.Dva CreateQuery metody vytvořit dotazy, které jsou přidruženy ke zdroji dat.Dva Execute metod odeslání takové dotazy mají být provedeny.

    Neobecnou CreateQuery metoda používá reflexi k získání typ elementu sekvence, který vytvoří dotaz vrací, pokud byl proveden.Poté použije Activator k vytvoření nové třídy QueryableTerraServerData instance, která je vytvořena s typ elementu jako obecný typ argumentu.Výsledek volání není obecný CreateQuery metoda je stejná jako obecného CreateQuery kdyby byla volána metoda správný typ argumentu.

    Většina logiku pro provedení dotazu je zpracována do jiné třídy, které přidáte později.Tato funkce je zpracovat jinde, protože je konkrétní zdroj dat je dotazovaný, že kód v této třídě je obecný žádné LINQ zprostředkovatele.Tento kód lze použít pro různé zprostředkovatele, pravděpodobně změnit název třídy a název kontextu typ dotazu, který je odkazován v dvě metody.

Přidání vlastní typ představující Data výsledků

Typ .NET představující data získaná z webové služby budete potřebovat.Tento typ se používá v klientovi LINQ dotaz definujte výsledky požaduje.Následující procedura vytvoří takového typu. Tento typ s názvem Place, obsahuje informace o jedné zeměpisné umístění, například město, park nebo jezero.

Tento kód obsahuje také typ výčtu, s názvem PlaceType, který definuje různé typy zeměpisné umístění a používá Place třídy.

Chcete-li vytvořit vlastní výsledek typu

  • Přidat Place třídy a PlaceType výčtu do projektu.

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

    Konstruktor pro Place typu zjednodušuje vytváření objektu výsledek z typu, který je vrácený službou WWW.Zatímco může poskytovatel vrátit výsledek typu definovaného rozhraní API webové služby přímo, bude vyžadovat klientské aplikace přidat odkaz na webovou službu.Vytvořením nového typu jako součást zprostředkovatele knihovny nemá klient vědět o typech a metodách, které zpřístupní webové služby.

Přidání funkcí získat Data ze zdroje dat.

Tato implementace zprostředkovatele předpokládá, že vnitřní volání Queryable.Where obsahuje informace o umístění pomocí dotazu webové služby.Nejvnitřnějším Queryable.Where volání je where klauzule (Where klauzule v Visual Basic) nebo Queryable.Where volání metody, které vyvolá nejprve v LINQ dotazu nebo jeden nejblíže "dole" stromu výraz, který představuje dotaz.

Vytvoření třídy kontextu dotazu

  • Přidat TerraServerQueryContext třídy do projektu.

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

    Tato třída organizuje práci spuštění dotazu.Po vyhledání výrazu, který představuje nejvnitřnějším Queryable.Where volání, tento kód načte lambda výraz, který představuje predikát, který byl předán Queryable.Where.Potom předá predikátu výraz metody částečně vyhodnotit, aby všechny odkazy na místní proměnné jsou přeloženy do hodnoty.Potom zavolá metodu extrahovat požadované umístění z predikátu a volá jinou získat výsledek data z webové služby.

    V dalším kroku zkopíruje tento kód stromu výraz, který představuje LINQ dotazu a umožňuje jednu změnu na strom výrazů.Kód podtřídy návštěvník stromu výraz používá nahradit zdroj dat, který je použit operátor volání vnitřní dotaz na konkrétní seznam Place objekty, které byly získány z webové služby.

    Před seznam Place objektů je vložen do stromu výraz, jeho typ je změněn z IEnumerable na IQueryable voláním AsQueryable.Změna tohoto typu je nezbytné, protože při přepsána strom výrazů je uzel, který představuje metodu volání metody operátor dotazu vnitřní rekonstruována.Uzel je rekonstruována, protože jeden z argumentů (který je zdroj dat, který je použit k).Call(Expression, MethodInfo, IEnumerable<Expression>) Metodu, která slouží k rekonstrukci uzlu, vyvolá výjimku, pokud některý argument není přiřadit odpovídající parametr metody, které budou předány.V tomto případě IEnumerable seznam Place nebylo možné přiřadit do objektů IQueryable parametru Queryable.Where.Proto je jeho typ změněn na IQueryable.

    Změnu typu na IQueryable, kolekce také získává IQueryProvider členský přístup Provider vlastnost, která lze vytvářet a spouštět dotazy.Dynamický typ IQueryable°Place je kolekce EnumerableQuery, což je typ, který je interní System.Linq API.Dotaz zprostředkovatele, který je spojen s tímto typem provede nahrazením dotazy Queryable operátor standardního dotazu volá ekvivalentem Enumerable operátory, tak to skutečně stane dotaz LINQ objekty dotazu.

    Konečné kód TerraServerQueryContext dvou metod třídy volá na IQueryable seznam Place objekty.Volá CreateQuery Pokud klient dotaz vrací vyčíslitelné výsledky nebo Execute -vyčíslitelné výsledek vrátí dotaz klienta.

    Kód v této třídě je velmi specifické pro tohoto zprostředkovatele TerraServer-USA.Proto je zapouzdřen v TerraServerQueryContext místo bude vložen přímo do více obecné třídy IQueryProvider provedení.

Zprostředkovatel vytváříte vyžaduje informace v Queryable.Where predikát dotazu webové služby. proto používá LINQ objekty práce provádění LINQ dotazu pomocí vnitřního EnumerableQuery typu.Alternativní způsob použití LINQ objekty spustit dotaz, je klient obtékání část dotazu prováděnou LINQ na objekty v LINQ objekty dotazu.To lze provést voláním AsEnumerable<TSource> na zbytek dotazu, který je součástí dotazu, které poskytovatel požaduje pro své specifické účely.Výhodou tohoto druhu implementace je rozdělení práce mezi vlastního poskytovatele a LINQ objektů je více transparentní.

[!POZNÁMKA]

Poskytovatel v tomto tématu je jednoduchého zprostředkovatele, který má vlastní podporu minimální dotazu.Proto závisí na silně LINQ objekty spouštět dotazy.Komplexní LINQ zprostředkovatele, jako Technologie LINQ to SQL mohou podporovat celý dotaz bez předání jakékoliv práce na LINQ objekty.

Vytvoření třídy získat data z webové služby

  • Přidat WebServiceHelper třídy (nebo v modulu Visual Basic) do projektu.

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

    Tato třída obsahuje funkci, která získává data z webové služby.Tento kód používá typ s názvem TerraServiceSoapClient, která je automaticky generována projekt Windows Communication Foundation (WCF), volání metody webové služby GetPlaceList.Potom každý výsledek přeložit z návratový typ metody webové služby .NET typ, který definuje zprostředkovatele dat.

    Tento kód obsahuje dvě kontroly, které zlepšují využitelnost zprostředkovatele knihovny.První kontrola omezuje maximální doba čekání na odpověď klienta aplikace celkový počet volání webové služby na dotaz na pět.Pro každé umístění zadané v dotazu klienta je generována jeden požadavek webové služby.Zprostředkovatel proto vyvolá výjimku, pokud dotaz obsahuje více než pět míst.

    Druhé políčko určuje, zda je rovno maximální počet výsledků, které můžete vrátit počet výsledků vrácených z webové služby.Pokud číslo maximální počet výsledků, je pravděpodobné, že výsledky z webové služby jsou zkráceny.Zprostředkovatel klienta nevrací neúplný seznam, vyvolá výjimku.

Přidání třídy návštěvník stromu výrazů

Vytvoření návštěvníka, který vyhledá nejvnitřnějším kde volání metody výraz

  1. Přidat InnermostWhereFinder třídu, která dědí ExpressionVisitor třídy do projektu.

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

    Tato třída dědí třídu základní výraz stromu návštěvník provést vyhledání konkrétní výraz funkce.Třída základní výraz stromu návštěvníka je navržena pro zděděné a specializované pro konkrétní úkol, který zahrnuje procházení stromu výrazů.Odvozená třída přepíše VisitMethodCall k hledání výraz, který představuje vnitřní volání metody Where ve stromu výraz, který představuje dotaz klienta.Tento vnitřní výraz je výraz, který extrahuje zprostředkovatele hledání umístění z.

  2. Přidat using směrnic (Imports příkazy v jazyce Visual Basic) souboru pro následující obory názvů: System.Collections.Generic, System.Collections.ObjectModel a System.Linq.Expressions.

Vytvoření návštěvníka, který extrahuje data dotazu webové služby

  • Přidat LocationFinder třídy do projektu.

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

    Tato třída je použita extrahovat informace o umístění z predikát, který předá klient Queryable.Where.Je odvozen z ExpressionVisitor třída a přepisuje pouze VisitBinary metoda.

    ExpressionVisitor Třídy odešle binární výrazy, například rovnost výrazy jako place.Name == "Seattle" (place.Name = "Seattle" v Visual Basic), do VisitBinary metoda.V toto přepsání VisitBinary metoda, pokud je výraz odpovídá vzoru rovnosti výrazu, který může poskytovat informace o umístění, informace jsou extrahovány a uloženy v seznamu umístění.

    Tato třída používá návštěvník stromu výraz najít informace o umístění stromu výraz, protože návštěvník je určen pro procházení a zkoumání stromy výrazů.Výsledný kód je neater a méně náchylný než, pokud byla provedena bez použití návštěvník.

    V této fázi návodu poskytovatel podporuje pouze omezené způsoby poskytovat informace o umístění v dotazu.Později v tématu přidáte funkci, která umožní další způsoby poskytovat informace o umístění.

Vytvoření návštěvníka, který upravuje strom výrazů

  • Přidat ExpressionTreeModifier třídy do projektu.

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

    Tato třída je odvozena z ExpressionVisitor třída a přepíše VisitConstant metoda.Tato metoda nahradí objekt, který je použit operátor volání nejvnitřnější standardního dotazu s konkrétní seznam Place objekty.

    Tato třída modifikátor stromu výraz používá návštěvník stromu výraz, protože návštěvník je určen k procházení zkoumat a kopírovat stromy výrazů.Děděním ze třídy základní výraz stromu návštěvníka této třídy vyžaduje minimální kód vykonávat svou funkci.

Přidání vyhodnocení výrazu

Predikát, který je předán Queryable.Where metoda v dotazu klienta může obsahovat sub-expressions, které nezávisí na parametru lambda výraz.Tyto izolované sub-expressions můžete a okamžitě by měl být vyhodnocen.Odkazy na místní proměnné nebo proměnné členů, které je nutné přeložit do hodnoty, může být.

Další třídy zpřístupní metodu, PartialEval(Expression), která určuje, které, z sub-trees ve výrazu lze vyhodnotit okamžitě.Potom vyhodnotí výrazy vytvořením lambda výraz, kompilace a delegát vrácené vyvolání.Nakonec jej nahradí podstromu nový uzel, který představuje hodnotu konstanty.Se nazývá dílčí hodnocení.

Přidání třídy provádět částečné vyhodnocení stromu výrazů

  • Přidat Evaluator třídy do projektu.

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

Přidání pomocné třídy

Tato část obsahuje kód pro tři pomocné třídy zprostředkovatele.

Přidání pomocné třídy, používaný k provádění System.Linq.IQueryProvider

  • Přidat TypeSystem třídy (nebo v modulu Visual Basic) do projektu.

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

    IQueryProvider Tato pomocná třída používá implementaci, který jste přidali dříve.

    TypeSystem.GetElementTypezískat argument obecný typ používá reflexi IEnumerable<T> (IEnumerable(Of T) v Visual Basic) kolekce.Tato metoda je volána z neobecnou CreateQuery metoda v implementaci poskytovatele dotazu zadat typ prvek kolekce výsledek dotazu.

    Tato pomocná třída není specifické pro tento Web TerraServer-USA poskytovatel metadat.Proto je lze znovu použít pro všechny LINQ zprostředkovatele.

Vytvořte třídu pomocné stromu výraz

  • Přidat ExpressionTreeHelpers třídy do projektu.

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

    Tato třída obsahuje metody, které lze použít k určení informací o a extrahovat data z určité typy stromy výrazů.V tomto poskytovateli tyto metody používají LocationFinder třídy ze stromu výraz, který představuje dotaz extrahovat informace o umístění.

Chcete-li přidat výjimku typu neplatná dotazů

  • Přidat InvalidQueryException třídy do projektu.

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

    Tato třída definuje Exception typ poskytovatele lze vyvolat při nerozumí LINQ dotaz od klienta.Definováním tento typ výjimky neplatný dotaz poskytovatele lze vyvolat zvláštní výjimku než právě Exception z různých míst v kódu.

Nyní jste přidali všechny kusy požadované kompilovat zprostředkovatele.Sestavení LinqToTerraServerProvider projektu a ověřte, zda jsou žádné chyby při kompilaci.

Testování zprostředkovatele LINQ

Můžete otestovat vaše LINQ obsahuje zprostředkovatele vytvořením aplikace klient LINQ dotaz na zdroj dat.

Vytvoření aplikace klient k testování zprostředkovatele

  1. Přidat nový Aplikace konzoly projekt do řešení s názvem ClientApp.

  2. V novém projektu přidejte odkaz na sestavení poskytovatele.

  3. Přetáhněte app.config soubor z projektu zprostředkovatele do projektu klienta.(Tento soubor je nezbytný pro komunikaci s webovou službou).

    [!POZNÁMKA]

    V Visual Basic, máte klepněte Zobrazit všechny soubory tlačítko app.config v souboru Průzkumníku.

  4. Přidejte následující using příkazy (Imports prohlášení v Visual Basic) se Program.cs (nebo Module1.vb v Visual Basic) souboru:

    using System;
    using System.Linq;
    using LinqToTerraServerProvider;
    
    Imports LinqToTerraServerProvider
    
  5. V Main metoda v souboru Program.cs (nebo Module1.vb v Visual Basic), vložte následující kód:

    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
    

    Tento kód vytvoří novou instanci IQueryable<T> typu, který je definován ve vašem zprostředkovateli a dotazy, které objekt pomocí LINQ.Dotazu určuje umístění k získání dat pomocí výrazu rovnosti v.Protože zdroje dat implementuje IQueryable, kompilátor převádí syntaxe výrazu dotazu na volání standardních operátorů pro dotazování definované v Queryable.Vnitřně sestavit tyto metody standardních dotazovacích operátorů strom výrazů a volání Execute nebo CreateQuery metod, které jsou implementovány jako část vaší IQueryProvider provedení.

  6. Sestavení ClientApp.

  7. Nastavte klientské aplikace jako projekt "Spouštěcí" pro vaše řešení.V Průzkumníku, klepněte pravým tlačítkem myši ClientApp projektu a vyberte nastavit jako projekt při spuštění.

  8. Program spustit a zobrazit výsledky.By měla být přibližně tří výsledků.

Přidání složitějších dotazů

Poskytovatel, který máte k tomuto bodu umožňuje velmi omezenou klienti určit informace o umístění v LINQ dotazu.Konkrétně je poskytovatel pouze možnost získat informace o umístění z rovnosti výrazy jako Place.Name == "Seattle" nebo Place.State == "Alaska" (Place.Name = "Seattle" nebo Place.State = "Alaska" v Visual Basic).

Následující procedura ukazuje, jak přidat podporu pro další způsob zadání informací o umístění.Po přidání tohoto kódu poskytovatele bude možné extrahovat informace o umístění z výrazů volání metody jako place.Name.StartsWith("Seat").

Přidání podpory predikátů obsahujících String.StartsWith

  1. V LinqToTerraServerProvider projektu, přidejte VisitMethodCall metoda LocationFinder definici třídy.

    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);
    }
    
  2. Překompilovat LinqToTerraServerProvider projektu.

  3. Pokud chcete vyzkoušet nové funkce poskytovatele, otevřete soubor Program.cs (nebo Module1.vb v Visual Basic) v ClientApp projektu.Nahraďte kód ve Main metodu následujícím kódem:

    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. Program spustit a zobrazit výsledky.By měla být přibližně 29 výsledků.

Následující procedura ukazuje, jak přidat poskytovatele informace o umístění zadat pomocí dvou dalších metod specificky dotaz klienta povolit funkce Enumerable.Contains a List<T>.Contains.Po přidání tohoto kódu poskytovatele bude možné extrahovat informace o umístění z metody volání výrazů v dotazu klienta jako placeList.Contains(place.Name), kde placeList kolekce je konkrétní seznam dodané klientem.Výhodou umožní klientům použít Contains metoda je, že mohou zadat libovolný počet umístění pouhým přidáním do placeList.Počet míst pro různé syntaxe dotazu nezmění.

Přidání podpory pro dotazy, které mají v jejich klauzuli where obsahuje metodu

  1. V LinqToTerraServerProvider v projektu LocationFinder definici třídy, nahraďte VisitMethodCall metodu následujícím kódem:

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

    Tato metoda přidá každý řetězec v kolekci, Contains je použita k seznamu umístění dotazu na Web service s.Metoda s názvem Contains je definován v obou Enumerable a List<T>.Proto VisitMethodCall metoda musí zkontrolovat pro oba tyto typy deklarací.Enumerable.Containsje definována jako rozšiřující metoda; Proto je použita pro kolekce je skutečně první argument metody.List.Containsje definována jako metodu instance; Proto je kolekce, je použita pro přijímající objekt metody.

  2. Překompilovat LinqToTerraServerProvider projektu.

  3. Pokud chcete vyzkoušet nové funkce poskytovatele, otevřete soubor Program.cs (nebo Module1.vb v Visual Basic) v ClientApp projektu.Nahraďte kód ve Main metodu následujícím kódem:

    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. Program spustit a zobrazit výsledky.By měla být přibližně 5 výsledky.

Další kroky

Toto téma návod ukázal, jak vytvořit LINQ zprostředkovatele pro jednu metodu webové služby.Jestliže chcete vykonávat další rozvoj LINQ zprostředkovatel zvažte tyto možnosti:

  • Povolit LINQ zprostředkovatele pro zpracování jiných způsobů určení umístění v dotazu klienta.

  • Prozkoumat metody, TerraServer-USA webové služby vystavuje a vytvořit LINQ poskytovatele, rozhraní s jednou z těchto metod.

  • Najít jinou webovou službu zájem a vytvořit LINQ zprostředkovatele pro něj.

  • Vytvořit LINQ zprostředkovatele pro zdroj dat než webové služby.

Další informace o tom, jak vytvořit vlastního poskytovatele LINQ naleznete LINQ: vytváření poskytovatele IQueryable na blogy MSDN.

Viz také

Úkoly

Vzorky LINQ

Jak: Upravit výraz stromů (C# a Visual Basic)

Referenční dokumentace

IQueryable<T>

IOrderedQueryable<T>

Koncepty

Povolení LINQ dotaz na zdroj dat

Služby Windows Communication Foundation a datové služby WCF v sadě Visual Studio