Multiple users editing same data

Multiple users editing same data

Old forum URL: forums.lhotka.net/forums/t/4492.aspx


meierk posted on Wednesday, March 12, 2008

I just deployed my first CSLA application and am starting to get feedback.  In general, the users are very happy with the application, but there is one issue that seems to be common for many users.  That issue is that multiple users can open an edit form for the same data, make changes to a few dozen controls and then discover that they cannot save their changes because another user has changed the data.

The data entry starts from a readonly grid on the main form.  The user selects a row then clicks an edit button which opens the edit form and passes the data rows ID to the form.  The form then acquires its own instance of the data that the users can then edit and save.  The edit form is bound to a class that is derived from BusinessBase.

One solution that I come up with is to set a database flag when entering an edit form.  When subsequent users launch the edit form for the same data row, I would check the flag and possibly notify the user that another user may be editing the same data.  This does not appeal to me for a few reasons, one of which is the possibility that the flag state could get messed up and then knowbody would be able to edit that data row.  Hopefully someone has some advice that will rescue me.

Is there a way to determine that another user is editing the same data when opening a form?

Thx,
Kevin

ajj3085 replied on Wednesday, March 12, 2008

Your flag (or locking) method is a good way to go, provided you build in a way that accounts for locks which are not properly cleared.

You could include an administrative feature that allows one to break stale locks, or you could also say that edits must be commited within a timeframe or the lock automatically expires, or both.  I worked on a solution that did both of those things, and it worked rather well.

rsbaker0 replied on Wednesday, March 12, 2008

Any time you have two users editing the same record, there is the potential for a collision.  At least one of them has to be the victim, and I'm a firm believer that "first one wins" is the correct way to go.

Locking will accomplish this, but I think this introduces complexity and may impact overally scalability.

What we did was come up with a concurrency model that reduces the chances of a collision. There are several parts to this.

(1) Only changed fields are tested. If two users change different fields of the same record, then both will go through. Yes, you can contrive a situation where this doesn't work well, but in practice we've found it works very well.

(2) Where numeric values are involved, we have an optional capability to update these fields with deltas rather than absolute values (e.g. add/subtract from the field rather than assign a specific value).

(3) In some cases "last one wins" is OK with regards to specific fields. For example, if you are auditing and recording the last user and update date/time for a record, then you shouldn't use these fields to enforce concurrency, and it's quite OK if the second of two updates to these specific fields "wins" -- it in fact reflects what you want recorded.

meierk replied on Thursday, March 13, 2008

Unfortunately, for my users this issue has only one outcome.  After a brief meeting with them, the demand was that I find a way to inform the users that another user *may potentially be* editing the same data, as soon as they launch the edit form.  Additionally, they would prefer that I show them which user may be editing the data.  They are NOT demanding that I lock all users out after the first user opens the form, but they do what to know that there is a risk of losing their edits.

Personally, I don't think this is an unreasonable request, but other than managing through some type of database flag, I was hoping that there was a pattern I could apply or even a feature built into CSLA such as NumberOfTimesObjectHasBeenInstantiatedCount.

I was also contemplating the idea of setting up an event in either my business class or the edit form and then having my main form subscribe to the event to keep a count of the instances of the edit form or business object.  Problem is, that I still need to persists this somewhere in order for all clients to see the count, or can I somehow broadcast the count and setup listeners in each client?

I'm still learning C# and am not sure what tools and techniques are available to me.

Thx,
Kevin

Curelom replied on Thursday, March 13, 2008

It sounds like you may need something similar to a logging system, where somebody opens/closes the edit window you write to a log table which would contain times and user information.  Upon opening the edit window, it would check the log table.  It would also have to have the cleanup logic that locks would have in case things got out of sync.

JoeFallon1 replied on Thursday, March 13, 2008

meierk:
Unfortunately, for my users this issue has only one outcome.  After a brief meeting with them, the demand was that I find a way to inform the users that another user *may potentially be* editing the same data, as soon as they launch the edit form.  Additionally, they would prefer that I show them which user may be editing the data.  They are NOT demanding that I lock all users out after the first user opens the form, but they do what to know that there is a risk of losing their edits.

Personally, I don't think this is an unreasonable request,

