FromCache now supports local collections
Posted on: January 3, 2011
A commenter recently discovered that the technique I use to generate a cache key for any IQueryable was not working properly for one of his queries. The query contained a call to List.Contains, and the contents of the list was not getting put into the cache key.
The problem was that the query cache didn’t specifically know about local collection values.
Local collection values can be supplied as parameters to query operators such as Contains and Any, if the query provider supports them.
var ids = new List<int> { 1, 3, 5 };
var q = from c in db.Customers
where ids.Contains(c.CustomerId)
select c;
As far as FromCache was concerned, ids is just a constant expression, and treated it no differently to any other constant when evaluating its cache key.
Unfortunately, the ToString implementation of Lists (and of Arrays, and IEnumerables in general) is not suitable for use as a cache key, because it outputs the type name and not (for example) a representation of every element in the collection.
I couldn’t understand how I’d overlooked this. But then I remembered that Entity Framework v1, which was the main provider I was using at the time, didn’t support local collections either!
LINQ to SQL has always supported the Contains method, and Entity Framework now supports both Any and Contains overloads with local collections. So, I’ve updated the original source code and the blog post to support local collections.
This involves a pass over the expression tree to expand appropriate local collection values which might be used in methods like Any or Contains. Each method call in the expression tree is examined, and the argument to any parameters of type IEnumerable<> or List<> is wrapped in an object with an implementation of ToString which prints every element in the collection.
I’ve also extracted the method IQueryable.GetCacheKey and made it public, which could help emphasise that the meat of the idea is to automatically generate a cache key for a query (and that you can do whatever you like with it, such as make a FromCache extension method, or managing the cache manually).
If you’re using the FromCache extension method, please update use the latest version of the source code!
12 Responses to "FromCache now supports local collections"
3.5 implementation of Zip here
http://stackoverflow.com/questions/1616554/create-an-enumeratordatatype-datatype-from-2-enumerables
I found this on a Silverlight site can anyone change this into an extension method?
///
/// Creates a new expression that is like this one, but using the
/// supplied children. If all of the children are the same, it will
/// return this expression.
///
/// The property of the result.
/// The property of the result.
/// This expression if no children changed, or an expression with the updated children.
public MethodCallExpression Update(Expression @object, IEnumerable arguments) {
if (@object == Object && arguments == Arguments) {
return this;
}
return Expression.Call(@object, Method, arguments);
}
awesome. Testing now
Just ran it through my sanity test suite and all tests are green. I don’t quite understand what the difference is but the code written for PLINQ won’t pass my test suite. I run my full regression set overnight and report back.
I’ll also test also test wether a deferred query used as the parameter for the Contains() get expanded correclty
Ok its all good news for me. All 1694 of my integration tests run correctly. I’ve also removed a ToList() that materialized a query which was used in a following query.
Now a couple more questions. I’m using this cache for a read only database. Can I safely set the sliding window expiry time to say 24 hrs?
I’m also struggling to find some perfmon counters to monitor my cache hit/misses. Can anyone point me to the right ones?
And finally a big THANKS
I don’t know if this is a bug or whether I have done something wrong but If I have created a query like this…
public IQueryable All() where T : class, new()
{
return new ObjectQuery(GetSetName(), this.context, MergeOption.NoTracking).FromCache().AsQueryable();
}
And run the query against the AdventureWorks database like this….
List products = new List(session.All().Where(x => x.Color.Equals(“Black”, StringComparison.InvariantCultureIgnoreCase)));
Then I will get a nullExceptionError since some products Color property is null.
A workaround is this…..
List products = new List(session.All().Where(x => x.Color != null && x.Color.Equals(“Black”, StringComparison.InvariantCultureIgnoreCase)));
but obviously that isn’t really acceptable as a standard query with the following code would allow me to return a list without the exception and workaround.
public IQueryable All() where T : class, new()
{
return this.context.CreateQuery(this.GetSetName()).AsQueryable();
}
Any ideas?….
Many Thanks
James
Thanks for the swift reply! I’m sorry I should have explained better.
I’ve been working on a way to utilize the Entity Framework using generics so the code I posted was part of something much larger.
Using == works though I’m not sure why. I would have assumed .Equals() would have used == internally. I’ll have to see if I can come up with something that won’t cause the error.
Running a few tests on your caching framework shows some outstanding results. I’m able to return results so much faster.
It’s oustanding stuff!
Thanks… Yeah after a bit of reading I figured that. I’ve managed to get it all working now though so all is good.
I’m amazed by the results. Incredible work!
January 3, 2011 at 9:27 pm
I fixed this issue by updating the SubtreeEvaluator.Evaluate function. It looks for arrays and enumberables and rewrites them to a NewArrayExpression which ToString is what we’re expecting. Source code is in PLINQO and in the following form post …
http://community.codesmithtools.com/Template_Frameworks/f/66/p/11653/44664.aspx#44664