2.4. Handling Concurrency Errors
As we have just seen, rows
may fail to synchronize; changes made at either end might not be
acceptable at the other end. Therefore, we add one last bit of
functionality to our demo. For each row that fails to synchronize, we
need to determine which row it was, where it was rejected (server or
client), and why it was rejected.
The possible reasons for why a row might fail to synchronize are specified by Microsoft in the ConflictType enumerator; see Table 1.
Table 1. Synchronization Errors
ConflictType | Meaning |
---|
ClientDeleteServerUpdate | The client deleted a row that the server updated |
ClientInsertServerInsert | The client and server both inserted a row that has the same primary key value |
ClientUpdateServerDelete | The server deleted a row that the client updated |
ClientUpdateServerUpdate | The client and the server updated the same row |
Note that ClientDeleteServerDelete
is not an error. Any scenario in which the client wanted the row
deleted and the row is deleted is not an error. The fact that the row
was already deleted, and may have had different values in it from what
the client thought it had, does not matter.
We start with the SyncAgent object; the object whose Synchronize method executes and monitors the entire synchronization. The SyncAgent object holds references to two SyncProvider
objects: one that it uses to communicate with the server and one that
it uses to communicate with the client. In our application, one is a SQL
Server 2005 provider and one is a SQL Server CE provider. During
synchronization, whenever a row cannot be inserted, updated, or deleted,
at either the client or the server, because of a currency conflict, the
appropriate provider raises an ApplyChangeFailed
event. To process conflicts, we need to handle that event in our
application. To illustrate, we will detect failures that occur at the
client; highly similar code would be used to detect server-side
failures.
It is the SyncAgent’s LocalProvider property object that will raise the ApplyChangeFailed event. To make this event visible to IntelliSense, we need to cast the LocalProvider to the specific class of provider that will reside in the property at runtime; normally either SqlCeClientSyncProvider or DbServerClientSyncProvider. Thus, our event signup code is:
((SqlCeClientSyncProvider)
(syncAgent.LocalProvider))
.ApplyChangeFailed +=
new EventHandler
<ApplyChangeFailedEventArgs>
(FormData_ApplyChangeFailed);
The actual handler is shown in Listing 5. The EventArgs that we are handed contains a Conflict object, which, in turn, contains the reason for the row being rejected and also the row itself. The Conflict.ConflictType property contains the reason, while the Conflict.ClientChange property contains an untyped data table that holds the rejected row.
Listing 5. The Synchronization Error Handler
void FormData_ApplyChangeFailed(
object sender,
ApplyChangeFailedEventArgs e)
{
MessageBox.Show(
string.Format(
"{1}{0}{2}.{0}{0}" +
"{3} {4}.{0}{0}" +
"{5}{0}{6}.{0}",
Environment.NewLine,
"Conflict Type:",
e.Conflict.ConflictType.ToString(),
"CustomerID:",
e.Conflict.ClientChange.Rows[0]
["CustomerID"].ToString(),
"CompanyName:",
e.Conflict.ClientChange.Rows[0]
["CompanyName"].ToString()));
}
|
Figure 17
shows the result from a scenario in which a row was inserted at the
server concurrently with a row of the same primary key being inserted at
the device.
This concludes our brief demonstration of developing a Data Synchronization Service. Figures 18 and 19 diagram our resulting Smart Device application and Data Synchronization Service, respectively.
2.5. Some Closing Notes on Data Synchronization Services
As the need arises to
add functionality to your solution, such as automatic conflict
resolution or business logic, you can do so in any of several ways,
three of which are mentioned here.
Add logic
to the client program, such as we did with our simple conflict detection
logic. This is a good solution for logic that is applicable to this
client only.
Add
logic to the service program. This is a slightly unstable solution, as
it may need to be redone if future modifications are generated by
rerunning the Configure Data Synchronization Wizard.
Derive classes that contain your functionality from the Microsoft.Synchronization
classes and use these classes in your service. This is a good solution
for logic that must be applied to the data regardless of the application
that is manipulating that data.
In summary, Data Synchronization Services do the following:
Provide a complete solution for the synchronization of data that is gathered by Occasionally Connected Applications
Allow the Smart Device application to handle synchronization failures
Encapsulate the synchronization infrastructure functionality within a SyncAgent object and its contained objects
Provide more capability than RDA (albeit by being more complex)
Differ
from Merge Replication in that they are controlled by the developer
rather than the database administrator, and this allows for more
application of business logic