I think it is unreasonable. Please explain why someone would want to edit a record knowing that their changes would not be saved? Do they even know what they are asking for? Or did you omit some information in your post that makes this request reasonable?

The database "flag" idea is called Application Locking. You may have a small app now and you could make the "mistake" of adding the flag to the table where your data resides. That may work today but when your app grows you could wind up regretting it.

For example say you only have Orders to deal with today. In the future you may have to deal with Requests, Invoices, etc. The Admin locking feature needs to be able to see all of them. So I use a separate table to store the locks. Each row has a GUID, a RecordType (PO, Invoice, Req), and a RecordKey (the PK of the document we are referring to). I also have LockType which allows me to distinguish the various locks for the Admin. e.g. the PO is locked because it is being edited in Purchasing, or Receiving Or Invoicing. I also trap the userID.

With all of this information available, if a user tries to edit a PO and can't acquire the lock, then they get a message saying "John Smith is editing the PO in Receiving as of 3/13/08 2:15 PM." - or something like that.

I use a CSLA BO to manage the lock for a PO (or invoice or ...).

It adds some complexity but the design is scalable.

The Admin screen is now easy to display since all the locks are in a single table. It makes deleting stale locks simple. Also, in my web app we have code to detect to since if a user abandons a object with a lock and we try to release it. This succeeds 90% of the time or more. This reduces the burden on an Admin to release stale locks.

Joe

meierk replied on Thursday, March 13, 2008

JoeFallon1:

I think it is unreasonable. Please explain why someone would want to edit a record knowing that their changes would not be saved?

It is not my place to tell the user what is reasonable or not.  I merely provide them with my professional opinion and recommendation and then implement what they are willing to pay for.  I stand by my comment about their request being reasonable.  If the application *must* allow multiple users to open the edit form for the same data, the users should know the risks when they open the form.  It is not fool proof because user 1 may have numerous edits leaving time for user 2 to enter the form, make a quick change and then exit leaving user 1 in a position of failure.  I also suggested that instead of just failing the save, that I re-acquire the data and allow the user to continue making changes and save.  That is still being considered, but was initially rejected by the users stating that it was too complex.  Remember when your parents got their first computer and trying to explain to them what a click was or asking if the application was minimized?

What happens in many cases is that a user opens the edit form to view the data without any intent on saving it.  That user however, may get up to get a coffee, end up in the washroom, or even wonder down to the plant.  When finally return to their desk 2-3 hours later, they decide to actually make a change.  The client simply does not want to stop users from making necessary changes to the data because the first users ethics (or culture depending on your view) is questionable.  They have unanimously agreed that in a case like this, if the first user returns to their desk and decides to change the data, that it is acceptable for those edits to be lost.

JoeFallon1:

The database "flag" idea is called Application Locking. You may have a small app now and you could make the "mistake" of adding the flag to the table where your data resides. That may work today but when your app grows you could wind up regretting it.

If this was a concern I would agree, however, this is a re-write of an application that has been in production for over 8 years.  In that 8 years, I have not had to touch the source.  In theory I agree with you, but I do not expect this application to ever grow in size.

Application locking was my first consideration, but before I implemented anything I wanted to ask the community if there was alternatives that I haven't thought of.  It appears from the responses that I was probably on the same track that others would have ventured.

Thanks for all the responses.

Kevin

ajj3085 replied on Friday, March 14, 2008

meierk:
It is not my place to tell the user what is reasonable or not.  I merely provide them with my professional opinion and recommendation and then implement what they are willing to pay for.  I stand by my comment about their request being reasonable. 


I agree.  You had to clearly explain the options and risks and make a recomendation, but in the end your job is to deliver what they want.

meierk:
What happens in many cases is that a user opens the edit form to view the data without any intent on saving it.  That user however, may get up to get a coffee, end up in the washroom, or even wonder down to the plant.  When finally return to their desk 2-3 hours later, they decide to actually make a change.  The client simply does not want to stop users from making necessary changes to the data because the first users ethics (or culture depending on your view) is questionable.  They have unanimously agreed that in a case like this, if the first user returns to their desk and decides to change the data, that it is acceptable for those edits to be lost.

