Concurrency

Update errors can be caused by all the usual factors—a timeout waiting for a connection, a network error, and so on—but the most common update error is a concurrency problem.

By default, update commands use optimistic concurrency, which attempts to match every value. This runs into immediate trouble when two users make overlapping changes. If you attempt to update a record, and that record no longer has the initial set of values you retrieved, the update fails with an OptimisticConcurrencyException.

There are two overall strategies you can use to deal with concurrency issues: you can try to avoid concurrency problems before they happen, or you can try to resolve concurrency issues after they occur.

To avoid concurrency problems, you need to modify your update logic. For example, you could require only a few fields to match, or just the primary key to allow last-in-wins updating. In LINQ to SQL, this behavior can't be controlled on a case-by-case basis—instead, it's determined at the table level based on the Column attributes you apply to your data class. Each Column attribute has an UpdateCheck property, which determines when matching is required on that field. Allowed values are Always (the default), Never, and WhenChanged.

You explored different concurrency strategies in Chapter 8. Table 13-1 shows how you can use the Column.UpdateCheck property to implement them with LINQ to SQL.

Table 13-1. Concurrency Strategies in LINQ to SQL

Concurrency Strategy Behavior

Implementation with LINQ to SQL

Match-all-values

Match timestamp

Last-in-wins

Merge changes

Only allow the update if no values have changed.

Only allow the update if the time-stamp hasn't changed. This is the same behavior as match-all-values, but offers better performance, particularly if you have large field values.

Always commit the update and wipe out whatever exists.

Always commit the update, unless the value you want to change has been modified.

Give every property in the data class a Column attribute that sets UpdateCheck to Always. This is the default.

Give the timestamp property in the data class a Column attribute that sets IsVersion to true. The DataContext will automatically use this value to perform its concurrency checking.

Give every property in the data class a Column attribute that sets UpdateCheck to Never.

Give every property in the data class a Column attribute that sets UpdateCheck to WhenChanged.

You can further refine these strategies by using a combination of different Column.UpdateCheck values on different columns. For example, some columns can require more stringent update checking, because changing them would change the data significantly.

If at least some of the original column values use update checking, and one of these values is changed by another user, an OptimisticConcurrencyException will be thrown when you call DataContext.SubmitChanges(). At this point, you can try to resolve the problem. You saw one example of this strategy in Chapter 10, where a grid allowed a user to confirm a change after showing the recently changed values that were currently in the database. You can accomplish the same effect with LINQ to SQL, with a bit more work. If the user agrees to apply the change, you can use the Refresh() method with RefreshMode.KeepChanges (to keep the recent changes, but apply the new changes on top) or RefreshMode.KeepCurrentValues (to replace the recent changes with all the values that are in memory for the current user). Then you're ready to apply the change by calling SubmitChanges() again.

0 0

Post a comment

  • Receive news updates via email from this site