If you’ve at all paid attention to much of the buzz surrounding Visual Studio code-named Orcas (available now in Beta 1), which is set to hit the streets by the end of 2007, you know that Linq stands for “Language Integrated Query,” a feature supported by the next versions of C# and VB that allows you to query data with SQL-like syntax right from within the language. And by data I mean not just relational data stored in tables within a DBMS like Oracle or SQL Server, but also XML, in-memory collections (anything implementing IEnumerable<T>), plus the forthcoming ADO.Net Entity Framework, and various other stores and data facades, such as LDAP, NHibernate and even Amazon. In case you’re wondering, it’s been announced that VS Orcas will include Linq to SQL but not Linq to Entities, which will come out sometime in the first half of 2008. To find out more, check out the Channel 9 Entity Framework video and this collection of Linq white papers. One of the best Linq tutorials, in my opinion, is Anders Hejlsberg’s recent Linq Session at MIX. As if all this weren’t enough, there were also announcements at MIX on Astoria (data as XML over HTTP) and Jasper (codeless entity generation and data binding for dynamic languages).
After returning from some back-to-back teaching engagements, I decided to relax a little bit before starting to prep for teaching some .Net 3 courses. So what do I do for relaxation? Start dabbling with Linq, of course! So after brushing up on the basics, I started to think about using Linq to SQL (or Entities) with an n-tier application architecture, where the client remains oblivious to the data store and simply deals with a middle tier service that handles all that stuff.
I naively assumed the framework would handle batch updates in a way similar to what we had with ADO.Net DataTables, which track changes internally, allowing you to send them to a service where they can be applied to a database. However, much to my chagrin, I discovered that Beta 1 of Linq to SQL does not include a client-side change-tracking feature, which means we probably won’t see it in the Orcas final release either. (See blog posts by Roger Jennings, Andres Aguiar, and Dan Simmons for a lively discussion of the issue.) However, there is hope we’ll see something come out post-Orcas that handles change tracking across tiers.
In the meantime (which could be some time), you’ll need to roll your own client-side change tracker. And that got me thinking. Wouldn’t it be nice to have a simple change tracker you could start using now without even needing to rely on Linq to SQL? Client and server would have to agree on how to communicate change data. You could either incorporate that in the objects themselves or send the changes separately. Either way, there’s going to be some coupling. So I opted for the first approach. Simply require every object sent across the wire to have an ObjectState property, which consists of an enum with entries for Unchanged, Added, Deleted, and Changed. Then on the service-side, have some code that applies those changes to some sort of data store. (I leave aside for now the issue of concurrency management, which will no doubt be part of the post-Orcas Linq to SQL / Entities product.)
For the client piece, I created a SimpleChangeTracker with an IObjectState interface and a ChangeTrackingList<T> class that inherits from BindingList<T> and constrains T to implement IObjectState. It tracks changes and additions by handling the ListChanged event of the base class, and it keeps track of deletions by overriding RemoveItem and storing a copy of the item (for this reason T is also constrained to implement ICloneable). To keep track of deleted items, we need a way to uniquely identify them, so I also require a class that implements IEqualityComparer. You can download my SimpleChangeTracker, which also includes a sample WCF service with a Windows Forms app as the client.
Having rolled my own change tracker, I wanted to test it out with Linq to SQL. So I wrote a simple n-tier app that includes a WCF service that talks to the Northwind database and returns an array of Products. To “extend” the class for change tracking, I inserted a partial Product class into the project with an ObjectState property of ChangeType enum, adding a DataMember attribute for serialization. The service just has two methods: GetProducts, which returns an array of products, and UpdateProducts, which accepts an array of products. The second method is most interesting. There I retrieve products from the database using Linq to SQL, then use Linq to find those that match the changed items. After replicating those changes to the entities returned from the database, I simply call SubmitChanges on the DataContext to push those back to the database. (I just use “last update wins” in case of concurrency conflicts.) You can download my LinqSqlNTier project, which was creating using VS Orcas Beta 1.