Jeff,
All of this sounds like a good approach.
The concurrency issue can be solved in many ways. The one you describe above is the "frustrating" version for a user.
Since it is a common experience for more than 1 users to try to edit the same document, you can try implementing an application level locking mechanism. This can be a separate database table that stores information about the document being locked. You insert a row to lock a docuemnt and you delete the row to unlock the document. When your user requests a document you first attempt to acquire the lock. You look in this table and check to see if the doctype and doc PK are in the table. If not, you can acquire the lock and then insert those values (plus others you may want to track).
When another user attempts to acquire the same document, they won;t be able to get the lock and you can tell them that User X is editing the document beginning at time Y.
When the user is done editing the document and they save it successfully, you can remove the lock.
Things to watch out for:
1. Users abandoning their edit in "unapproved ways". e.g. let session expire, close browser, etc.
We trapped for many of these occurences and attempted to release the lock. This works in 90% of all cases, but there are still odd ways that a lock can be abandoned.
2. You need to provide an Admin UI to release a given lock. This allows your users to call this Admin and tell them they need to be able to edit a locked document. The Admin can check with the current user to see if they are really still editng the document or not and then they can remove the lock.
Joe
I had to resurface this thread because I expect I will need to deal with this concurrency issue myself in the near future.
I like the design that Joe mentioned, mainly because I can extend this to not only locking BO's but also for locking certain UI's that only 1 person should be in at a time. However, my concern stems from the following case:
Object A is not in a locked state.
User 1 needs to access Object A for read-only purposes.
User 1 obtains lock for Object A.
User 2 needs to access Object A for edit purposes.
Object A is currently locked by User 1.
The solution is to not lock Object A for users who only need read-only access, but I am at a loss as to how I could come up with an elegant solution to this. From the perspective of an end user, I would not want to be "overly burdened" by having to open a form in a different way just to achieve read-only access.
I would love to hear some suggestions on possible UI designs for this. We are using WinForms for presentation. The only thing that is popping into my head right now is examining the modifier keys when a user is opening an interface..
Thanks.
We started with a similar application migration and have avoided locking entirely.
There are several ways to implement concurrency, but we have chosen to implement what we call "changed values concurrency" and not do any locking. First change to a field from a loaded object "wins", any others result in a concurrency exception when the "stale" but changed object is saved. However, two users can independently update separate fields in the same object and both updates can succeed.
We chose this approach mainly because it requires no database-specific features (e.g. timestamp columns), nor does it require any alteration of the database schema. Also, the odds of two users really wanting to edit the same object are low because of the way the data is partitioned, so we mainly wanted to ensure integrity of the data. This approach works well except for contrived situations (e.g. one user updates city, another updates zip code).
With code generation, it's easy to capture original values of properties in the property setter when they are changed to they are available to the concurrency implementation. Also, we have situations in which the logic itself really needs to know if a specific field was changed at the time the object is saved -- e.g. IsDirtyProperty("PropertyName"), and the concurrency mechanism doubles for this purpose. Not only that, but we also have a field-level auditing mechanism for any field in the database, and this becomes almost trivial to implement with this concurrency mechanism in place.
Patrick,
I just ran into that issue and I resolved it by adding an optional parameter to my factory method.
Public Shared Function NewBO(ByVal key As Integer, Optional ByVal useEditScreen As Boolean = True) As BOuseEditScreen is passed to the DataPortal by my Criteria class and then I only acquire the lock when it is True.
(On my Read Only screen I obviously set it to False.)
If useEditScreen ThenJoe
Overall, I think your approach makes good sense.
lefty:
Concurrency is about the only thing we're running into with this method, because I don't really want two people editing the same data at the same time. The method of checking a timestamp field before update would work, but I don't want the agent entering a bunch of data and then when they go to save they find out that everything they did was rejected.
I think you will have that problem with any application that uses a pooled database connection that is only kept open just as long as necessary for any one task. Each trip to the database is unconnected to the prior trips.
The best way to test whether that feature is a requirement or a wish is to assign price tag and a timeline just for that feature. Here's an example, with fictional numbers, of course:
"This feature will add 2 months to the project timeline and cost an additional $50,000 in labor for an application of our size. The two months delay will mean that the $10,000 a month projected savings will also be delayed, for a total cost of $70,000. To put that in perspective, that equates to 7,000 lost data collisions, at an estimated $10 labor cost apiece. Our staff tells us that we can expect 50 collisions a month, for a break-even point in 11 and 1/3 years."
If the numbers work out like that, it may not be a requirement any longer....
If you want that rich UI (especially if you have the BO keep the rules for the calcuations, which you should) I suggest a WinForms application. Nothing beats a desktop app for a rich experience. Deploy using ClickOnce, and you're all set. You can even ensure that the framework itself is installed prior to installing your application (and if need be, any additional components that would require installation).
Copyright (c) Marimer LLC