O/R Mappers: Optimistic Concurrency

I finally got around to adding support for optimistic concurrency to my WilsonORMapper, and it definitely had me think through quite a few design scenarios that I'll share here.  Optimistic concurrency works by adding some portion of the record's original values to the where clause of the update statement to make sure no one else has updated it first.  Modifying the update is relatively easy, so my issue was that I needed original values.  It was tempting to think I had the original values already, since I do track the values, but the problem is that my broker might be used in a multi-user web or distributed app.  This means that the values I'm tracking will always be the last known persisted values, which is great for some scenarios, but this does not support optimistic concurrency.

One thing that should be obvious is that I need to store the original values with the actual object, so I set out to explore the various alternatives to achieve that result.  One possible solution is relatively trivial -- just require all entity objects to derive from a specific base class which would include support for storing the original values.  This is indeed what many other O/R mappers do, but I had originally chosen to avoid the base class approach for a variety of reasons, and I still have the very same objections.

The next possible alternative is to do the reverse -- instead of having a common base, its possible to create a dynamic assembly where all of the entity types are extended.  This actually turned out to be very easy to do also, but this would mean that I would have to give up my current simplicity of allowing the explicit new to create objects.  This is one of the cornerstones of my mapper approach -- KISS -- Keep It Simple Stupid.  So I also abandoned this approach, although I may use it someday for some other feature.

Another possibility is to dynamically modify the existing type using some code injection.  The most common way to do this in .NET is to use Aspect Oriented Programming techniques, but this requires not just a base class, but a very heavy base -- ContextBoundObject.  Another injection solution is to use the RAIL framework, which looks very promising, but then I noticed there was another problem that I had been overlooking from the beginning.

That problem, which seems to be overlooked by MS ObjectSpaces and most other O/R mappers, is that optimistic concurrency is not necessarily always based on all original values!  Its actually quite common to just use a TimeStamp column in MS Sql, and I had already just added the ability to configure fields like TimeStamp to be read-only for someone.  Well its just natural to extend this idea and allow the user the configure each field with a persistType that can be either Persist (the default), ReadOnly, or Concurrent.  This solves the read-only cases like TimeStamp immediately, but it has the downside of forcing my users to create and configure two fields for other fields that should take part in the optimistic concurrency check -- one each to hold the new and original values.

This is indeed an extra burden on the user, but it has a couple of redeeming values too.  First, it allows the simplicity I've strived for to remain in the non-optimistic cases.  Next, it allows the flexibility to define your concurrency fields, which is missing from at least some other systems -- great if you are using TimeStamp or if have you have so many fields that you don't want to pass them all -- which was something else I had just dealt with.  Finally, my mapper also suports updates with stored procedures, and I don't really see how I could have continued to allow them without somehow also allowing the user to have configurable parameters for both the new and original values anyhow -- so case closed.

I know that I will probably get derided for this solution, since its not very "pretty", but in the end it meets my goals of simplicity for the main cases, which is my strength, while still offering some additional flexibility if you are willing to configure it.

6 Comments

  • The first public showing of ObjectSpaces used a type of code injection. You authored your objects as abstract base classes, leaving all your data properties abstract. At runtime the system generated code to turn your abstract classes concrete. This had the drawback of not being able to use 'new' to construct your object. The next version that never showed up at PDC used ContextBoundObject like you described in order to intercept references to properties, etc, to store original values and such. Unfortunately, all that stuff was designed for building remoting proxies and the cost for in-memory cases was just too heavy. The current ObjectSpaces implementation does not rely on either, simplifying the process greatly.

  • Ok, I'll bite. What then does the current ObjectSpaces implementation do? Please inform us, don't leave us hanging!

  • As far as I can tell, the alpha version that is available now uses something similar to RAIL -- they create dynamic assemblies that get substituted for the real things at load-time. This was exactly where I was also heading until I stopped and decided that things needed to be more flexible anyhow. It would be nice for Matt to confirm it though, since its really hard to tell for sure.

  • "Optimistic concurrency works by adding some portion of the record's original values to the where clause of the update statement to make sure no one else has updated it first. "

    Heh :) Optimistic (or pessimistic) concurrency doesn't work. Either way someone looses work done. It's OR the one who saved first, OR the one who tries to save last OR the one who started first OR the one who started last.



    When do people start seeing that low level concurrency is not the way to go?

  • I've solved it this way (but it's probably not appropriate for you, as you use the non-intrusive way)



    I've defined an interface, IConcurrencyPredicateFactory. This interface has a method which is defined to produce filters based on the entity passed in and the type it is required for (delete, save).



    An instance of this interface can be inserted into the entity object, for example at construction or whenever you want.



    The advantage is that you can produce whatever concurrency scheme you want! Timestamp and some value? just a value? just a set of values? just a timestamp? no problem. Just produce the filter object.



    When you have constructed an object hierarchy at runtime (like customer with some new orders, orders have new order detail objects, perhaps some other referenced objects), you can simply save the Customer and all objects get persisted recursively, AND the concurrency predicate factories inside these objects are used at the moment they're required. Set and forget :)



    This approach has also the advantage that you can set concurrency on a per-object basis or on a per-entity type basis.

  • I totally agree that optimistic concurrency is often, and incorrectly, given a very lofty place. That's why I didn't even bother to include it in my first version, and even now make you set it up. I also don't like the fact that its the default in MS ObjectSpaces, although I assume (hope) there's some way to turn it off. I have seen very few systems over the years that have multiple people updating the same records, although its very common to have triggers or other checks against common summary data, but that's not really the same thing at all!

Comments have been disabled for this content.