lukky:Second, I've seen some discussion about concurrency handling, and there doesn't seem to be a clean solution sticking out as a "better" one. What I'd like to implement is a "first wins" scenario with warning to user when conflicts happen. The way I'm seeing it would be to only update modified fields. So I'm thinking of having two PropertyInfo objects for each property of my objects, one with the curent value, and one with the original value. Then, I'd generate the SQL dynamically to only include modified fields. Has anyone implemented such a beast ? Any forseeable pitfalls ?
This is *exactly* the concurrency model that we use, but you don't really need two properties for each field.
Basically (and code generation will help with this), you need a common base class that maintains a dictionary of some sort of the original values. Then, you use a common property setter or helper (e.g. PropertyChanging(fieldname, oldvalue, newvalue) that stores the original value the first time a property is changed.
When the object is updated, your CRUD layer can append the original values of the changed properties to the WHERE clause. If no records are updated, then a collision has occurred.
This model allows for lazy instantiation of the original value cache, so there is no overhead unless a field is changed, and then the overhead is limited to just the fields that are changed. I used a HybridDictionary which uses the more efficient list implementation when just a few entries are present and switches over to a hash mechanism automatically when necessary.
Remember to flush the original value cache when the object is successfully updated.
Note, with CSLA 3.5 and later, it's probably possible to avoid the separate dictionary and implement your own IFieldData and manage the old value directly in the FieldManager. However, much of that infrastructure in CLSA is marked as "internal", so this approach might require some CSLA changes.
I'm hoping you can explain your use case a bit more because I'm confused why this level of complexity is required - but I'm very interested in learning more.
In my experience, concurrency can be handled simply with a LastModified field that contains a date/time value. When the record is retrieved from the data store, this value is read into your BO. When an update occurs, this value is supplied and compared to the record's current value. If different, then someone else has already changed the record and an error is thrown (as a SqlException which is handled in code). If they match, then the update proceeds. As part of the update, the value is also updated in the data store and sent back to the BO where it updates its value to once again match so subsequent updates will succeed.
I had one case where the owner wanted the application to prompt the user to overwrite the changes when detected. To do this, I added a CommandObject that polled the record to see if a change has occurred by, again, comparing these dates - it was triggered by a HasChangedOnServer() method. If the command returned a flag indicating that a change had occurred, I raised an event which was handled by the UI and prompted the user. Depending on the user's selection, I would either proceed with the update passing in an additional parameter telling the db to ignore concurrency or I would terminate the operation.
As for adding the complexity of dynamic SQL into the mix. The only benefit I've seen from this is to minimize the packet size sent from the application to the data store. And, most times, it hasn't saved me enough to warrant the additional processing time and code complexity to implement. I do know, however, that many ORM's on the market do enable this type of logic. NHibernate is one, I believe. It can be used with CSLA within the DataPortal_XYZ methods (instead of directly working with ADO.NET objects, use NHibernate to access and work with the data store).
All that said, I am very interested in learning more if you have a use case that requires working with the individual properties as you've described. Please share more.
Thanks!
Luc,
1. The timestamp solution is the most straightforward to implement. It is also the method that Rocky uses in the Project Tracker sample application so you can see exactly how to do it. I recommend it. Many projects do not use concurrency checking at all so the timestamp method gives you the most basic level of concurrency checking and it is the easiest to implement.
2. I have to question how often it is important that 2 users be allowed to edit different fields of the same row of data and both be allowed to save their changes as long as they do not overlap. It sounds good in theory - but how often does it really happen? Building the required infrastructure to support this concurrency mdoel would be a cool and interesting project - of very little value to the business owner in most cases. If you already have the infrastructure then I guess it wouldn't hurt ot "just use it". But this is non-trivial to implement and in my mind you can spend your time doing other things and just implement #1 above.
YMMV.
Joe
On concurrency,
We use a simple row version field. The data is passed to a stored procedure that uses an update statement that includes row_version = row_version + 1 and the where clause has row_version = @row_version
The @row_version is the row version from the object. If the rowcount returned is 0, then the stored proc raises a concurrency error.
Copyright (c) Marimer LLC