Instruktaż: Tworzenie dostawcą IQueryable LINQ
W tym temacie zaawansowane instrukcje krok po kroku dotyczące tworzenia niestandardowego LINQ dostawcy. Po zakończeniu można używać dostawcy utworzyć napisać LINQ kwerend usługi sieci Web Szukacz USA.
Usługa sieci Web Szukacz USA zapewnia interfejs do bazy danych zdjęć lotniczych w Stanach Zjednoczonych.Udostępnia on również metoda, która zwraca informacje o lokalizacjach w Stanach Zjednoczonych, danego fragmentu lub całej nazwy lokalizacji.Ta metoda, która nosi nazwę GetPlaceList, jest metoda, Twój LINQ dostawca będzie połączenie. Dostawca będzie używać Windows Communication Foundation (WCF) do komunikowania się z usługą sieci Web.Aby uzyskać więcej informacji na temat usługi sieci Web Szukacz-USA, zobacz Przegląd usług sieci Web USA Szukacz.
Ten dostawca jest stosunkowo proste IQueryable dostawcy.Oczekuje określonych informacji w kwerendach, które obsługuje i posiada system typu zamkniętego, narażając jednego typu do reprezentacji danych wynik.Ten dostawca analizuje tylko jeden typ wyrażenie wywołania metody drzewa wyrażenie, który reprezentuje kwerendę, która jest najbardziej wewnętrzne wywołanie Where.Wyodrębnia dane, które musi on mieć aby przesyłać kwerendy do usługi sieci Web z tego wyrażenia.Następnie wywołuje usługa sieci Web i wstawia zwrócone dane do drzewa wyrażenie miejsce początkowe IQueryable źródło danych.Reszta wykonanie kwerendy jest obsługiwane przez Enumerable implementacje operatorów standardowej kwerendy.
Przykłady kodu w tym temacie znajdują się w C# i Visual Basic.
W tym instruktażu przedstawiono następujące zadania:
Tworzenie projektu w Visual Studio.
Implementowanie interfejsów, które są wymagane dla IQueryableLINQ dostawca: IQueryable<T>, IOrderedQueryable<T>, i IQueryProvider.
Dodawanie niestandardowego typu .NET do reprezentowania danych z usługi sieci Web.
Tworzenie klasy kontekście kwerendy i klasy, która pobiera dane z usługi sieci Web.
Tworzenie podklasy odwiedzający drzewa wyrażenie, które wyszukuje wyrażenie, które reprezentuje wywołanie znajdujące się najniżej Queryable.Where metoda.
Tworzenie podklasy odwiedzający drzewa wyrażenie, które wyodrębnia informacje z LINQ kwerendy w żądanie usługi sieci Web.
Tworzenie podklasy odwiedzający drzewa wyrażenie, które modyfikuje drzewa wyrażenie, które reprezentuje pełną LINQ kwerendy.
Za pomocą klasy modułu szacującego częściowo oceny drzewa wyrażenie.Ten krok jest konieczny, ponieważ tłumaczy on wszystkie odniesienia zmiennych lokalnych w LINQ kwerendy do wartości.
Tworzenie helper drzewa wyrażenie klasy i nową klasę wyjątków.
Badania LINQ dostawcy z aplikacji klienckiej, która zawiera LINQ kwerendy.
Dodanie bardziej złożonych funkcji kwerendy do LINQ dostawcy.
[!UWAGA]
Dostawca LINQ, który tworzy w tym instruktażu jest dostępny jako próbka.Aby uzyskać więcej informacji Próbki LINQ.
Wymagania wstępne
W tym instruktażu wymaga funkcji, które są wprowadzane w Visual Studio 2008.
[!UWAGA]
Na danym komputerze mogą być używane inne nazwy lub lokalizacje pewnych elementów interfejsu użytkownika programu Visual Studio, które są używane w poniższych instrukcjach. Używana wersja programu Visual Studio oraz jej ustawienia określają te elementy. Aby uzyskać więcej informacji, zobacz Visual Studio, ustawienia.
Tworzenie projektu
Tworzenie projektu programu Visual Studio
W Visual Studio, Utwórz nową Biblioteka klas aplikacji. Nazwa projektu LinqToTerraServerProvider.
W Solution Explorer, wybierz opcję Class1.cs (lub Class1.vb) plik i zmień jego nazwę na QueryableTerraServerData.cs (lub QueryableTerraServerData.vb).W oknie dialogowym, które się pojawi, kliknij przycisk Tak zmienić wszystkie odwołania do elementu kodu.
Utwórz dostawcę jako Biblioteka klas projektu w Visual Studio , ponieważ klient pliku wykonywalnego aplikacji spowoduje dodanie zestawu dostawcy jako odniesienie do ich projektu.
Aby dodać odwołanie usługi Usługa sieci Web
W Solution Explorer, kliknij prawym przyciskiem myszy LinqToTerraServerProvider projektu, a następnie kliknij przycisk Dodać odwołanie do usługi.
Dodać odwołanie do usługi zostanie otwarte okno dialogowe.
W adres wpisz http://terraserver.microsoft.com/TerraService2.asmx.
W nazw wpisz TerraServerReference , a następnie kliknij przycisk OK.
Usługa sieci Web Szukacz USA dodaje się jako odwołanie do usługi, dzięki czemu aplikacja może komunikować się z usługą sieci Web na Windows Communication Foundation (WCF).Przez dodanie usługi odwołanie do projektu, Visual Studio generuje app.config pliku, który zawiera serwer proxy i punktu końcowego dla usługi sieci Web.Aby uzyskać więcej informacji, zobacz Usług Windows Communication Foundation i usług WCF w danych w programie Visual Studio.
Teraz masz projekt, który zawiera plik o nazwie app.config, plik o nazwie QueryableTerraServerData.cs (lub QueryableTerraServerData.vb) i odwołania usługi o nazwie TerraServerReference.
Interfejsy niezbędne do wykonania
Aby utworzyć LINQ dostawca, jako minimum, należy zaimplementować IQueryable<T> i IQueryProvider interfejsów. IQueryable<T>i IQueryProvider są uzyskiwane z innych wymaganych interfejsów; w związku z tym, poprzez realizację tych dwóch interfejsów, również realizują inne interfejsy, które są wymagane dla LINQ dostawcy.
Jeśli chcesz wsparcia sortowania operatorów kwerendy, takie jak OrderBy i ThenBy, musi też implementować IOrderedQueryable<T> interfejsu.Bo IOrderedQueryable<T> wynika z IQueryable<T>, można zaimplementować zarówno tych interfejsów w jednym typie, czego nie tego dostawcy.
Aby zaimplementować System.Linq.IQueryable'1 i System.Linq.IOrderedQueryable'1
W pliku QueryableTerraServerData.cs (lub QueryableTerraServerData.vb), Dodaj następujący kod.
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> Wprowadzania w życie przez QueryableTerraServerData klasy implementuje trzy właściwości zadeklarowane w IQueryable i dwie metody wyliczania zadeklarowane w IEnumerable i IEnumerable<T>.
Ta klasa ma dwa konstruktorów.Konstruktor pierwszy nazywa się od aplikacji klienta do utworzenia obiektu, aby napisać LINQ kwerenda wykonana.Drugi konstruktor jest nazywany wewnętrznego do biblioteki dostawca przez kod w IQueryProvider realizacji.
Gdy GetEnumerator wywoływana jest metoda obiektu typu QueryableTerraServerData, jest wykonywana kwerenda, która reprezentuje i wyniki kwerendy są wyliczane.
Ten kod, oprócz nazwy klasy, nie dotyczy to usługodawca sieci Web Szukacz USA.W związku z tym, może zostać użyty do dowolnego LINQ dostawcy.
Aby zaimplementować System.Linq.IQueryProvider
Dodaj TerraServerQueryProvider klasy 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); } } }
Kod dostawcy kwerendy tej klasy implementuje cztery metody, które są wymagane do wykonania IQueryProvider interfejsu.Dwa CreateQuery metody tworzenia kwerend, które są skojarzone ze źródłem danych.Dwa Execute metody wysyłania kwerend takiej ma być wykonane.
Niepodstawowego CreateQuery metoda wykorzystuje odbicie uzyskać typ element sekwencji, że kwerenda tworzy wróci, jeśli zostało wykonane.Następnie Activator klasy, aby utworzyć nowy QueryableTerraServerData instancji, która jest skonstruowana z typu elementu jako argument typu rodzajowego.Wynik wywołania niepodstawowego CreateQuery metoda jest taka sama, jak typowa CreateQuery metoda miał została wywołana z argumentem poprawnego typu.
Większość logiki wykonanie kwerendy jest obsługiwane w innej klasy, który należy dodać później.Ta funkcjonalność jest obsługiwane, gdzie indziej bo to specyficzne dla źródła danych, którego dotyczy kwerenda, kod tej klasy jest rodzajowy dowolnej LINQ dostawcy.Aby użyć tego kodu dla innego dostawcy, trzeba zmienić nazwę klasy i nazwy typ kontekstu kwerendy, która odwołuje się do dwóch metod.
Dodawanie niestandardowego typu do reprezentowania danych wyników
Trzeba będzie typu .NET do reprezentowania danych uzyskanych z usługi sieci Web.Ten typ będzie używany w kliencie LINQ kwerendę, aby określić wyniki chce.Poniższa procedura tworzy tego typu. Tego typu o nazwie Place, zawiera informacje o jednej lokalizacji geograficznych miasta, parku lub jeziora.
Ten kod zawiera również typem wyliczenia o nazwie PlaceType, która definiuje różne rodzaje położenia geograficznego i jest używany w Place klasy.
Aby utworzyć typ niestandardowy wynik
Dodaj Place klasy i PlaceType wyliczenie 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 dla Place typu upraszcza tworzenie obiektu wyników z typu, który jest zwracany przez usługę sieci Web.Podczas gdy dostawca może zwrócić wynik zdefiniowanych przez interfejs API usługi sieci Web bezpośrednio, będzie to wymagać aplikacje klienckie, aby dodać odwołanie do usługi sieci Web.Tworząc nowy typ jako część biblioteki dostawca, klient nie ma wiedzieć na temat typów i metod, które udostępnia usługi sieci Web.
Dodawanie funkcji do pobierania danych ze źródła danych
Ta implementacja dostawcy zakłada się, że najbardziej wewnętrzne wywołanie Queryable.Where zawiera informacje o lokalizacji, aby użyć kwerendy usługi sieci Web.Pola wewnętrznego Queryable.Where jest where klauzula (Where w klauzuli Visual Basic) lub Queryable.Where wywołanie metody, które występuje po raz pierwszy w LINQ kwerendy lub najbliższy "u dołu" drzewa wyrażenie, które reprezentuje kwerendy.
Aby utworzyć klasę kontekście kwerendy
Dodaj TerraServerQueryContext klasy 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); } } }
Ta klasa służy do organizowania pracy, wykonywanie kwerendy.Po znalezieniu wyrażenie, które reprezentuje skrajnym polu Queryable.Where rozmowy, ten kod pobiera Wyrażenie lambda, który reprezentuje predykatu, który został przekazany do Queryable.Where.Następnie przechodzi predykatu wyrażenie do metody częściowo oceniane, tak, że wszystkie odwołania do zmiennych lokalnych są tłumaczone na wartości.Następnie wywołuje metodę wyodrębnić żądanej lokalizacji z predykat i wywołuje metodę innym celu uzyskania danych wyników z usługi sieci Web.
W następnym kroku ten kod kopiuje drzewa wyrażenie, które reprezentuje LINQ kwerendy i sprawia, że jedna modyfikacja drzewa wyrażenie.Kod używa podklasy odwiedzający drzewa wyrażenie zastąpić wywołanie operatora wewnętrzne kwerenda jest stosowany do źródło danych z listy konkretnych Place obiektów, które zostały uzyskane z usługi sieci Web.
Przed wykaz Place obiektów jest wstawiany do drzewa wyrażenie, jego typ jest zmieniana z IEnumerable do IQueryable przez wywołanie AsQueryable.Zmiana tego typu jest konieczne, ponieważ gdy drzewo wyrażenie jest przebudowywany, węzeł, który reprezentuje wywołanie metody Metoda operator wewnętrzne kwerenda jest rekonstruowany.Węzeł jest rekonstruowany, ponieważ zmienił się jeden z argumentów (czyli źródła danych, który jest stosowany do).Call(Expression, MethodInfo, IEnumerable<Expression>) Metoda, która jest używana do rekonstruowania węzła, będzie Zgłoś wyjątek, jeśli którykolwiek z argumentów nie jest możliwa do przypisania do odpowiadającego mu parametru metody, która będzie przekazywana do.W tym przypadku IEnumerable lista Place obiektów, nie będzie można przypisać do IQueryable parametr Queryable.Where.W związku z tym, jego typ jest zmieniany na IQueryable.
Przez zmianę jej typu, aby IQueryable, Kolekcja również uzyskuje IQueryProvider Członkowskich dostępne przez Provider właściwość, że można utworzyć lub wykonać kwerendy.Dynamiczne typ IQueryable°Place jest kolekcja EnumerableQuery, który jest typem, który jest wewnętrznym System.Linq interfejsu API.Dostawca kwerendy, który jest skojarzony z tym typem wykonuje kwerendy, zastępując Queryable kwerendy standardowe operatora połączeń z odpowiednik Enumerable operatorów, tak że skutecznie kwerenda staje się LINQ do obiektów kwerendy.
Ostateczny kod w TerraServerQueryContext klasy zwraca jedną z dwóch metod na IQueryable lista Place obiektów.Wywołuje CreateQuery , jeśli klient zwraca wyniki agreguje ustalony, lub Execute Jeśli kwerendy klient zwraca wynik agreguje ustalony.
Kod tej klasy jest bardzo specyficzne dla tego dostawcy Szukacz-USA.W związku z tym, jest hermetyzowany w TerraServerQueryContext klasy, a nie bezpośrednio do bardziej ogólnych dodaje się IQueryProvider realizacji.
Dostawca tworzenia wymaga tylko informacje w Queryable.Where Predykat kwerendy usługi sieci Web. dlatego używa LINQ do obiektów do prac wykonywanych z LINQ kwerendy przy użyciu wewnętrznego EnumerableQuery typu.Alternatywny sposób używania LINQ do obiektów, aby wykonać kwerendy ma zawinąć częścią kwerendy mają być zrealizowane przez klienta LINQ do obiektów w LINQ do obiektów kwerendy.Można to osiągnąć przez wywołanie AsEnumerable<TSource> w dalszej części kwerendy, która jest częścią kwerendy, która wymaga szczególnych celach.Zaletą tego rodzaju realizacji jest to, że podział pracy między niestandardowego dostawcy i LINQ do obiektów jest bardziej przejrzysty.
[!UWAGA]
Dostawca, przedstawione w tym temacie jest proste dostawca, który obsługuje kwerendy minimalne własnych.W związku z tym, to opiera się na LINQ do obiektów w celu wykonania kwerendy.Kompleks LINQ dostawcy, takich jak LINQ to SQL może obsługiwać całego kwerendy bez przekazywanie wszelkich prac do LINQ do obiektów.
Aby utworzyć klasę, aby uzyskać dane z usługi sieci Web
Dodaj WebServiceHelper klasy (lub moduł w 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; } } } }
Ta klasa zawiera funkcje, które pobiera dane z usługi sieci Web.Ten kod zawiera typ o nazwie TerraServiceSoapClient, który jest generowany automatycznie projektu przez Windows Communication Foundation (WCF), wywołanie metody usługi sieci Web GetPlaceList.Następnie każdy wynik jest tłumaczony od zwrotu typu metody usługi sieci Web typu .NET, definiujący przez dostawcę danych.
Ten kod zawiera dwie kontrole, które zwiększają użyteczność biblioteki dostawca.Pierwsze sprawdzenie ogranicza maksymalny czas, aplikacja kliencka czeka na odpowiedź przez ograniczenie całkowita liczba połączeń, które są wykonane z usługą sieci Web, na kwerendę do pięciu.Dla każdej lokalizacji, określonej w kwerendzie klienta jest generowany jeden żądanie usługi sieci Web.W związku z tym dostawca zgłasza wyjątek, jeśli kwerenda zawiera więcej niż pięciu miejscach.
Drugi wyboru określa, czy liczba wyników zwróconych przez usługę sieci Web jest równa maksymalną liczbę wyników, które mogą być zwracane.Jeśli liczba wyników jest maksymalna liczba, jest prawdopodobne, że wyniki z usługi sieci Web są obcinane.Zamiast niepełną listę do klienta, dostawca zgłasza wyjątek.
Dodawanie klas odwiedzający drzewa wyrażenie
Aby utworzyć użytkownika, że znajdzie skrajnym polu, gdzie wyrażenia przez wywołanie metody
Dodaj InnermostWhereFinder klasy, która dziedziczy ExpressionVisitor klasy 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; } } }
Ta klasa dziedziczy klasy podstawowej wyrażenie drzewo odwiedzających do wykonywania funkcji znajdowania określonego wyrażenia.Klasy podstawowej wyrażenie drzewo użytkownik ma na celu być dziedziczone i specjalistyczne dla określonego zadania, który obejmuje przechodzenie do drzewa wyrażenie.Przesłonięcia klasy pochodne VisitMethodCall metoda do poszukiwania wyrażenie, które reprezentuje znajdujące się najniżej wywołanie Where w drzewa wyrażenie, które reprezentuje kwerendy klienta.Wewnętrzne wyrażenie to jest wyrażenie wyodrębniające lokalizacje wyszukiwania od dostawcy.
Dodaj using dyrektyw (Imports instrukcji w języku Visual Basic) do pliku następujące obszary nazw: System.Collections.Generic, System.Collections.ObjectModel i System.Linq.Expressions.
Aby utworzyć użytkownika, który wyodrębnia dane kwerendy usługi sieci Web
Dodaj LocationFinder klasy 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); } } }
Ta klasa jest używana, aby wyodrębnić informacje o lokalizacji z predykatu, który klient przekazuje do Queryable.Where.Wynika to z ExpressionVisitor klasy i przesłonięcia tylko VisitBinary metoda.
ExpressionVisitor Klasy wysyła binarny wyrażeń, takich jak równość wyrażeń, jak place.Name == "Seattle" (place.Name = "Seattle" w Visual Basic), do VisitBinary metoda.W tym przesłanianie VisitBinary metodę, jeśli wyrażenie pasuje do wzorca wyrażenie równości, który może dostarczać informacje o lokalizacji, że informacje jest ekstrahowana i przechowywane w listę lokalizacji.
Klasa ta używa gościem drzewa wyrażenie znaleźć informacje o lokalizacji w drzewie wyrażenie, ponieważ użytkownik jest przeznaczony dla przejeżdżające i analizie wyrażenie drzew.Kod wynikowy jest lepsze i mniej podatne niż jeśli miał została dokonana bez przy użyciu przez osobę odwiedzającą.
Na tym etapie instruktażu dostawca obsługuje tylko ograniczony sposoby dostarczania informacji o lokalizacji, w kwerendzie.W dalszej części tematu należy dodać funkcję umożliwiającą więcej sposobów dostarczania informacji o lokalizacji.
Aby utworzyć użytkownika, który modyfikuje drzewa wyrażenie
Dodaj ExpressionTreeModifier klasy 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; } } }
Ta klasa pochodzi od ExpressionVisitor klasy i przesłonięcia VisitConstant metoda.W tej metodzie zastępuje obiekt, który wywołanie operatora wewnętrzne kwerendy standardowe jest stosowane do betonu wykaz z Place obiektów.
Ta klasa modyfikator drzewa wyrażenie używa odwiedzający drzewa wyrażenie, ponieważ użytkownik ma na celu przy przechodzeniu przez folder, badanie i kopiowania drzewa wyrażenie.Przez wynikających z klasy podstawowej wyrażenie drzewo odwiedzających, ta klasa wymaga minimalny kod do wykonywania jego funkcji.
Dodawanie modułu szacującego wyrażenie
Predykatu, który jest przekazywany do Queryable.Where metoda w kwerendzie klienta mogą zawierać sub-expressions, które nie zależą od parametru Wyrażenie lambda.Te sub-expressions na białym tle mogą i powinny być oceniane natychmiast.Mogą być odwołania do zmiennych lokalnych lub zmienne składowe, które muszą być przetłumaczone na wartości.
Klasy następny opisuje metodę, PartialEval(Expression), który określa, które ewentualnie kontaktach w wyrażeniu można oszacować natychmiast.Tworząc wyrażenie lambda, zestawiania i wywoływanie delegata zwracane są następnie oblicza te wyrażenia.Wreszcie to zastępuje poddrzewa nowego węzła, który reprezentuje wartość stałą.Jest to znane jako częściowej oceny.
Aby dodać klasę do wykonywania oceny częściowe drzewa wyrażenie
Dodaj Evaluator klasy 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; } } } }
Dodawanie klasy pomocy
Ta sekcja zawiera kod na trzy klasy pomocy dla dostawcy.
Aby dodać klasy pomocy, który jest używany przez implementację System.Linq.IQueryProvider
Dodaj TypeSystem klasy (lub moduł w 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 Wykonania, który dodano wcześniej używa tej klasy pomocy.
TypeSystem.GetElementTypeużywa odbicie w celu uzyskania argumentu typu rodzajowego IEnumerable<T> (IEnumerable(Of T) w Visual Basic) kolekcji.Ta metoda jest wywoływana z nie uniwersalne CreateQuery metoda w celu wykonania dostawca kwerendy do dostarczania typu element zbioru wyników kwerendy.
Ta klasa nie jest specyficzne dla tego usługodawca sieci Web Szukacz USA.W związku z tym, może zostać użyty do dowolnego LINQ dostawcy.
Aby utworzyć wyrażenie drzewo klasa
Dodaj ExpressionTreeHelpers klasy 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)); } } }
Ta klasa zawiera metody, które służy do określenia informacji o i wyodrębnienia danych z określonych rodzajów drzew wyrażenie.W tym dostawcą, metody te są używane przez LocationFinder klasy, aby wyodrębnić informacje o lokalizacji z drzewa wyrażenie, które reprezentuje kwerendy.
Aby dodać typ wyjątku nieprawidłowa kwerend
Dodaj InvalidQueryException klasy 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; } } } }
Ta klasa definiuje Exception typu, który dostawca mogą rzucać, gdy nie rozumie LINQ kwerendę od klienta.Definiując tego typu wyjątku kwerendy nieprawidłowy dostawca można rzucić wyjątku określonego w więcej niż tylko Exception z różnych miejsc w kodzie.
Wszystkie elementy, które są wymagane do kompilacji dostawcy zostało dodane.Budowanie LinqToTerraServerProvider projektu i sprawdzić, czy nie ma żadnych błędów kompilacji.
Testowanie dostawca programu LINQ
Można przetestować swój LINQ dostawcy przez tworzenie aplikacji klienta, który zawiera LINQ kwerendy przed źródła danych.
Aby utworzyć aplikację klient do testowania swojego dostawcy
Dodaj nowy Console Application Tworzenie projektu i nadaj mu nazwę ClientApp.
W nowym projekcie należy dodać odwołanie do zestawu dostawcy.
Przeciągnij app.config pliku z Twój projekt do projektu klienta.(Ten plik jest niezbędne do komunikacji z usługą sieci Web).
[!UWAGA]
W Visual Basic, musisz kliknąć Pokaż wszystkie pliki przycisk, aby zobaczyć app.config pliku w Solution Explorer.
Dodaj następujący using instrukcji (Imports instrukcja w Visual Basic) do Program.cs (lub Module1.vb w Visual Basic) pliku:
using System; using System.Linq; using LinqToTerraServerProvider;
Imports LinqToTerraServerProvider
W Main metoda w pliku Program.cs (lub Module1.vb w Visual Basic), wstawić następujący kod:
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
Kod ten tworzy nowe wystąpienie IQueryable<T> typu zdefiniowanego w dostawcy i kwerend, które obiekt za pomocą LINQ.Kwerendy określa lokalizację uzyskanie danych przy użyciu wyrażenia równości.Ponieważ źródło danych implementuje IQueryable, kompilator przekłada składni wyrażenia kwerendy wywołania operatorów kwerendy standardowe zdefiniowane w Queryable.Wewnętrznie, metody te kwerendy standardowe operator budowy drzewa wyrażenie i wywołanie Execute lub CreateQuery metod, które są realizowane w ramach swojej IQueryProvider realizacji.
Budowanie ClientApp.
Ustawianie aplikacji klienta jako projekt "Uruchamianie" dla rozwiązania.W Solution Explorer, kliknij prawym przyciskiem myszy ClientApp projekt i zaznacz jako projekt uruchamiania.
Uruchom program i wyświetlić wyniki.Powinno być około trzech wyników.
Dodanie możliwości bardziej złożone kwerendy
Dostawca, który ma do tego punktu zapewnia bardzo ograniczony sposób dla klientów, aby określić informacje o lokalizacji, w LINQ kwerendy.W szczególności, dostawca jest tylko możliwość uzyskania informacji o lokalizacji z równości wyrażeń takich jak Place.Name == "Seattle" lub Place.State == "Alaska" (Place.Name = "Seattle" lub Place.State = "Alaska" w Visual Basic).
Następna procedura pokazuje, jak dodać obsługę dodatkowy sposób określania informacji o lokalizacji.Po dodaniu tego kodu dostawcy będą mogli wyodrębnić informacje o lokalizacji z wyrażenia wywołania metody, takie jak place.Name.StartsWith("Seat").
Aby dodać obsługę predykatów, które zawierają String.StartsWith
W LinqToTerraServerProvider projektu, dodać VisitMethodCall metoda LocationFinder definicji klasy.
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); }
Ponowna kompilacja LinqToTerraServerProvider projektu.
Aby przetestować nowe możliwości swojego dostawcy, otwórz plik Program.cs (lub Module1.vb w Visual Basic) w ClientApp projektu.Zastąp kod w Main metoda następujący kod:
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
Uruchom program i wyświetlić wyniki.Powinno być około 29 wyników.
Następna procedura pokazuje, jak dodawanie funkcjonalności do dostawcy, aby włączyć kwerendy klienta określić informacje o lokalizacji za pomocą dwóch dodatkowych metod, w szczególności Enumerable.Contains i List<T>.Contains.Po dodaniu tego kodu dostawcy będą mogli wyodrębnić informacje o lokalizacji z wyrażenia w kwerendzie klienta, wywołanie metody, takie jak placeList.Contains(place.Name), gdzie placeList kolekcji jest lista konkretnych dostarczonych przez klienta.Zaletą poinformowanie klientów, użyj Contains metoda jest, że można określić dowolną liczbę lokalizacji tylko przez dodanie ich do placeList.Różnicowanie liczby lokalizacji nie powoduje zmiany składni kwerendy.
Aby dodać obsługę kwerend, które mają metoda zawiera w ich klauzuli 'where'
W LinqToTerraServerProvider projektu w LocationFinder klasy definicji, zastąpić VisitMethodCall metoda następujący kod:
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); }
Metoda ta dodaje każdy ciąg w kolekcji, Contains jest stosowany do listy lokalizacji, aby wykonać kwerendę sieci Web dodatku service z.Metoda o nazwie Contains jest zdefiniowany w obu Enumerable i List<T>.W związku z tym VisitMethodCall metoda musi sprawdzić dla obu tych typów deklarowania.Enumerable.Containsjest zdefiniowany jako metodę rozszerzenia; Dlatego zbioru, który jest stosowany do jest faktycznie pierwszy argument do metody.List.Containsjest zdefiniowany jako metodę wystąpienia; Dlatego zbioru, który jest stosowany do jest obiektem odbierającym metody.
Ponowna kompilacja LinqToTerraServerProvider projektu.
Aby przetestować nowe możliwości swojego dostawcy, otwórz plik Program.cs (lub Module1.vb w Visual Basic) w ClientApp projektu.Zastąp kod w Main metoda następujący kod:
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
Uruchom program i wyświetlić wyniki.Powinno być około 5 wyników.
Następne kroki
W tym temacie instruktażu pokazałem, jak stworzyć LINQ dostawcy dla jednej metody usługi sieci Web.Jeśli chcesz wykonywać dodatkowe rozwoju LINQ dostawcy, należy rozważyć następujące możliwości:
Włącz LINQ dostawcy do obsługi innych sposobów określania lokalizacji w kwerendzie klienta.
Zbadanie innych metod Szukacz-USA w sieci Web umożliwia uzyskanie dostępu do usługi i utworzyć LINQ dostawcy, z jednej z tych metod.
Znaleźć innej usługi sieci Web są zainteresowane i utworzyć LINQ dostawcy dla niego.
Tworzenie LINQ dostawcy dla źródła danych, niż usługi sieci Web.
Aby uzyskać więcej informacji o tworzeniu dostawcy LINQ, zobacz LINQ: budynek dostawca IQueryable na blogach MSDN.
Zobacz też
Zadania
Jak: Modyfikowanie wyrażenia drzew (C# i Visual Basic)
Informacje
Koncepcje
Włączenie źródła danych dla kwerend LINQ
Usług Windows Communication Foundation i usług WCF w danych w programie Visual Studio