I have a situation where two persons might work on the same order (stored in an MS SQL database) from two different computers. To prevent data loss in the case where one would save his copy of the order first, and then a little later the second would save his copy and overwrite the first, I've added a check against the lastSaved field (datetime) before saving.
The code looks roughly like this:
private bool orderIsChangedByOtherUser(Order localOrderCopy)
{
// Look up fresh version of the order from the DB
Order databaseOrder = orderService.GetByOrderId(localOrderCopy.Id);
if (databaseOrder != null &&
databaseOrder.LastSaved > localOrderCopy.LastSaved)
{
return true;
}
else
{
return false;
}
}
This works for most of the time, but I have found one small bug.
If orderIsChangedByOtherUser returns false, the local copy will have its lastSaved updated to the current time and then be persisted to the database. The value of lastSaved in the local copy and the DB should now be the same. However, if orderIsChangedByOtherUser is run again, it sometimes returns true even though no other user has made changes to the DB.
When debugging in Visual Studio, databaseOrder.LastSaved and localOrderCopy.LastSaved appear to have the same value, but when looking closer they some times differ by a few milliseconds.
I found this article with a short notice on the millisecond precision for datetime in SQL:
Another problem is that SQL Server stores DATETIME with a precision of 3.33 milliseconds (0. 00333 seconds).
The solution I could think of for this problem, is to compare the two datetimes and consider them equal if they differ by less than say 10 milliseconds.
My question to you is then: are there any better/safer ways to compare two datetime values in MS SQL to see if they are exactly the same?
YOu have to make sure your time precisions line up - this is mostly doable by having the proper logic on the C# side to actually reduce the precision under that which is native in the DateTime object - basically make suer you have for example timestamps always in Seconds, not lower, across all layers.
If you do that properly, the timestamp across all layers will be immediately comparable.
Whilst you are within SQL 2005 and before the accuracy issue will always be there, never more accurate than 1/300th of a second, or 3.33ms.
Regardless of the lack of accuracy you are programming a flawed race condition where both users can still write to the database in quick sucession but both be considered sucessful. The lack of accuracy increases the chances of it occuring, as long as the check and subsequent writes occured within the same 3-4 ms.
Any attempt to check followed by a write suffers this problem, and you either have to accept the consequences of optimistic locking, change the locking to pessemistic or implement some form of semaphore type strategy to handle the locking properly.
You could use a timestamp field to check last edit date rather than a datetime field? (In SQL 2008 this is now RowVersion)
There is another way to do this potentially.
Instead of passing in the "Last Saved" from the local machine, modify the UPDATE stored procedure. Assign LastSaved = getdate() Then return the value of LastSaved (and any other results, such as IDs), and update the LastSaved time at the client with that result.
There are two obvious advantages to this. One is that your DateTime accuracy is preserved. The other is that the date and time are now consistent with the server, rather than suffering from any network latency and local clock drift issues.
We usually use auto generated SPs for CRUD operations, and the tables usually run "Created/LastUpdated" and "CreatedBy/LastUpdatedBy" pairs where the dates are set on the server, and the "By" values are passed in, and if NULL are set to System_User
I know you said you can't change the type, but if this is only to maintain compatibility & your using 2008 you could change the
lastSaved
field toDATETIME2
(which is fully compatible withDATETIME
) and useSYSDATETIME()
both of which have much greater precision.You could add an integer revision field to your order table. Every time a user saves the order you increase the revision by one. Then its easy to check if somebody has altered the order or if the user who wants to save the order are on the latest revision.