Disabling Linq
For a lot of things, Linq is really nice. But there is one very important problem with Linq. It make it very easy to do some really stupid things, because it is so easy to intermix in memory operations with remote operations. One of the most common problems that we run into with the RavenDB API was this:
session.LuceneQuery<User>("Users/ByName")
.Where(x=>x.Name == username)
.FirstOrDefault();
The problem is that LuceneQuery is not the Linq endpoint to RavenDB. It is the lower level API that we use to query RavenDB, and the Linq API is built on top of that. The problem is that it is very easy to write code like that, and it works, for small amounts of data. What this code actually does is load the first page of information from the database and do the filtering in memory.
Obviously, this is not ideal, but this is a pretty common mistake. We are currently thinking of ways to drastically simplify the API, but in the meantime, something had to be done about this.
LuceneQuery returns an instance of IDocumentQuery:
public interface IDocumentQuery<T> : IEnumerable<T>
That was good, because that meant that we had a hook to place stop the user from making this mistake. The way Linq works, it first looks for an instance method and only afterward it will look for extension methods. That allowed me to define the following:
[Obsolete(@" You cannot issue an in memory filter - such as Where(x=>x.Name == ""Ayende"") - on IDocumentQuery. This is likely a bug, because this will execute the filter in memory, rather than in RavenDB. Consider using session.Query<T>() instead of session.LuceneQuery<T>. The session.Query<T>() method fully supports Linq queries, while session.LuceneQuery<T>() is intended for lower level API access. If you really want to do in memory filtering on the data returned from the query, you can use: session.LuceneQuery<T>().ToList().Where(x=>x.Name == ""Ayende"") ", true)] IEnumerable<T> Where(Func<T, bool> predicate);
Now, the code above it going to generate a very descriptive compilation error, and the entire class of bugs just went out the window.
Comments
Creative.
Good solution, not destroying the api but very clearly pointing the consumer in a better direction.
How about stop deriving IDocumentQuery from IEnumerable, and adding an .AsEnumerable() method to it?
This will make moving into the IEnumerable monad more explicit.
Omer, but that would mean that we couldn't do things like:
First(), Single(), Count, etc
A descriptive obsolescence as in NH. Maybe you should allow these queries to be executed from another interface, like ImProbablyMakingAMistakeSession or sth like this?
But all the other methods mentioned take predicates to filter on, so it's the same thing. I would never write:
.Where(x => x.Name == "ayende").FirstOrDefault();
But
.FirstOrDefault(x => x.Name == "ayende");
There are so many extension methods, do you really want to Obsolete them all? Think I agree with Omer
Oren,
As Neil observed, there are many LINQ operators that can filter.
Even Single() with no predicate is filtering, and as such will cause a full query to hit the DB, only to be filtered back into a single item.
You should rethink this.
If you do want to use linq-to-objects, you'll just have to add a single .AsObservable() method and then you could go run with your scissors. :-)
Only works if you are referebcing the LuceneQuery through a IDocumentQuery. If you happen to have a reference to an IEnumerable which happens to be a LuceneQuery then the LINQ extension method will still get used.
Comment preview