views:

192

answers:

3

I am currently working on a C# windows form with linq-to-sql that selects a row from a SQL table and then allows the user to edit the fields in that row. This form will be used by multiple users at a time and I would like to have it so when someone has selected a row and is currently entering data in the form (with which the row will be updated once the user hits a button on the form) no one else may select that row. Once the user hits the button to execute an update query the updated row should be selectable again. Essentially the process is:

user selects row-->row becomes unselectable by other users-->user modifies row fields values-->user submits and row updates-->row becomes selectable again.

Is there some sort of existing functionality I can use to accomplish this or will I need to create a table that tracks which rows are unselectable and have the application query that table before selecting rows (ie query table to see if row is unavailable, if not insert rowid user is editing, then delete row after user has submitted changes)?

+3  A: 

What you are trying to do is enforce non-optimistic concurrency on the resources you are accessing (in this case, database rows).

Generally, the data models in .NET only support optimistic concurrency (if you are supporting concurrency at all, that is), and most database programming that you see nowadays follows the same trend.

That being said, you should probably look at an optimistic concurrency model, where you have a timestamp of some sort (whether it be a binary value or a date, something that changes with each update to the record) which is compared when you want to perform the update operation.

If the id and the timestamp match, the operation succeeds, otherwise, it informs the user someone else has updated the record and should reload the data and try to update the record again.

It should be noted that this pattern scales much, much better than any non-optimistic patterns.

That being said, if you truly want a non-optimistic pattern, you have to fake it.

As mentioned above, the .NET data provider models do not support non-optimistic concurrency. When a user begins the operation, it would check a field ("locked" or something like that) to see if the record is currently locked. If it is locked, then you don't allow the user to open the form to modify the data. If it is NOT locked, then you allow the user to open the form and modify the data.

There are two important things that you have to do here. The first is to set the "locked" field to true, to indicate that you have a lock.

The second is to make sure you are operating in a transaction when comparing the value and updating the locked field. If you don't, then the possibility of two clients updating the field at the same time exist, which you don't want.

The best way to encapsulate this is to have a stored procedure which takes the id of the record you want to lock.

It then performs an update, like so:

--- Update the table.
update <table> set locked = 1 where id = @id and locked = 0

--- Return the rowcount.
return @@rowcount

It should be noted that the check on the "locked" field is very important, as it allows the @@rowcount value to be either 0 or non-zero (depending on whether or not your ids are unique) which you can evaluate to false in the zero case and true in the non-zero case in your application code.

Then, in your application code, you complete the transaction, and if the value is true, you open the form and allow the user to edit, otherwise, you inform them they have to wait.

When the user is done saving the record, they set the "locked" bit back to zero, again, in a transaction with the other save operations.

The one glaring drawback to this is the fact that if your application crashes after the user has locked the record, it will never become unlocked, and you will have to manually go and remove the "locked" bit.

With transactions and optimistic concurrency, you don't have to worry about any of this, so I would again, strongly urge you to not use the non-optimistic concurrency model.

casperOne
+2  A: 

Linq to sql has several different concurrency options, but not like you describe. When handling the save you can set RefereshMode to KeepCurrentValues, OverwriteCurrentValues, KeepChanges, or set options at the field level with the [UpdateCheck] attribute to Never, Always, WhenChanged.

You would need to write your own code to lock the records like you are referring to. Another option might be to try and save the record, catch any concurrency exceptions, and ask the user what they would like to do (thus setting the refresh mode listed above).

thekaido
A: 

By default, LINQ-To-SQL handles concurrency on it's own. When data is updated back to the database, LINQ-To-SQL will check the original values against the current values in the database. If the original values that LINQ-To-SQL fetched are different from the current values in the database, the update will fail, signaling that the data has been updated by another means. The different options are what "thekaido" talked about.

The SubmitChanges method of the DataContext will throw a ChangeConflictException if the row was changed since LINQ-To-SQL fetched the row.

Eclipsed4utoo