LINQ to SQL has built-in support for optimistic concurrency; an entity is able to be retrieved and used in more than one operation at a time. When an entity is retrieved from the database, and then updated, if changes by another party occur in the interim, they are identified and a conflict is detected.
The mechanism supporting this feature is configured using two Column
attribute properties: IsVersion
and UpdateCheck
.
IsVersion
allows you to designate an entity class member to be used for optimistic concurrency control (OCC). Before committing a change to an entity, the data context verifies that no other transaction has modified its data. If the check reveals conflicting modifications, the committing transaction rolls back and an exception is raised.
Note
IsVersion
is not required for conflict detection. When not specified, however, the data context must retain copies of the entity’s member values and must compare the original values with the database values to detect conflicts, which is not terribly efficient.
LINQ to SQL supports multitier applications by allowing entities to be attached to a DataContext
. Such an entity may have been retrieved using a different DataContext
instance, or deserialized after being sent over the wire from another tier via a web service.
In the following example, you see that by adding a version property to the BankAccount
class (presented in the previous section), you can prevent conflicting changes from being written to the database. BankAccount
now contains a DateVersion
property of type byte[]
:
byte[] dataVersion;
[Column(
IsVersion = true,
IsDbGenerated = true,
UpdateCheck = UpdateCheck.Never)]
public byte[] DataVersion
{
get
{
return dataVersion;
}
set
{
Assign(ref dataVersion, value);
}
}
The DataVersion
value is materialized as a ROWVERSION field in the database (see Figure 29.12). The ROWVERSION data type causes the table field to be automatically updated whenever a row update occurs.
The BankingDataContextTests
class contains a test method for demonstrating conflict detection. It creates a CheckingAccount
, called beforeAccount
, and inserts it into the database.
It then retrieves the same account, this time in the scope of a new BankingDataContext
, sets its CheckbookCode
to a new value, and then updates the database.
Finally, it attaches the beforeAccount
instance to a new DataContext
and attempts to update the CheckbookCode
to a new value. See the following excerpt:
[TestMethod]
[Tag("i2")]
[ExpectedException(typeof(ChangeConflictException))]
public void ContextShouldEnforceUpdateChecking()
{
CheckingAccount beforeAccount;
using (BankingDataContext context = databaseUtility.CreateContext())
{
beforeAccount = new CheckingAccount
{
Balance = 100,
CheckbookCode = "11111"
};
context.BankAccounts.InsertOnSubmit(beforeAccount);
context.SubmitChanges();
}
using (BankingDataContext context = databaseUtility.CreateContext())
{
CheckingAccount afterAccount
= (CheckingAccount)context.BankAccounts.Where(
x => x.Id == beforeAccount.Id).First();
afterAccount.CheckbookCode = "22222";
context.SubmitChanges();
}
using (BankingDataContext context = databaseUtility.CreateContext())
{
context.BankAccounts.Attach(beforeAccount);
beforeAccount.CheckbookCode = "33333";
context.SubmitChanges();
}
}
When context.SubmitChanges
is called, a conflict is detected because the afterAccount
object, representing the same account, was updated in the database in the interim. This raises a ChangeConflictException
(see Figure 29.13), which is defined as an expected exception, using the ExpectedException
test attribute shown in the previous excerpt.
Providing a dedicated version property within your entity classes is good practice when enabling conflict detection. When not used, the data context relies solely on the Column
attribute’s UpdateCheck
property for inferring conflict detection behavior.
3.133.126.199