Caching box, as shown in Figure 34-5.
Now when you select Next you are presented
with a new step in the wizard that allows you
to configure the way that data is synchronized
between the server and your local database
cache. In Figure 34-6 you can see that for
each table you can toggle the synchronization
mode between Incremental and Snapshot.
The former is better for tables that contain
a large quantity of data that changes
frequently; the latter is for tables that contain
small reference sets that change infrequently
and don’t require change tracking.
The other option presented in Figure 34-6 is whether to enable SQL Server change tracking. Sync
Services relies on being able to track changes to the data in order to synchronize those changes
between the server and the client. Out of the box it supports two mechanisms for doing this. You
can either enable change tracking, in which case changes on the server are automatically tracked by
the SQL Server database, or you can configure Sync Services to track changes within your database
fiGure 34-5
fiGure 34-6
750 .
chaPter 34 SynchronizATion SerViceS
tables. The former is only available with SQL Server 2008, and the latter requires additional fields,
triggers, and tables in order to provide equivalent change tracking capabilities. If you are going to be
deploying the database to SQL Server 2008, it is recommended that you enable change tracking.
When you click Finish you are prompted to confirm that you want to apply the server changes
immediately (Figure 34-7). If you’re working on a database shared by others, you may want
to review the generated scripts before allowing them to execute. For this example leave both
checkboxes checked, which will create the database scripts (including undo scripts) and add them
to your project, as well as execute them on the server database, to either enable change tracking
or to create the additional change tracking columns, triggers, and tables.
Clicking OK both persists this configuration in the form of synchronization classes and invokes a
synchronization between the server and the local data file, as shown in Figure 34-8.
fiGure 34-7 fiGure 34-8
Forcing synchronization at this point means that the newly created SQL Server Compact (SSC)
database file is populated with the correct schema and any data available on the server. The
LocalCRMDataSet is also added to your project.
If you now look at the Data Sources tool window, you will see that there is a LocalCRMDataSet
node that contains a Customer node. As you did previously, set the Customer node to Details and
the CustomerId node to None. Then drag the Customer node across onto the designer surface of the
LocalForm. The result should be a form similar to the one shown in Figure 34-9.
fiGure 34-9
synchronization services over n-Tiers .
751
Adding these components brings the same components to the design surface and the same code
to the form as when you were connecting directly to the server. The difference here is that a
CustomerTableAdapter connects to the local database instead of the server. As before, you need
to add the code to specify the CustomerId for new records in the CurrentChanged event of the
CustomerBindingSource.
The last thing you need to add to this part of the project is a mechanism to invoke the synchronization
process. Simply add a button, SynchronizeButton, to the bottom of the LocalForm and double-click
it to generate the click-event handler. Then add the following code to trigger a synchronization.
Vb
Private Sub SynchronizeButton_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs)
Handles SynchronizeButton.Click
Dim syncAgent As New CRMCacheSyncAgent()
Dim syncStats As Microsoft.Synchronization.Data.SyncStatistics =
syncAgent.Synchronize()
Me.CustomerTableAdapter.Fill(Me.LocalCRMDataSet.Customer)
End Sub
Code snippet LocalForm.vb
c#
private void SynchronizeButton_Click(object sender, EventArgs e){
var syncAgent = new CRMCacheSyncAgent();
var syncStats = syncAgent.Synchronize();
this.customerTableAdapter.Fill(this.localCRMDataSet.Customer);
}
Code snippet LocalForm.cs
Pay particular attention to the next-to-last line of this snippet, in which you use the
CustomerTableAdapter to fill the Customer table. This is important: without this line the user
interface will not reflect changes in the SSC database that have been made by the synchronization
process.
synchronization serVices oVer n-tiers
So far, the entire synchronization process is conducted within the client application with a direct
connection to the server. One of the objectives of an occasionally connected application is to be
able to synchronize data over any connection, regardless of whether it is a corporate intranet or the
public Internet. Unfortunately, with the current application you need to expose your SQL Server so
that the application can connect to it. This is clearly a security vulnerability, which you can solve by
taking a more distributed approach. Sync Services has been designed with this in mind, allowing the
server components to be isolated into a service that can be called during synchronization.
752 .
chaPter 34 SynchronizATion SerViceS
Sync Services supports separating the synchronization process so that the client application
communicates via a WCF service, instead of directly to the server database. To do this, you need to
create a WCF service that implements the four methods that makes up Sync Service, as shown in the
following IServiceCRMCacheSyncContract interface.
Vb
<ServiceContractAttribute()> _
Public Interface IServiceCRMCacheSyncContract
<OperationContract()> _
Function ApplyChanges(ByVal groupMetadata As SyncGroupMetadata, _
ByVal dataSet As DataSet, _
ByVal syncSession As SyncSession) As SyncContext
<OperationContract()> _
Function GetChanges(ByVal groupMetadata As SyncGroupMetadata, _
ByVal syncSession As SyncSession) As SyncContext
<OperationContract()> _
Function GetSchema(ByVal tableNames As Collection(Of String), _
ByVal syncSession As SyncSession) As SyncSchema
<OperationContract()> _
Function GetServerInfo(ByVal syncSession As SyncSession) As SyncServerInfo
End Interface
The WCF Service essentially acts as a remote proxy for the server provider used by Sync Service. To use
the WCF Service, you first need to add it to the client project using Add Service Reference (right-click
your project and select this option from the context menu). Then you need to set the Remote Provider
on the Sync Agent to be a new instance of the ServerSyncProviderProxy. The constructor for the
ServerSyncProviderProxy class takes a single parameter which should be the proxy class that was
generated for the WCF Service using Add Service Reference. Now, when you call Synchronize, Sync
Services will use the Remote Provider to call the methods on the WCF Service. The WCF Service will in
turn communicate with the server database carrying out the synchronization logic.
backGround synchronization
You may have noticed that when you click the synchronize button, the user interface appears to
hang until the synchronization completes. Clearly this wouldn’t be acceptable in a real-world
application, so you need to synchronize the data in the background, thereby allowing the user to
continue working. By adding a BackgroundWorker component (in the Components group in the
Toolbox) to the LocalForm, you can do this with only minimal changes to your application. The
following code illustrates how you can wire up the events of the BackgroundWorker, which has been
named bgWorker, to use the Sync Service implementation. This makes use of an additional button,
SynchronizeInBackgroundButton, that was added to the LocalForm:
Vb
Private Sub SynchronizeInBackgroundButton_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) _
Handles SynchronizeInBackgroundButton.Click
Me.SynchronizeButton.Enabled = False
Me.SynchronizeInBackgroundButton.Enabled = False
Background synchronization .
753
Me.bgWorker.RunWorkerAsync(New CRMCacheSyncAgent())
End Sub
Private Sub bgWorker_DoWork(ByVal sender As System.Object,
ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles bgWorker.DoWork
Dim syncAgent As Microsoft.Synchronization.SyncAgent =
TryCast(e.Argument, Microsoft.Synchronization.SyncAgent)
If syncAgent Is Nothing Then Return
syncAgent.Synchronize()
End Sub
Private Sub bgWorker_RunWorkerCompleted(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles bgWorker.RunWorkerCompleted
Me.CustomerTableAdapter.Fill(Me.LocalCRMDataSet.Customer)
Me.SynchronizeInBackgroundButton.Enabled = True
Me.SynchronizeButton.Enabled = True
End Sub
Code snippet LocalForm.vb
c#
private void SynchronizeInBackgroundButton_Click(object sender, EventArgs e){
this.SynchronizeButton.Enabled =false;
this.SynchronizeInBackgroundButton.Enabled = false;
this.bgWorker.RunWorkerAsync(new CRMCacheSyncAgent());
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e){
var syncAgent = e.Argument as Microsoft.Synchronization.SyncAgent;
if (syncAgent == null) return;
syncAgent.Synchronize();
}
private void bgWorker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e){
this.customerTableAdapter.Fill(this.localCRMDataSet.Customer);
this.SynchronizeInBackgroundButton.Enabled = true;
this.SynchronizeButton.Enabled = true;
}
Code snippet LocalForm.cs
In this snippet you are not reporting any progress, but Sync Services does support quite a rich event
model that you can hook into in order to report on progress. If you want to report progress via the
BackgroundWorker component, you need to enable its WorkerReportsProgress property. The
following code illustrates how you can hook into the ApplyChanges event on the client component
754 .
chaPter 34 SynchronizATion SerViceS
of Sync Services in order to report progress (in this case to a label called “SyncProgressLabel” added
to the form). Other events correspond to different points in the synchronization process.
Vb
Private Sub bgWorker_DoWork(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles bgWorker.DoWork
Dim syncAgent As Microsoft.Synchronization.SyncAgent = _
TryCast(e.Argument, Microsoft.Synchronization.SyncAgent)
If syncAgent Is Nothing Then Return
Dim clientProvider As _
Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider = _
CType(syncAgent.LocalProvider, _
Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider)
AddHandler clientProvider.SyncProgress, AddressOf SyncProgress
syncAgent.Synchronize()
End Sub
Private Sub SyncProgress(ByVal sender As Object, _
ByVal e As Microsoft.Synchronization.Data. SyncProgressEventArgs)
Dim progress = 0
If (e.GroupProgress.TotalChanges > 0) Then
progress = (e.GroupProgress.TotalChanges -
e.GroupProgress.TotalChangesPending) _
* 100 / e.GroupProgress.TotalChanges
End If
Me.bgWorker.ReportProgress(progress, e.SyncStage.ToString())
End Sub
Private Sub bgWorker_ProgressChanged(ByVal sender As Object, _
ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
Handles bgWorker.ProgressChanged
Me.SyncProgressLabel.Text = e.UserState.ToString
End Sub
Code snippet LocalForm.vb
c#
private void bgWorker_DoWork(object sender, DoWorkEventArgs e){
var syncAgent = e.Argument as Microsoft.Synchronization.SyncAgent;
if (syncAgent == null) return;
var clientProvider = syncAgent.LocalProvider as
Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider;
clientProvider.SyncProgress += SyncProgress;
syncAgent.Synchronize();
}
private void SyncProgress(object sender,
Microsoft.Synchronization.Data.SyncProgressEventArgs e){
var progress = 0;
if(e.GroupProgress.TotalChanges>0){
progress = (e.GroupProgress.TotalChanges -
e.GroupProgress.TotalChangesPending)
Client Changes .
755
*100 /e.GroupProgress.TotalChanges;
}
this.bgWorker.ReportProgress(progress, e.SyncStage.ToString());
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e){
this.SyncProgressLabel.Text = e.UserState.ToString();
}
Code snippet LocalForm.cs
client chanGes
Working through the example so far, you may have been wondering why none of the changes you
have made on the client are being synchronized to the server. If you go back to Figure 34-6, you
will recall that you selected Incremental from the top drop-down, which might lead you to believe
that changes from both the client and server will be synchronized. This is not the case and it is the
wording above this control that gives it away. For whatever reason, this control only enables you to
select options pertaining to “Data to download.” To get changes to propagate in both directions,
you have to override the default behavior for each table that is going to be synchronized. Again,
right-click the CRMCache object in the Solution Explorer and select View Code. In the following
code, we have set the SyncDirection property of the CustomerSyncTable to be bidirectional. You
may also want to do this for the ServerCRMCache item so that both synchronization mechanisms
will allow changes to propagate between client and server.
Vb
Partial Public Class CRMCacheSyncAgent
Partial Class CustomerS yncTable
Private Sub OnInitialized()
Me.SyncDirection = _
Microsoft.Synchronization.Data.SyncDirection.Bidirectional
End Sub
End Class
End Class
CRMCache.vb
c#
partial class CRMCacheSyncAgent{
partial class CustomerSyncTable{
private void OnInitialized(){
this.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Bidirectional;