Ahh.  This is a bit different.  Would they accept needing to click a button to "unlock" the edit form.   That is, by default the form opens, but everything is readonly.  They may click a button that allows editing.  At that point you can get an exclusive lock, or a non-exclusive lock (which is your warning message).

meierk:
If this was a concern I would agree, however, this is a re-write of an application that has been in production for over 8 years.  In that 8 years, I have not had to touch the source.  In theory I agree with you, but I do not expect this application to ever grow in size.

Well, it sounds to me you're adding a feature that wasn't there before, correct?  I think an independant locking is the best way to go; you can add support for similar locking on other forms in the future, and, moer imporantly, your locking logic is seperated more from your business logic.  That will be a pretty significant benefit.

meierk:
Application locking was my first consideration, but before I implemented anything I wanted to ask the community if there was alternatives that I haven't thought of.  It appears from the responses that I was probably on the same track that others would have ventured.

Ya, I think there's pretty much only one way to handle this.  Recording a lock in the database.  Whether or not the lock prevents other users from editing or not is another matter, but trying to get clients talking to each other directly is going to be a HUGE PITA.

Good luck, and let us know more about your end solution and how it turns out!

meierk replied on Friday, March 14, 2008

ajj3085:

Ahh.  This is a bit different.  Would they accept needing to click a button to "unlock" the edit form.   That is, by default the form opens, but everything is readonly.  They may click a button that allows editing.  At that point you can get an exclusive lock, or a non-exclusive lock (which is your warning message).

That is an interesting idea.  I'll pass that option on to my client.


Thanks,

Kevin

zinovate replied on Friday, March 14, 2008

We have had the same issues with other apps we have built. It is not an easy one to overcome. There is no perfect solution without implamenting a ton of architecture, including client to client notifaction of record lock releases.

One word of warning: If you use this scheme, don't for get that the user that first started editing the data thinks they have exclusive access to the data since they were not warned that the record was locked. You will want to be sure they fully underssand the posible impact.

Like I said, there is no perfect solution.

ajj3085 replied on Friday, March 14, 2008

zinovate:
including client to client notifaction of record lock releases.


There are easier ways to get other clients informed that don't involve client to client notifications.  I don't think I'd ever suggest that as a strategy, since that becomes a maintence and configuration nightmare.

zinovate replied on Friday, March 14, 2008

Like I said, there is no perfect solution. We have embeded IM in our app so it was an easy addition.

SomeGuy replied on Monday, March 17, 2008

Have you considered having the forms time out? Kind of like Enterprise Manager does with queries. The form can either close, be made read only, or re-load with fresh data.

In your 'washroom' use case above, what do your users want to happen to the first user? Are they not allowed to make changes if changes have been made since they opened the form? Will their changes still overwrite, but now the subsequent users were 'warned' about that possibility and will have to check back later to see if their changes were overwritten?

 

meierk replied on Monday, March 17, 2008

SomeGuy:

Have you considered having the forms time out? Kind of like Enterprise Manager does with queries. The form can either close, be made read only, or re-load with fresh data.



I offered that solution already, but it was rejected as there as times the users will be entering data and will get a sales call that preoccupies them for extended periods of time.

Basically what it boils down to is that the client does not really want the system to "act" or perform actions for them.  They want total control and just want the system to do the best it can to inform them that another user "may" be editing the same data.  From that point on it is in their hands.  If it is urgent that they be able to edit the data, they will walk down the hall and ask other users to get out for a few minutes.  It does not have to be fool proof, but it would help if they new the names of other users that may have the same data open.

I have decided to attempt to implement a broadcast type service so if anyone has any links or source they wish to share I would appreciate it.  In this case, whenever the user opens the edit form (or potentially for saves as well), the application will broadcast the username and record id across the network - like MSN Messenger.  Whenever a user first opens the edit form, the application will listen to the broadcasts and enumerate the list to determine if the data is already open somewhere else.  I could potentially start a thread in the form so that throughout the duration that the user has the form open, they could be informed of any changes to the data that effectively invalidates their view.

Thx,
Kevin

ajj3085 replied on Monday, March 17, 2008

Well, like I said, you're better off recording data in the database and polling that at various times.  Adding a timestamp column to the database will give you a quick indicator if the record has changed as well. 

