Add the TerraServerQueryContext class to your project.
Imports System.Linq.Expressions
Public Class TerraServerQueryContext
' Executes the expression tree that is passed to it.
Friend Shared Function Execute(ByVal expr As Expression, _
ByVal IsEnumerable As Boolean) As Object
' The expression must represent a query over the data source.
If Not IsQueryOverDataSource(expr) Then
Throw New InvalidProgramException("No query over the data source was specified.")
End If
' Find the call to Where() and get the lambda expression predicate.
Dim whereFinder As New InnermostWhereFinder()
Dim whereExpression As MethodCallExpression = _
whereFinder.GetInnermostWhere(expr)
Dim lambdaExpr As LambdaExpression
lambdaExpr = CType(CType(whereExpression.Arguments(1), UnaryExpression).Operand, LambdaExpression)
' Send the lambda expression through the partial evaluator.
lambdaExpr = CType(Evaluator.PartialEval(lambdaExpr), LambdaExpression)
' Get the place name(s) to query the Web service with.
Dim lf As New LocationFinder(lambdaExpr.Body)
Dim locations As List(Of String) = lf.Locations
If locations.Count = 0 Then
Dim s = "You must specify at least one place name in your query."
Throw New InvalidQueryException(s)
End If
' Call the Web service and get the results.
Dim places() = WebServiceHelper.GetPlacesFromTerraServer(locations)
' Copy the IEnumerable places to an IQueryable.
Dim queryablePlaces = places.AsQueryable()
' Copy the expression tree that was passed in, changing only the first
' argument of the innermost MethodCallExpression.
Dim treeCopier As New ExpressionTreeModifier(queryablePlaces)
Dim newExpressionTree = treeCopier.CopyAndModify(expr)
' This step creates an IQueryable that executes by replacing
' Queryable methods with Enumerable methods.
If (IsEnumerable) Then
Return queryablePlaces.Provider.CreateQuery(newExpressionTree)
Else
Return queryablePlaces.Provider.Execute(newExpressionTree)
End If
End Function
Private Shared Function IsQueryOverDataSource(ByVal expression As Expression) As Boolean
' If expression represents an unqueried IQueryable data source instance,
' expression is of type ConstantExpression, not MethodCallExpression.
Return (TypeOf expression Is MethodCallExpression)
End Function
End Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace LinqToTerraServerProvider
{
class TerraServerQueryContext
{
// Executes the expression tree that is passed to it.
internal static object Execute(Expression expression, bool IsEnumerable)
{
// The expression must represent a query over the data source.
if (!IsQueryOverDataSource(expression))
throw new InvalidProgramException("No query over the data source was specified.");
// Find the call to Where() and get the lambda expression predicate.
InnermostWhereFinder whereFinder = new InnermostWhereFinder();
MethodCallExpression whereExpression = whereFinder.GetInnermostWhere(expression);
LambdaExpression lambdaExpression = (LambdaExpression)((UnaryExpression)(whereExpression.Arguments[1])).Operand;
// Send the lambda expression through the partial evaluator.
lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression);
// Get the place name(s) to query the Web service with.
LocationFinder lf = new LocationFinder(lambdaExpression.Body);
List<string> locations = lf.Locations;
if (locations.Count == 0)
throw new InvalidQueryException("You must specify at least one place name in your query.");
// Call the Web service and get the results.
Place[] places = WebServiceHelper.GetPlacesFromTerraServer(locations);
// Copy the IEnumerable places to an IQueryable.
IQueryable<Place> queryablePlaces = places.AsQueryable<Place>();
// Copy the expression tree that was passed in, changing only the first
// argument of the innermost MethodCallExpression.
ExpressionTreeModifier treeCopier = new ExpressionTreeModifier(queryablePlaces);
Expression newExpressionTree = treeCopier.CopyAndModify(expression);
// This step creates an IQueryable that executes by replacing Queryable methods with Enumerable methods.
if (IsEnumerable)
return queryablePlaces.Provider.CreateQuery(newExpressionTree);
else
return queryablePlaces.Provider.Execute(newExpressionTree);
}
private static bool IsQueryOverDataSource(Expression expression)
{
// If expression represents an unqueried IQueryable data source instance,
// expression is of type ConstantExpression, not MethodCallExpression.
return (expression is MethodCallExpression);
}
}
}
This class organizes the work of executing a query. After finding the expression that represents the innermost Queryable.Where call, this code retrieves the lambda expression that represents the predicate that was passed to Queryable.Where. It then passes the predicate expression to a method to be partially evaluated, so that all references to local variables are translated into values. Then it calls a method to extract the requested locations from the predicate, and calls another method to obtain the result data from the Web service.
In the next step, this code copies the expression tree that represents the LINQ query and makes one modification to the expression tree. The code uses an expression tree visitor subclass to replace the data source that the innermost query operator call is applied to with the concrete list of Place objects that were obtained from the Web service.
Before the list of Place objects is inserted into the expression tree, its type is changed from IEnumerable to IQueryable by calling AsQueryable. This type change is necessary because when the expression tree is rewritten, the node that represents the method call to the innermost query operator method is reconstructed. The node is reconstructed because one of the arguments has changed (that is, the data source that it is applied to). The Call(Expression, MethodInfo, IEnumerable<(Of <(Expression>)>)) method, which is used to reconstruct the node, will throw an exception if any argument is not assignable to the corresponding parameter of the method that it will be passed to. In this case, the IEnumerable list of Place objects would not be assignable to the IQueryable parameter of Queryable.Where. Therefore, its type is changed to IQueryable.
By changing its type to IQueryable, the collection also obtains an IQueryProvider member, accessed by the Provider property, that can create or execute queries. The dynamic type of the IQueryable°Place collection is EnumerableQuery, which is a type that is internal to the System.Linq API. The query provider that is associated with this type executes queries by replacing Queryable standard query operator calls with the equivalent Enumerable operators, so that effectively the query becomes a LINQ to Objects query.
The final code in the TerraServerQueryContext class calls one of two methods on the IQueryable list of Place objects. It calls CreateQuery if the client query returns enumerable results, or Execute if the client query returns a non-enumerable result.
The code in this class is very specific to this TerraServer-USA provider. Therefore, it is encapsulated in the TerraServerQueryContext class instead of being inserted directly into the more generic IQueryProvider implementation.