4. Resolving Change Conflicts
Alerting users to change conflicts would be pretty
pointless if there was no way to resolve the conflicts in question.
There are a few approaches that we can take when resolving change
conflicts.
Resolving Conflicts Globally
The LINQ to SharePoint provider makes resolving conflicts a relatively painless process. The ChangeConflicts property of the DataContext object returns a reference to an object of type ChangeConflictCollection. This object provides a ResolveAll method that can be called to resolve all conflicts. What could be simpler?
The ResolveAll method has three overloads:
ResolveAll() This overload is used when the default resolution behavior is acceptable. Effectively, this is the same as calling ResolveAll with the RefreshMode parameter set to KeepChanges and the autoResolveDeletes parameter set to true.
ResolveAll(RefreshMode) This overload is used when a RefreshMode setting of KeepChanges is not appropriate.
ResolveAll(RefreshMode, Boolean)
This overload is used when delete conflicts should not be automatically
resolved. The Boolean parameter is a flag that determines whether or
not to delete conflicts that should be resolved automatically. By
allowing delete conflicts to be automatically resolved, any delete
conflicts are effectively ignored. This behavior makes sense, because
the item to be updated no longer exists, so other than notifying the
user, no other options are available. Setting this value to false will cause an InvalidOperationException to be thrown if any delete conflicts exist when ResolveAll is called.
The ResolveAll methods make use of a RefreshMode enumeration to specify how conflicts should be resolved. The enumeration has three possible values:
KeepChanges When this option is selected for the RefreshMode parameter of the ResolveAll
method, any updated values are maintained even if they conflict with
the current database values. All other values that were not
specifically updated are changed to match the current database values.
In effect, this option merges fields that were specifically updated
with the latest values in the database.
KeepCurrentValues When this option is selected for the RefreshMode parameter of the ResolveAll
method, all field values are applied regardless of whether they match
the current database values. In effect, this option disregards all
concurrent changes, overwriting them with the current state of the
object being updated.
OverwriteCurrentValues When this option is selected for the RefreshMode parameter of the ResolveAll
method, all field values are updated to reflect the current state of
the database. Any updates that do not correspond with the current state
are lost. This option disregards any changes, replacing all values with
the current values from the database.
To see the effects of calling ResolveAll, change the sample code for the Concurrent Update button to this:
retry:
try
{
dxWrite.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
//bind any conflicts to a data grid so that we can see them
dataGridView2.DataSource = dxWrite.ChangeConflicts.ToList();
dxWrite.ChangeConflicts.ResolveAll();
goto retry;
}
You’ll notice that after the ResolveAll method is called, a call to SubmitChanges
must again occur to retry the changes. Running the sample application
and clicking the Concurrent Update button now returns the same list of
change conflicts—but this time each conflict is flagged as resolved.
Also, checking the Asset Notes list using the user interface will
confirm that all updates have been applied. Try re-running this test to
see the operation of the other RefreshMode options.
Resolving Conflicts Individually
I’m sure you’ll agree that the ResolveAll
method works well when you need to resolve all conflicts en masse, but
what happens if you want to apply different rules to some of the
conflicts? Or what if you want to apply different rules to specific
fields? The LINQ to SharePoint conflict resolution model allows for
this behavior.
And what if you want to go further? What if
you want to handle change conflicts on a field-by-field basis? Again,
the LINQ to SharePoint provider allows for this.
Record Level Conflict Resolution
Along with a ResolveAll method on the ChangeConflictCollection object, which allows you to resolve all conflicts on all objects with one method call, is a Resolve method on the ObjectChangeConflict object. As mentioned earlier, the ObjectChangeConflict
object represents a change conflict for a single entity. Remember that
in relational database parlance, an entity corresponds to a record. By
selecting specific ObjectChangeConflict objects from the ChangeConflictCollection, you can apply different resolution options for each record.
For example, if you wanted to ignore any updates where the LocationCode contained Location001, you could change the preceding code sample as follows:
retry:
try
{
dxWrite.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
//bind any conflicts to a data grid so that we can see them
dataGridView2.DataSource = dxWrite.ChangeConflicts.ToList();
dxWrite.ChangeConflicts.ResolveAll();
goto retry;
}
Field Level Conflict Resolution
Each ObjectChangeConflict object has a MemberConflicts property that returns a reference to a collection of MemberChangeConflict objects. As described earlier, a MemberChangeConflict object represents a change conflict at the individual field level, and again, like the ObjectChangeConflict object, the MemberChangeConflict object has a Resolve method. The difference this time is that there are only two overloads: The first accepts a RefreshMode parameter and behaves in a similar fashion to the Resolve
method on the higher level objects. The second override accepts an
object, and rather than attempting to resolve the conflict based on the
values that have been set, it simply sets the field value to the object.
For example, if we wanted to flag fields where new
value contained the original value but a conflict had been detected, we
could do this by appending the text Disputed to the new field value. Using the preceding code sample, we could achieve this with the following:
retry:
try
{
dxWrite.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
//bind any conflicts to a data grid so that we can see them
dataGridView2.DataSource = dxWrite.ChangeConflicts.ToList();
//Select the conflicts that we're interested in
var keepChanges = (from c in dxWrite.ChangeConflicts
from m in c.MemberConflicts
where m.CurrentValue.ToString().Contains(
m.OriginalValue.ToString())
&& m.CurrentValue is string
select m).ToList();
//Resolve by appending some text
keepChanges.ForEach(m => m.Resolve(m.CurrentValue + "-Disputed"));
//Call ResolveAll to resolve everything else using the default values
dxWrite.ChangeConflicts.ResolveAll();
goto retry;
}
You can see that handling change conflicts using
LINQ to SharePoint is flexible enough to accommodate practically any
requirement.
Considerations when Setting ConflictMode
You may have noticed in the past few code samples that SubmitChanges is being called with ConflictMode.ContinueOnConflict.
This is useful when more than one item is being updated, because it
allows you to deal with all conflicts at once. Changing this value to ConflictMode.FailOnFirstConflict would allow only one conflict to be detected each time SubmitChanges was called. The code in the sample would still work, but it would do so by calling SubmitChanges many times, each time resolving one conflict.
One thing to bear in mind when resolving
conflicts is that in the time it takes to resolve a conflict, more
concurrent updates may have occurred. In heavy traffic situations where
many concurrent updates present a problem, resolving conflicts
individually may be more effective, because it takes less time to apply
changes to one item than it does to apply changes to many, so the
likelihood of more concurrent updates occurring is reduced.