I’ve been working on the Silverlight client for the RavenDB. In Raven, you can write a Linq query in the Silverlight client that will ultimately be executed against a Raven server somewhere. The execution of the query is asynchronous and we want that to be explicit in the api. It should be obvious to the developer that its execution is an asynchronous operation. By that I mean, we don’t want the api to give the consuming developer a nasty surprise. Ayende wrote about this issue a few months ago.
To demonstrate how easy it is to make a mistake, you could very easily write this code:
using (var session = documentStore.OpenAsyncSession())
var companies = session.Query<Company>()
.Where(x => x.Name == "Blue Spire")
The problem here is that the execution of the query is going to be asynchronous and we need to provide a mechanism for a callback after the results have been returned from the server. We have added ToListAynsc() which will initiate the request and provide a Task for handling the continuation. That’s great, but now the problem is that calling ToList() doesn’t make any sense. We’d like to prevent a developer from calling ToList(), preferably at compile time.
Unfortunately, the technique that Ayende used in his post won’t work for us. At least, not without some tweaking. Why not?
Well, if we examine the session above. we’ll see the Query method returns an IRavenQueryable<T>. This interface inherits from the built-in interfaces necessary for supporting Linq; which ultimately is IQueryable. Now the nature of Linq is to have a series of chained method calls. After the first link in the Linq, we no longer have an IRavenQueryable<T>, instead we have an IQueryable<T>. This is because all of the Linq methods are extensions methods living on Queryable. (Well, at least the ones we’d be using in this discussion so far. There are other implementations such as the ones on Enumerable.)
Once a link in the chain returns an IQueryable<T> then any method defined on Queryable is valid for compilation, even though it might not make sense. What we’d like to do is override these Linq methods so that we always return an IRavenQueryable<T> and thus explicitly control what is available to developer.
It turns out that this is easy to do, but (like all things in implementing Linq) rather tedious.
We have to provide our own implementation of every extension method for IQueryable. In most cases, the new implementation is just a matter of casting back to our desired type:
public static IRavenQueryable<T> Where<T>(this IRavenQueryable<T> source, Expression<Func<T, bool>> prediate)
return (IRavenQueryable<T>)Queryable.Where(source, prediate);
Now that we can always return an IRavenQueryable<T> we can use the Obsolete attribute to disable the Linq operations that don’t make sense for our context.
[Obsolete("You cannot execute a query synchronously from the Silverlight client. Instead, use ToListAsync().", true)]
public static IList<T> ToList<T>(this IRavenQueryable<T> source)
throw new NotSupportedException();
This also means that we are able to make ToListAsync specific to IRavenQueryable<T> (the only place where it makes sense).
public static Task<IList<T>> ToListAsync<T>(this IRavenQueryable<T> source)
// if you really want to see the implementation, check out the source on github
01-21-2011 10:30 AM