Setting up the scene
To build my sample provider, I’ll be querying a sample Web Service. First, Let’s see the big picture of what I’m trying to do :
- DummyWs : my sample provider will be built against a web service. That service will return objects from its model (Person objects first, maybe some other types later), according to search criteria.
- DummyWsModel : the model classes and entities will reside in an service-agnostic assembly that will be shared across the projects.
- DummyWsLinqer : the Linq provider will be based on the service and the model.
- Tests : this project will use the Linq provider to consume data from the service, but will not hold any direct reference to the service. The idea here is to check both :
- that the data retrieved data is correct,
- that the parts of the query that can be handled by the service, well, have been handled by the service and not by the client.
Here is a graph representing the projects dependencies :
I will start with a very simple Model, with a single entity called Person. I’ll probably make the model richer in the next steps, when I have already something working. For now, a Person has an ID, which is a Guid, a first name, a last name and an age.
My first goal will be to make the provider effectively call the service, and return the results. To do so, I will have to capture the Expression tree representing the call, and replace the expression representing the persons by the results of the service.
I’ve based my work upon the Visual Studio samples, and in particular the walkthrough: “Creating an IQueryable LINQ Provider”. Most of the basic code is taken from this sample. But although this sample is not very complicated, it mixes several topics and goals and I’ve found it uneasy to understand. This is why I wanted to make my own implementation and make baby steps.
As I know that my service will return data, the simplest possible test against my provider is to get an enumerator and check that I can at least iterate once :
public void GivenAProviderWhenIGetAnEnumeratorThenICanMoveNext() { QueryableDummyData<Person> queryablePersons = new QueryableDummyData<Person>(); bool exists = false; using (IEnumerator<Person> enumerator = queryablePersons.GetEnumerator()) { exists = enumerator.MoveNext(); } Assert.IsTrue(exists); }
The previous code is written in the “Tests” project, and from the first schema we know that only the DummyWsModel and DummyWsLinqer projects are referenced. Person is a class from the Model, and QueryableDummyData<T> is the entry point to express queries against the model. Please note that there is nothing here which refers directly to the web service.
From the MSDN sample, I’ve taken only the core components :
- a class to be used as the entry point : QueryableDummyData<T>, an implementation of IOrderedQueryable<T>
- the provider : DummyQueryProvider, an implementation of IQueryProvider,
- DummyQueryContext, the class which really handles the queries, and transforms them so that they can really be executed.
- TypeSystem, an internal helper class.
QueryableDummyData, DummyQueryProvider and TypeSystem are basically copy-pasted (shame on me) from the MSDN sample, and quite not modified apart from the renaming. DummyQueryContext is the place where the work gets really done.
QueryableDummyData implements IOrderedQueryable<T>, which means IQueryable<T>, IEnumerable<T>, IOrderedQueryable, IQueryable and IEnumerable. Out of these, the real implementations needed are 3 properties from IQueryable and the generic and non-generic GetEnumerator overloads from IEnumerable<T> and IEnumerable. This class does no real work and the GetEnumerator implementations delegate the work to the Provider’s Execute method.
DummyQueryProvider implements IQueryProvider, but for now the only relevant methods are the two Execute overloads (generic and non-generic). Again, these methods do nothing more that to delegate the work to the DummyQueryContext class.
As I said before, DummyQueryContext is the place where something finally happens. To know how, simply continue to the next post !