If you already don't have peer to peer communications, going the "broadcast" route is going to be far more time consuming, difficult to setup and maintain, and probably more error prone.  As a first question, how will you know the IPs of the clients to broadcast information anyway?  You also have to map IPs to user ids so that you can tell who's who.  Like I said, it will be easier to record who has opened the form in the database, and have clients check that data.

Good luck.
Andy

SomeGuy replied on Monday, March 17, 2008

meierk:
SomeGuy:

Have you considered having the forms time out? Kind of like Enterprise Manager does with queries. The form can either close, be made read only, or re-load with fresh data.



I offered that solution already, but it was rejected as there as times the users will be entering data and will get a sales call that preoccupies them for extended periods of time.


But will the system still overwrite intervening changes when the user returns? I would be most worried about maintaining data integrity and avoiding data loss.

I also see a lot of extra work/wasted time with users trying to track down other users who (inadvertantly) leave forms open.

I see leaving forms open as the action/behavior to be avoided and subsequently 'punished' (by having their changes discarded if the underlying data changed). At least they are the only user who has to recreate their changes and they are informed of that fact when they try to save it, rather than (possibly) multiple users losing their changes (even if they were warned beforhand) and also not having a practical way to know if they lost their changes.

 

Justin replied on Tuesday, March 18, 2008

In the application I work with we have had to address this same issue and honestly any concurrent business app will probably need to deal with it in one way or another.

First the ideal which in our next version we may try to attempt: The source control model, whereby a business entity treated just like source code, that is when opened for editing it is "checked out" and "checked in" when saved, if someone else checks in between another users check in that last check in goes through a "merge" presenting the differences and letting the user resolve anything that could not be automatically merged. This of course is not trivial and what difference/merging means would most likely be different per entity and need to be implemented differently for each with perhaps some similar rules for properties and child objects etc. Honestly this goes with version control for the business entity which our users want as well to see a history of changes and be able to rollback, again basically the same functions we as developers demand for our prized source code business users demand of their prized business data, not unreasonable but not easy. This model also solves many issues of offline editing.

Now for the way we have implemented currently, which is the "exclusive locking". First our app has a concept of "Session" in the database, this is table that maintains a user session just like and instead of an ASP session. We use our own session management so that it may be used directly in our database with foreign keys etc. This is key to doing application level locks reliably and makes it trivial to monitor user activity. You may question performance of maintaining a session table in your apps database, but so far with SQL 2005 we have successfully tested with nearly 2500 concurrent users under load. Each entity that needs locks gets a SessionID (GUID) foreign key to the Session table, if another session attempts to open the entity for editing they are denied. In our experience users DO NOT really want to be allowed to edit and overwrite while another user is making changes unless it would be handled properly with merging, this causes way to many issues especially as the system grows in size, they would rather be denied update with the ability to go read only until the lock is freed. If a session is orphaned from the system due to say a network disconnection our session timeout mechanism clears the locks, the timeout is adjustable and is usually 15 to 60 minutes just like an ASP session with the app doing a "heartbeat" to keep the session alive that is slightly less than the timeout. The administration area of our app has a place to manually clear locks should the need arise and sessions themselves can be cleared forcefully kicking a user( and consequently clearing any locks). Note with a reliable session mechanism integrated other things start to become more trivial such as security checks, since the SessionID is basically a secure token representing an authenticated user.

Just my input based on a system that has been working for years and proven to scale up to a few thousand concurrent users.

ajj3085 replied on Thursday, March 13, 2008

Well, I think the easiest way to go is still with a locking system, but the lock doesn't prevent editing.  The technique you describe is going to be much, much harder.  So if the record is "locked" you'll load up a field with that value and display a message.  In addition, you can build another BO that allows you to poll to see if a user still has a lock.  Of course its possible that more than one user can open the edit form as well, so you may have multiple user names to display.

tetranz replied on Thursday, March 13, 2008

I might be stating the obvious but if you go the lock way, make sure it's an indivisable test and set.

This is not reliable:

If lock flag on Order is false Then
    Set lock flag on Order to true
End If

I've done this by using a unique database index. Each lockable object has a unique id (probably the order id). To grab the lock, try to insert that id into the lock table which enforces uniqueness on that column. If the insert succeeds then you now have the lock, if not then someone else has it.
 
Good luck
Ross

Copyright (c) Marimer LLC