As with many developers around the globe I downloaded the latest CTP of the Linq technology preview last week, but of course, work and other things got in the way. As such it was this evening when I really had my first play (other than just opening the box and going wooow, which I dutifully did last week). Of course, as Bill has already pointed out, one of the big improvements in the VB.NET space is the From, Where, Select syntax for Linq expressions. This makes more sense if you look at how IDE support (eg intellisense) is vital to a feature being usable!
Despite giving a session at this year’s Code Camp I still felt that there was a lot more I need to play with to really test the boundaries of this new technology. An interesting scenario presented itself over the weekend, and for those of you building distributed, ocassionally connected applications this scenario might sound familiar:
Let say I have a database which contains a table called Customers. My mobile application wants to be able to call a webservice (eg CurrentCustomers) which will return a list/array of Customer objects. While offline, these Customers may be modified, then when a connection is available, updates are persisted back to the database via another webservice (eg UpdateCustomer, which takes a Customer object as the parameter).
Now, lets break this into Linq/Dlinq components
Webservice
– Create a new Linq ASP.NET application
– Add a Dlinq object model and drag my Customers table across from Server Explorer
– Create the Webservice methods CurrentCustomers and UpdateCustomer
– Add code to CurrentCustomer to return the approprate Customer objects
Return (From c In db.Customers _
Select c).ToArray
NB: Will leave code for UpdateCustomer for the moment as this is a bit tricky….
Client
– Add web reference to the new webservice
– Call the webservices (ie retrieve customer list, make changes and call update webservice)
So, the only part that I’m left to implement is the UpdateCustomer method in the webservice. And here lies a problem – the way that Dlinq works is that when you access objects using a linq expression it dynamically loads them into memory. As you make changes to these objects it tracks what has been modified so that when you call SubmitChanges on the DataContext it knows what changes to persist to the database. Since the Client side of your application needs to be able to operated when disconnected, there is no way for this persistance manager to keep track of the objects you are working with. When the Client submits the changes via the UpdateCustomer method somehow the persistance manager needs to be notified so that it can handle the writing to the database.
Before we jump in and look at the solution lets just delve a little into how the Dlinq persistance model operates. The persistance model starts with a class that inherits from DataContext. Think of this correlating to a database. This class in turn contains a number of Table(of T) objects, where T is a mapping class for a table in the database (in our case the Customer table). T also implements the INotifyPropertyChanging and INotifyPropertyChanged interfaces. This point is significant as it is these events that are used by the persistance manager to track changed objects. When a changed event is raised on an object, a clone of that object is created and maintained by the manager. At a later stage the user has the option of rejecting changes (in which case the object is reverted to the clone) or submitting changes (in which case the difference between the objects is used to update the database).
The solution to our scenario is that somehow we need to a) get an unchanged version of the Customer object we have modified from the persistance manager, b) update that object with the Customer object sent from the client and finally c) persist the changes to the database. In doing this I actually worked in reverse since a) is actually more fiddly, but I will present them here in order so that they make more sense.
a) Get an Unchanged Customer
To retrieve a Customer object from the persistance manager we need to access the CommonDataServices object that sits within the DataContext object. As this is a protected property we need to us a bit of reflection magic to discover it. Once we have a reference we can call the Requery method to retrieve the Customer object. Note that this object may be retreived from the in memory cache, or will be pulled from the database if the object doesn’t already exist.
Dim ser As Object = context.GetType.InvokeMember(“Services”, Reflection.BindingFlags.GetProperty Or Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic, Nothing, context, Nothing)
Dim currentCustomerState As Object = ser.GetType.InvokeMember(“Requery”, Reflection.BindingFlags.InvokeMethod Or Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public, Nothing, ser, New Object() {newCustomerState, False})
b) Update Unchanged Customer
To update the managed Customer from the client’s Customer all we need to do is iterate through each of the writable Properties of the Customer type. The appropriate properties will also be marked with the ColumnAttribute to indicate their association with a database column.
Dim props As PropertyInfo() = gettype(Customer).GetProperties
For Each prop As PropertyInfo In props
If prop.CanWrite And prop.CanRead Then
Dim attribs As ColumnAttribute() = TryCast(prop.GetCustomAttributes(GetType(ColumnAttribute), True), ColumnAttribute())
If attribs IsNot Nothing AndAlso attribs.Length > 0 Then
prop.SetValue(currentCustomerState, prop.GetValue(newCustomerState, Nothing), Nothing)
End If
End If
Next
c) Persist Changes to Database
What remains is to use the DataContext object to persist the changes to the database:
context.SubmitChanges
And there you have it – how to persist a webservice object. Now you can take this to the next level by extracting this out as an extension method for the DataContext class by removing all references to the Customer class, but I’ll leave this discussion to another time (or to Bill’s discussion on the topic).