In the course of trying to answer a customer question on how to leverage the WCF Data Services client for caching, I came across a great blog post on the subject—which I had almost completely forgotten about:
Working with Local Entities in Astoria Client
(This post—by OData developer Peter Qian—is, in fact, so old that is refers to WCF Data Services by its original code name “Astoria.”)
The customer was looking for a way to maintain a read-only set of relatively static data from an OData service in memory so that this data could be exposed by his web app. As Peter rightly points out, the best thing to do is use a NoTracking merge option when requesting the objects. In this case, the object data is available in the Entities collection of the DataServiceContext and can be exposed in various ways. The entity data stored by the context is wrapped in an EntityDescriptor that includes the entity’s tracking and metadata, so some fancier coding is involved to expose this cached data as an IQueryable<T>, which have the LINQ—iness that we all really want.
Just re-read the post yourself, and see if you agree with me that it’s a rediscovered gem for using the WCF Data Services client.
It would be nice to see how to turn on NoTracking in a WCF Data Services project. (There are a few questions asking how to do that on the web, but no answers...)
You set the merge option on a DataServiceContext-derived class, as follows:
NorthwindEntities context =
new NorthwindEntities(new Uri("services.odata.org/.../Northwind.svc"));
context.MergeOption = System.Data.Services.Client.MergeOption.NoTracking;
This merge option is evaluated when the returned entries are materialized. This means that you can do things like cache the existing merge option, change it to execute a query, and then change it back to the cached value, without affecting the other objects in the context.
Maintaining a client-side cache and finding things in the cache is the easy part in my experience.
Difficulties arise when you start trying to use the cache in a full crud app.
Let's say you have a Customer entity with a CustomerType navigation property. A natural thing to do is cache the CustomerTypes as the user might be able to change the Customer.CustomerType by choosing from a list of valid CustomerTypes, and you might not want to keep fetching the CustomerTypes over and over.
So, when you load a particular Customer you might call LoadProperty or Expand to fetch the current CustomerType for the Customer entity. You then let the user change the CustomerType to one of the entities in your cache :
1. This fails when you call SetLink because the cached entity isn't tracked by the context doing the save operation.
2. You can solve point 1. by using AttachTo() but this can throw an exception if an entity with the same URI has been loaded into the context. This can happen if say the CustomerTypes are used elsewhere in the same UI. For example if you have a grid of customers, and the user can change the CustomerType by clicking in a cell, then row 1 might CustomerType=1, row 2 might have CustomerType = 2, and if the user changes row 1 to a cached copy of CustomerType=2 you have a problem because you already loaded CustomerType=2 via the context for the entity in row 2.
3. It is also a pain in this example if you are using WPF and binding the SelectedItem of some list to the navigation property entity (rather than by id), where the binding won't find the right item in the cached list because the LoadProperty instance is not the same as the equivalent cached instance.
4. One solution to all this would be to generate the EF model with the ForeignKey values as well as the navigation properties, and then you just drive everything via Ids rather than navigation properties, however EF blows up if you do this and you have any 0..1 cardinality relationships, which is quite common in my experience and hasn't been a viable option on projects I have worked on.
Personally I have managed to work around all this by various tricks, but it wasn't pretty and was a little brittle for the developers to use.
My basic solution was to :
1. When the LoadProperty occurs, I find the equivalent entity in the cache and replace the loaded property with it.
2. I detach the loaded property from the context.
3. I check if the entity is being tracked when calling SetLink and if not call AttachTo.
A better, more elegant approach to solving this overall problem would be a fantastic blog post (or even better still, some extra framework features to help with the problem would be much appreciated).
Thanks for your tips on working with cached entities on the client using NoTracking.