# Sync SQL Server 2012 Data with SQL CE 3.5 SP2 over the Internet using WCF Services using MS Sync Framework v2.1
**by:** Saurabh Nandu     **in:**  [Programming](https://www.systenics.com/blogs/category/programming)     **tags:**  [*.NET*](https://www.systenics.com/blogs/tag/net) ,  [*SQL CE 3.5 SP2*](https://www.systenics.com/blogs/tag/sql-ce-35-sp2) ,  [*SQL Server*](https://www.systenics.com/blogs/tag/sql-server) ,  [*Sync Framework 2.1*](https://www.systenics.com/blogs/tag/sync-framework-21) ,  [*WCF*](https://www.systenics.com/blogs/tag/wcf) ,  [*WPF*](https://www.systenics.com/blogs/tag/wpf)
## Overview

Recently, we were working on a client project where the client required
multiple line of business Windows Based Client Applications to be able
to work offline and synchronize with the master MS SQL Database (hosted
on the internet), when internet connection is available. We faced
several challenges in building this application and I thought it would
be a good idea to blog the solution we created for the client. In this
post we have tried to build upon the samples provided by Microsoft but
explain the whole process so that its easier for others to understand
and implement this platform. This blog post does not try to explain in
detail how sync framework works, but tries to explain the creation of
WCF based web service to sync between server and clients over the
internet.

The particular client that we were working for had employees who used to
travel extensively and needed data at least refreshed daily. Internet
connection could not be guaranteed at all times so creating a web/mobile
based application was ruled out. There are significantly large number of
businesses in developing countries like India who still don’t have
access to reliable Internet connections, we have no choice but to build
Windows desktop based applications for them. The best solution for this
business problem was to create Windows based applications which would
sync data with a central server hosted on the internet.

## Technology Selection

Over the years there has been a much greater push of new technologies
built for the web and mobile devices, but there seems to be much slower
pace of development on the Windows application development side from
Microsoft. The choice of technology for building Windows based
application is WPF (Windows Presentation Framework), but the pace of
development on WPF is certainly way behind the pace of development on
the web technologies side.
The state of technologies that allow
synchronization between client and server is bad and totally confusing!
The sync framework has not advanced in ages, while there is no
alternative path presented. We are stuck to using Sync Framework v2.1,
released in last quarter of 2010 for our solution since we wish to have
a framework that provides automatic synchronization between SQL CE and
SQL Server databases. The latest, Sync Framework 4 builds upon Sync
Framework 2.1 and provides OData based synchronization so that you can
synchronize between device platforms without installing any framework on
the client platform, but it does not provide any features for automatic
sync between client and server db. If you want to use Sync Framework 4
then you need to manually handle the client side sync code. The only
highlight for this framework is that Microsoft is promoting it as the
means to synchronize with Azure cloud databases, so there are some
updated samples provided.
The other major issue with using Sync
Framework 2.1 is that it depends upon SQL CE 3.5 SP2 and does NOT work
with the latest SQL CE 4.0. For reasons best known to Microsoft they
have chosen to release SQL CE 4.0 without support for synchronization,
so we are stuck to using SQL CE 3.5 SP2. Tooling support in Visual
Studio 2012 is built to support SQL CE 4.0 and does not work with SQL CE
3.5.
On the server side, we will expose the sync
framework using WCF (Windows Communication Framework) web services. The
core reason behind this blog post is that the sample presented by
Microsoft is not well documented nor is it clear how to deploy the
sample application. In this blog post we are building upon the sample
provided by Microsoft but making things clear to understand. The default
WCF sample provided by Microsoft is built under the assumption that the
WCF service will be self-deployed, while we have modified the sample to
deploy the service in IIS.
To summarize technologies used,
on client side we will use WPF, SQL CE 3.5 Sp2 and MS Sync Framework
v2.1. On the server side we will use WCF web services, MS SQL 2012 (you
can use MS SQL 2008/Express does not matter) and MS Sync Framework v2.1.
We will be using Visual Studio 2012 and MS SQL 2012 for building this
application.

## Installing SDK’s and Frameworks

Before we begin development we need to have the following SDK’s and
Frameworks installed if they are missing:

[Microsoft Sync Framework 2.1 Software Development Kit (SDK)](http://www.microsoft.com/en-us/download/details.aspx?id=23217)  - SyncSDK-v2.1-x64-ENU.msi or SyncSDK-v2.1-x86-ENU.msi.
Its very important to remember to install the right flavor of the SDK
based on your development machine. Make sure you have the 64-bit version
installed for a 64-bit machine else the sync framework will not work
correctly.

[Microsoft SQL Server Compact 3.5 Service Pack 2 for Windows Desktop](http://www.microsoft.com/en-us/download/details.aspx?id=5783) - SSCERuntime-ENU.exe.
Please read the information provided on
the download page. The above file contains two msi installers. On a 32
bit machine only install the SSCERuntime_x86-ENU.msi file. But on a 64
bit machine you need to install BOTH SSCERuntime_x86-ENU.msi as well as
SSCERuntime_x64-ENU.msi files as per the instructions provided. Even if
you install this version over SQL CE 4.0, both versions run side-by-side
and do not cause any issues.

[Microsoft Sync Framework v4.0 Toolkit](http://code.msdn.microsoft.com/Sync-Framework-Toolkit-4dc10f0e) (optional)
This is an optional download, we use a particular
tool provided in this toolkit for provisioning the database the support
synchronization. Please read the download notes to install the
pre-requisites required to build these samples. We have provided a
pre-built version of the tool in the downloads.

[SQL Server Compact Toolbox](http://sqlcetoolbox.codeplex.com/releases) (optional)
As mentioned above, Visual Studio 2012 does not
connect to SQL CE 3.5 SP2. This Visual Studio 2012 add-in is very useful
to help you create and manage SQL CE 3.5 databases from within Visual
Studio.

## Getting Started – Database Design

The WCF service sample that we will be providing will be independent of
any particular application. To explain the working of the code base we
are taking a simple application where there are 3 tables Products,
Employees and EmployeeSales. The Database diagram for the same is shown
below:
Figure 1: Database diagram

In the attached download file, you can extract the db folder to find a
script **SampleSync.sql** as well as backup of the database **SampleSync.bak** (Note: the database backup will only restore to SQL Server 2012, if you
have a lower version please apply the script to create the database). You
can use the script file to create the above database schema.
We
are using a very simplified database schema with just 3 table, the
schema is pretty self-explanatory. **Products** table will have
a list of products that are defined on the server and only fetched on the
client end. No updates to products will be made on the client end. **Employees** table too defines the employees on the server, no changes are made to them
on the client. Ideally it would contain Employee login information too so
that you can automatically determine which employee is making the sale. Finally,
we have the **EmployeSales** table which will record the sales
of Employee’s, this table will be updated on both client and server and will
have to be synchronized both ways.
One of the most important
factor in designing a database which needs synchronization is to make
sure that the primary key is set to **uniqueidentifier** data
type. Its crucial since records can be generated on both client and server
side and there will be issues in making sure they are unique if you do not
use **uniqueidentifier** as the data type.

## Provisioning the Database

Before we can set up synchronization we need to provision the MS SQL
server side database to support Sync Framework. There are several
different ways of doing it and its beyond the scope of this blog post.
We choose to use the **SyncSvcUtilUI** tool provided in the Sync
Toolkit v4.0 since it provides a visual interface for setting up synchronization.
If can setup synchronization using which ever method you prefer, it does not
affect the wcf service code. We have provided a pre-built version of the tool
in the **SyncSvcUtilUI** folder.

There are two scopes we wish to define for our application as given
below:

-  **ProductsEmployees** – This scope will contain the **Products** and **Employees** table and will be used to push data from
the server to the client. No changes to these tables will be synced to
the server, as per the specifications discussed above.

-  **EmployeeSales** – This scope will contain filtered scope
for **EmployeeSales** table. This scope will allow changes
to be synced both ways, to and from the server.

Start the **SyncSvcUtilUI** tool and click on **Generate or Edit Sync Configuration** link. Provide the file path to save the sync configuration file with name
**SyncConfig** and click **Next** as shown in figure
2.

Figure 2: Create a new Sync Configuration file.

On the next screen first click the **Add** button to add a new
targeted database. Provide the **Config Name** as **SyncSample**, **Db Server** as your SQL Server name, **Db Name** as **SyncSample** and then select **Windows Authentication** or **SQL Authentication** based on your SQL Server configuration.
Most important, after you enter the settings click on the **Save** button, if you do not click on the save button then the wizard will not
save the values if you just click the **Next** button. Once the
database setting is saved click on the **Next** button. Figure
3 shows the database settings.

Figure 3: Add database

On the Select Scope screen first click on the Add button to add a new
scope. We need to define the two scopes as defined above, so provide the
Scope Name as **ProductsEmployees** for the first scope and select
**Enable Apply Bulk Procedures** checkbox and click **Save** button as seen in Figure 4. The reason why we select Enable Bulk Apply procedures
is that it speeds up the synchronization process significantly when we use
Bulk Procedures, you could alternatively leave it unchecked if that’s your
requirement, it does not affect our services implementation.

Figure 4: Add ProductsEmployees scope

On the same screen click Add button again to add another scope. Provide
the Scope Name as **EmployeeSales** and select **Enable Apply Bulk Procedures** checkbox and click **Save** button as seen in Figure 5. Then
click the **Next** button to proceed further.

Figure 5: Add EployeeSales scope

On the** Select Sync Tables** screen, first select **ProductsEmployees
**from the **Select Scope** dropdown. Then click the **Add/Edit** button to open the popup. Select the **SyncSample** database
from **Select Database** dropdown. From the list of tables select
**Products** and **Employees** table and click **Ok** to close the pop-up and add the tables to the scope.

Figure 6: Add tables to ProductsEmployees scope

Change the Select Scope dropdown to **EmployeeSales** and click
on the **Add/Edit** button again to open table select pop-up.
Select **SyncSample** database from the **Select Database** dropdown to populate the tables list. Select the **EmployeeSales** table from the Tables list and click **Ok** to close the pop-up
and add the table to scope. Click **Next** to continue after
adding tables to both the scope. The last screen shows the XML generated which
defines the configuration made by us. Click on the **Finish** button to save the scope configuration and close this wizard.
Figure 7 shows the table added to the scope.

Figure 7: Define Tables for Scope

On the main application screen now select **Provision or Deprovision** to start a new Wizard. On the Settings screen, select the SyncSample.config
file that was created previously. This step will actually run scripts on the
database to provision our tables to support synchronization. Once the file
is loaded, select **ProductEmployees** from the **Select Sync Scope** dropdown and click **Next** button. This will automatically
start modifying the database and the status will be shown on the screen. Once
the process finishes click on Back button again so that we can provision the
other scope too. In case you get an “Invalid Parameter” error while provisioning,
please move the sync config file created in steps above to the root drive
and then follow the process again. There is a bug in this tool, where if there
are any spaces in the path to the sync config file the provisioning fails
(*internally this UI application is passing parameters to the command
line syncsvcutil application and they are not encapsulating string
parameters in quotations, hence if the path contains spaces the
command line applications throws an error.*). Figure 8 shows the sync scope selection.

Figure 8: Select sync scope for provision

Once the previous provisioning is completed, select **EmployeesSales** from **Select Sync Scope** dropdown and click **Next** button to start provisioning for this scope. Once the provisioning is complete,
click **Finish** to close the wizard and then close the **SyncSvcUtilUI** application. Our database provisioning process is complete, you can examine
the database in SQL management studio to find extra tables and triggers added
to track any changes that are made to the synced tables. This completes the
database provisioning step. Figure 9 shows the completed provisioning step.

Figure 9: Finish Sync Scope provisioning

## Creating Visual Studio 2012 Projects

### 1) Service Interface

Fire up Visual Studio 2012 and start a new Project of type Class
Library. Enter the project name as **ServiceInterface** and the
solution name as **SyncSample** and click **OK** to create the project.

Figure 10: Create new Console Project

The **ServiceInterface** project will contain a single Interface
which will be implemented by both the WCF Web service as well as the client
proxy will use the service interface to invoke the web service manually. The
interface can also be defined in the web service project and does not need
a special project defined (as shown in the Microsoft Samples), but then you
would have to reference the web service assembly in your client project exposing
your business logic to the client machine which is not a good security decision.

In the solution explorer select the default **Class1.cs** and
delete it. Then right-click the project name in the solution explorer and
select **Add** –> Add **New Item** from the context
menu. Select **Interface** from Add new Item dialog and provide
the file name **ISyncService.cs** and click **OK** to create the new interface.
Before we get started with coding
the interface we need to add references to couple of assemblies. In
solution explorer, right click **References** and select **Add Reference** from the context menu. Select **Framework** under **Assemblies**, to list all the framework assemblies. Then check assemblies **System.ServiceModel** and **System.Runtime.Serialization** to select them.

Next, we need to references to the Sync Framework
assemblies.  Its important to note that Sync Framework needs
platform specific assemblies so if you are working on a 64-bit machine
you need to references 64 bit assemblies or if you are on a 32 bit
machine then you need to use 32 bit assemblies. Its important to ensure
that you check the deployment web server and build platform specific
assemblies, else the Sync Framework will not work.
Select Browse
on the **Add Reference** dialog, navigate to the path where the
Sync Framework is installed to reference **Microsoft.Synchronization.dll** and **Microsoft.Synchronization.Data.dll**. On a 64 bit
machine these assemblies can be found in the **C:\Program Files (x86)\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\x64** and**
C:\Program Files (x86)\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\ADO.NET\V3.1\x64** folders respectively. If you go one level up you would find the x86 folder
containing the 32-bit version of the assemblies. If you are developing on
a 32 bit machine then look within the **C:\Program Files\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\x86** and **C:\Program Files\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\ADO.NET\V3.1\x86** folders to find these assemblies. Click **OK** to reference
the two assemblies in your project.

Now copy paste code listing 1 below into the ISyncSerivce.cs interface,
save the file and compile the project.

```
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Data;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization;

namespace ServiceInterface
&lbrace;
[ServiceContract(SessionMode = SessionMode.Required)] // Important attribute
[ServiceKnownType(typeof(SyncIdFormatGroup))]
[ServiceKnownType(typeof(DbSyncContext))]
[ServiceKnownType(typeof(SyncSchema))]
[ServiceKnownType(typeof(WebSyncFaultException))]
[ServiceKnownType(typeof(SyncBatchParameters))]
[ServiceKnownType(typeof(GetChangesParameters))]
public interface ISyncService
&lbrace;
// Defines the first method to call when using SessionMode
[OperationContract(IsInitiating = true)]
void Initialize(string scopeName);

[OperationContract]
DbSyncScopeDescription GetScopeDescription();

[OperationContract]
void BeginSession(SyncProviderPosition position);

[OperationContract]
SyncBatchParameters GetKnowledge();

[OperationContract]
GetChangesParameters GetChanges(uint batchSize,
SyncKnowledge destinationKnowledge);

[OperationContract]
SyncSessionStatistics ApplyChanges(ConflictResolutionPolicy
resolutionPolicy, ChangeBatch sourceChanges, object changeData);

[OperationContract]
bool HasUploadedBatchFile(string batchFileid, string remotePeerId);

[OperationContract]
void UploadBatchFile(string batchFileid, byte[] batchFile,
string remotePeerId);

[OperationContract]
byte[] DownloadBatchFile(string batchFileId);

[OperationContract]
void EndSession();

//Indicates the last method to call when use SessionMode
[OperationContract(IsTerminating = true)]
void Cleanup();
&rbrace;

[DataContract]
public class SyncBatchParameters
&lbrace;
[DataMember]
public SyncKnowledge DestinationKnowledge;

[DataMember]
public uint BatchSize;
&rbrace;

[DataContract]
[KnownType(typeof(DataSet))]
public class GetChangesParameters
&lbrace;
[DataMember]
public object DataRetriever;

[DataMember]
public ChangeBatch ChangeBatch;
&rbrace;

[DataContract]
public class WebSyncFaultException
&lbrace;
public string message;
public Exception innerException;

public WebSyncFaultException(string message, Exception innerException)
&lbrace;
this.message = message;
this.innerException = innerException;
&rbrace;

[DataMember]
public string Message
&lbrace;
get
&lbrace;
return this.message;
&rbrace;

set
&lbrace;
this.message = value;
&rbrace;
&rbrace;

[DataMember]
public Exception InnerException
&lbrace;
get
&lbrace;
return this.innerException;
&rbrace;

set
&lbrace;
this.innerException = value;
&rbrace;
&rbrace;
&rbrace;
&rbrace;

```

Listing 1: ISyncService.cs

The code for Listing 1 is from the sample provided by Microsoft, it’s a
standard interface definition with methods defined which match the
methods called during Synchronization. One key point to note here is the **[ServiceContract(SessionMode = SessionMode.Required)]** attribute on line 13. This attribute forces the web service to enable sessions
on server, but its different from ASP.NET sessions. Instances of the WCF web
service class which will implement the **ISyncService** interface
will remain in memory and will service the same client call. This way objects
that are created on the server side in the **Initialize** method,
will remain active on the server to respond to further service calls until
the **CleanUp** method is called by the client explicitly. Another
benefit of enabling Sessions is that WCF guarantees the method calls will
respond in the order they were called, making the service behave like a native
class rather than a stateless service. The obvious drawback of this is that
service instances end up taking more memory on the server while they are executing
as compared to stateless services. But this seems to be the only way to get
Sync Framework to work on the internet transparently.
Besides the **ISyncService** interface there are 3 data classes
defined which are basically used to serialize data and exceptions over the
wire.

### 2) Web Service Project

In solution explorer right click the solution name and **Add –> New Project** from the context menu. Select the **WCF Service Application** template, provide the project name as **SyncService** and click
**OK** to add the new project to our solution. In solution explorer
select and delete the default IService1.cs as well as Service1.svc files.
Again in the solution explorer right click **References** and
select **Add Reference** from the context menu. In the **Add Reference** dialog select **Solution** – > **Projects** and select the **ServiceInterface** checkbox to add reference
to the project.
Select Browse on the **Add Reference** dialog, navigate to the path where the Sync Framework is installed to reference
**Microsoft.Synchronization.dll,** **Microsoft.Synchronization.Data.dll **and **Microsoft.Synchronization.Data.SqlServer.dll
**. On a 64 bit machine these assemblies can be found in the **C:\Program Files (x86)\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\x64** and**
C:\Program Files (x86)\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\ADO.NET\V3.1\x64** folders respectively. If you go one level up you would find the x86 folder
containing the 32-bit version of the assemblies. If you are developing on
a 32 bit machine then look within the **C:\Program Files\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\x86** and **C:\Program Files\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\ADO.NET\V3.1\x86** folders to find these assemblies. Click **OK** to reference
the assemblies in your project.

Once the references have been added we need to reference the database in
the web.config file. In solution explorer, double click on the
web.config file to open it and add the connection string section to it
as shown in Listing 2. We are using Windows authentication to connect to
the local SQL Server instance, you can modify the connection string to
correctly point to your database server.

```
<connectionStrings>
<add name="DBConnection" connectionString="Data Source=.;Initial Catalog=SyncSample;
Persist Security Info=True;Integrated Security=True;"
providerName="System.Data.SqlClient"/>
</connectionStrings>

```

Listing 2: Connection String setting in web.config file.

Once database connection has been set, let’s add code to this project.
Right click the project name in solution explorer and select **Add** –> **Class**. Provide the class name as **ServerSynchronizationHelper.cs** and click **OK **to create the class. This class is a
helper class used to connect with the SQL Server database. Copy paste
the code from listing 3 below to populate the code for the class. The
code for this class is also extracted from the Microsoft Sample, but its
refined to remove all CE client provider’s reference. The code below is
a simple class which creates and configures an instance of **SqlSyncProvider** object using the provided connection string and scope name.

```
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.IO;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization.Data.SqlServer;
using System.Configuration;

namespace SyncService
&lbrace;
public class ServerSynchronizationHelper
&lbrace;
string conString =
ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString;
/// <summary>
/// Configure the SqlSyncprovider.  Note that this method assumes you have a direct
/// conection to the server as this is more of a design time use case vs. runtime
/// use case.  We think of provisioning the server as something that occurs before
/// an application is deployed whereas provisioning the client is somethng that
/// happens during runtime (on intitial sync) after the application is deployed.
/// </summary>
/// <param name="hostName"></param>
/// <returns></returns>
public SqlSyncProvider ConfigureSqlSyncProvider(string scopeName)
&lbrace;

SqlSyncProvider provider = new SqlSyncProvider();
provider.ScopeName = scopeName;
provider.Connection = new SqlConnection(conString);

//create anew scope description and add the appropriate tables to this scope
DbSyncScopeDescription scopeDesc = new DbSyncScopeDescription(scopeName);

//class to be used to provision the scope defined above
SqlSyncScopeProvisioning serverConfig =
new SqlSyncScopeProvisioning((SqlConnection)provider.Connection);

//determine if this scope already exists on the server and if not go ahead
//and provision
if (!serverConfig.ScopeExists(scopeName))
&lbrace;

//note that it is important to call this after the tables have been added
//to the scope
serverConfig.PopulateFromScopeDescription(scopeDesc);

//indicate that the base table already exists and does not need to be created
serverConfig.SetCreateTableDefault(DbSyncCreationOption.Skip);

//provision the server
serverConfig.Apply();
&rbrace;

return provider;
&rbrace;

&rbrace;
&rbrace;

```

Listing 3: ServerSynchronizationHelper.cs source code

Finally, we need to add the WCF Web service to our project. Right click
the project name in solution explorer and select **Add** –>
**New Item**. In the Add New Item dialog select **WCF Service** and provide the service name as **SqlWebSyncService.svc** and
click **OK** to create the WCF web service.Cope paste the code
from listing 4 below into the code for the service. This code is again from
the Microsoft Sample and it implements the ISyncService contract we defined
above. We also need to delete the auto created **ISqlWebSyncService.cs** interface from solution explorer, since we are not using it.

```
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization.Data.SqlServer;
using System.IO;
using System.ServiceModel;
using System.Data.SqlClient;
using ServiceInterface;
using System.ServiceModel.Activation;

namespace SyncService
&lbrace;
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class SqlWebSyncService : ISyncService
&lbrace;
protected bool isProxyToCompactDatabase;
protected SqlSyncProvider sqlProvider;

protected DirectoryInfo sessionBatchingDirectory = null;
protected Dictionary<string, string> batchIdToFileMapper;
int batchCount = 0;

public void Initialize(string scopeName)
&lbrace;
ServerSynchronizationHelper helper = new ServerSynchronizationHelper();
this.sqlProvider = helper.ConfigureSqlSyncProvider(scopeName);
this.batchIdToFileMapper = new Dictionary<string, string>();
&rbrace;

public Microsoft.Synchronization.Data.DbSyncScopeDescription GetScopeDescription()
&lbrace;
Log("GetSchema: &lbrace;0&rbrace;", this.sqlProvider.Connection.ConnectionString);

DbSyncScopeDescription scopeDesc =
SqlSyncDescriptionBuilder.GetDescriptionForScope(
this.sqlProvider.ScopeName, (SqlConnection)this.sqlProvider.Connection);
return scopeDesc;
&rbrace;

public void BeginSession(Microsoft.Synchronization.SyncProviderPosition position)
&lbrace;
Log("*****************************************************************");
Log("******************** New Sync Session ***************************");
Log("*****************************************************************");
Log("BeginSession: ScopeName: &lbrace;0&rbrace;, Position: &lbrace;1&rbrace;",
this.sqlProvider.ScopeName, position);
//Clean the mapper for each session.
this.batchIdToFileMapper = new Dictionary<string, string>();

this.sqlProvider.BeginSession(position, null/*SyncSessionContext*/);
this.batchCount = 0;
&rbrace;

public SyncBatchParameters GetKnowledge()
&lbrace;
Log("GetSyncBatchParameters: &lbrace;0&rbrace;", this.sqlProvider.Connection.ConnectionString);
SyncBatchParameters destParameters = new SyncBatchParameters();
this.sqlProvider.GetSyncBatchParameters(out destParameters.BatchSize,
out destParameters.DestinationKnowledge);
return destParameters;
&rbrace;

public GetChangesParameters GetChanges(uint batchSize,
Microsoft.Synchronization.SyncKnowledge destinationKnowledge)
&lbrace;
Log("GetChangeBatch: &lbrace;0&rbrace;", this.sqlProvider.Connection.ConnectionString);
GetChangesParameters changesWrapper = new GetChangesParameters();
changesWrapper.ChangeBatch = this.sqlProvider.GetChangeBatch(batchSize,
destinationKnowledge, out changesWrapper.DataRetriever);

DbSyncContext context = changesWrapper.DataRetriever as DbSyncContext;
//Check to see if data is batched
if (context != null && context.IsDataBatched)
&lbrace;
Log("GetChangeBatch: Data Batched. Current Batch #:&lbrace;0&rbrace;", ++this.batchCount);
//Dont send the file location info. Just send the file name
string fileName = new FileInfo(context.BatchFileName).Name;
this.batchIdToFileMapper[fileName] = context.BatchFileName;
context.BatchFileName = fileName;
&rbrace;
return changesWrapper;
&rbrace;

public Microsoft.Synchronization.SyncSessionStatistics ApplyChanges(
Microsoft.Synchronization.ConflictResolutionPolicy resolutionPolicy,
Microsoft.Synchronization.ChangeBatch sourceChanges, object changeData)
&lbrace;
Log("ProcessChangeBatch: &lbrace;0&rbrace;", this.sqlProvider.Connection.ConnectionString);

DbSyncContext dataRetriever = changeData as DbSyncContext;

if (dataRetriever != null && dataRetriever.IsDataBatched)
&lbrace;
string remotePeerId = dataRetriever.MadeWithKnowledge.ReplicaId.ToString();
//Data is batched. The client should have uploaded this file to us prior to
//calling ApplyChanges.
//So look for it.
//The Id would be the DbSyncContext.BatchFileName which is just the batch
//file name without the complete path
string localBatchFileName = null;
if (!this.batchIdToFileMapper.TryGetValue(dataRetriever.BatchFileName,
out localBatchFileName))
&lbrace;
//Service has not received this file. Throw exception
throw new FaultException<WebSyncFaultException>(
new WebSyncFaultException("No batch file uploaded for id " +
dataRetriever.BatchFileName,
null));
&rbrace;
dataRetriever.BatchFileName = localBatchFileName;
&rbrace;

SyncSessionStatistics sessionStatistics = new SyncSessionStatistics();
this.sqlProvider.ProcessChangeBatch(resolutionPolicy, sourceChanges,
changeData,
new SyncCallbacks(), sessionStatistics);
return sessionStatistics;
&rbrace;

public bool HasUploadedBatchFile(String batchFileId, string remotePeerId)
&lbrace;
this.CheckAndCreateBatchingDirectory(remotePeerId);

//The batchFileId is the fileName without the path information in it.
FileInfo fileInfo = new FileInfo(Path.Combine(
this.sessionBatchingDirectory.FullName,
batchFileId));
if (fileInfo.Exists && !this.batchIdToFileMapper.ContainsKey(batchFileId))
&lbrace;
//If file exists but is not in the memory id to location mapper
//then add it to the mapping
this.batchIdToFileMapper.Add(batchFileId, fileInfo.FullName);
&rbrace;
//Check to see if the proxy has already uploaded this file to the service
return fileInfo.Exists;
&rbrace;

public void UploadBatchFile(string batchFileId, byte[] batchContents,
string remotePeerId)
&lbrace;
Log("UploadBatchFile: &lbrace;0&rbrace;", this.sqlProvider.Connection.ConnectionString);
try
&lbrace;
if (HasUploadedBatchFile(batchFileId, remotePeerId))
&lbrace;
//Service has already received this file. So dont save it again.
return;
&rbrace;

//Service hasnt seen the file yet so save it.
String localFileLocation = Path.Combine(
sessionBatchingDirectory.FullName, batchFileId);
FileStream fs = new FileStream(localFileLocation,
FileMode.Create, FileAccess.Write);
using (fs)
&lbrace;
fs.Write(batchContents, 0, batchContents.Length);
&rbrace;
//Save this Id to file location mapping in the mapper object
this.batchIdToFileMapper[batchFileId] = localFileLocation;
&rbrace;
catch (Exception e)
&lbrace;
throw new FaultException<WebSyncFaultException>(
new WebSyncFaultException("Unable to save batch file.", e));
&rbrace;
&rbrace;

public byte[] DownloadBatchFile(string batchFileId)
&lbrace;
try
&lbrace;
Log("DownloadBatchFile: &lbrace;0&rbrace;",
this.sqlProvider.Connection.ConnectionString);
Stream localFileStream = null;

string localBatchFileName = null;

if (!this.batchIdToFileMapper.TryGetValue(batchFileId,
out localBatchFileName))
&lbrace;
throw new FaultException<WebSyncFaultException>(
new WebSyncFaultException("Unable to retrieve batch file for id."
+ batchFileId, null));
&rbrace;

using (localFileStream = new FileStream(localBatchFileName,
FileMode.Open, FileAccess.Read))
&lbrace;
byte[] contents = new byte[localFileStream.Length];
localFileStream.Read(contents, 0, contents.Length);
return contents;
&rbrace;
&rbrace;
catch (Exception e)
&lbrace;
throw new FaultException<WebSyncFaultException>(
new WebSyncFaultException("Unable to read batch file for id " +
batchFileId, e));
&rbrace;
&rbrace;

public void EndSession()
&lbrace;
Log("EndSession: &lbrace;0&rbrace;", this.sqlProvider.Connection.ConnectionString);
Log("*****************************************************************");
Log("******************** End Sync Session ***************************");
Log("*****************************************************************");
this.sqlProvider.EndSession(null);
Log("");
&rbrace;

public void Cleanup()
&lbrace;
this.sqlProvider = null;
//Delete all file in the temp session directory
if (sessionBatchingDirectory != null)
&lbrace;
sessionBatchingDirectory.Refresh();

if (sessionBatchingDirectory.Exists)
&lbrace;
try
&lbrace;
sessionBatchingDirectory.Delete(true);
&rbrace;
catch
&lbrace;
//Ignore
&rbrace;
&rbrace;
&rbrace;
&rbrace;

protected void Log(string p, params object[] paramArgs)
&lbrace;
Console.WriteLine(p, paramArgs);
&rbrace;

private void CheckAndCreateBatchingDirectory(string remotePeerId)
&lbrace;
//Check to see if we have temp directory for this session.
if (sessionBatchingDirectory == null)
&lbrace;
//Generate a unique Id for the directory
//We use the peer id of the store enumerating the changes so that the
//local temp directory is same for a given source
//across sync sessions. This enables us to restart a failed sync by not
//downloading already received files.
string sessionDir = Path.Combine(this.sqlProvider.BatchingDirectory,
"WebSync_" + remotePeerId);
sessionBatchingDirectory = new DirectoryInfo(sessionDir);
//Create the directory if it doesnt exist.
if (!sessionBatchingDirectory.Exists)
&lbrace;
sessionBatchingDirectory.Create();
&rbrace;
&rbrace;
&rbrace;

&rbrace;
&rbrace;

```

Listing 4: SqlWebSyncService.Svc.cs

We are not going to explain the above code in detail since its provided
in the Microsoft samples. You can build the project to test that all the
code complies correctly without any errors. We need to deploy the
service into a virtual directory before we start its configuration. You
can either use IIS Express or choose to deploy the service into a
virtual directory. We choose to have the service deployed in its own
virtual directory, right click the project name in solution explorer and
select **Properties** from the context menu. In the properties
screen choose Web and then remove **Use IIS Express** checkbox
and click on **Create Virtual Directory** button to create new
Virtual Directory and then click on **Save** to save the settings..
See figure 11 for the properties screen settings.

Figure 11: Create Virtual Directory

Now click on Build item

We now need to add the web service configuration to the web.config file,
open the web.config file from solution explorer and add the following
section as shown in listing 5 to configure the web service.

```
<system.serviceModel>
<services>
<service behaviorConfiguration="SyncService.SyncServiceBehavior"
name="SyncService.SqlWebSyncService">
<endpoint address="" binding="wsHttpBinding"
bindingConfiguration="largeMessageHttpBinding"
contract="ServiceInterface.ISyncService">
<identity>
<!--Change this when hosting on server-->
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<host>
<baseAddresses>
<!--Change this when hosting on server-->
<add baseAddress="http://localhost/SyncService/SqlWebSyncService.svc"/>
</baseAddresses>
</host>
</service>
</services>
<bindings>
<wsHttpBinding>

<binding name="largeMessageHttpBinding" maxReceivedMessageSize="10485760">
<readerQuotas maxArrayLength="10485760"/>
<security mode="None" />
<reliableSession enabled="true" />
</binding>
</wsHttpBinding>
</bindings>
<behaviors>

<serviceBehaviors>
<behavior name="SyncService.SyncServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
</system.serviceModel>

```

Listing 5: system.serviceModel settings to be added in web.config file

The above config section configures the end points for the service. You
will have to make some updates to point to the right URL of the service
once you deploy on the server. One important point to note here is that
for enabling Session support in the web service we have to enable **reliableSession** WCF module. This completes the changes to the wcf service.  Its now
ready to synchronize against any client providers, as you can see from the
above codebase, the WCF service is pretty generic and can be used in any kind
of synchronization project.
One important step is to make sure
that the codebase is compiled in either x86 or x64 based on the platform
you plan to deploy this service. We have described this step below, but
in case you are just copying the web services codebase ensure to make it
compile specific to your deployment platform.

### 3) WPF Client

We now add the last part of this project, right click the solution name
in solution explorer and select **Add** –> **Add New Project** from the context menu. Select** WPF Application** from the**
Add New Project** dialog. Provide the project name as **SyncClient** and click
**OK** to create the new project. Again in the solution explorer
right click **References** and select **Add Reference** from the context menu. In the **Add Reference** dialog select
**Solution** – > **Projects** and select the
**ServiceInterface** checkbox to add reference to the project.
Then select **Framework** under **Assemblies**, to list all the framework assemblies. Then check assemblies **System.ServiceModel**, **System.ServiceModel.Channels** and **System.Runtime.Serialization** to select them.
Now select Browse on the **Add Reference** dialog, navigate to the path where the Sync Framework is installed to reference
**Microsoft.Synchronization.dll,** **Microsoft.Synchronization.Data.dll **and **Microsoft.Synchronization.Data.SqlCE.dll
**. On a 64 bit machine these assemblies can be found in the **C:\Program Files (x86)\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\x64** and**
C:\Program Files (x86)\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\ADO.NET\V3.1\x64** folders respectively. If you go one level up you would find the x86 folder
containing the 32-bit version of the assemblies. If you are developing on
a 32 bit machine then look within the **C:\Program Files\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\x86** and **C:\Program Files\Microsoft SDKs\Microsoft Sync
Framework\2.1\Runtime\ADO.NET\V3.1\x86** folders to find these assemblies. We also need to reference SQL CE assembly
**System.Data.SqlServerCe.dll**, click on **Browse** again and navigate to the **C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Desktop** folder to select the assembly. This assembly is also platform specific so
ensure that you select the right version based on the client platform. Click
**OK** to reference the assemblies in your project.

Let’s start adding code to our WPF project, right-click the SyncClient
project in solution explorer and select Add – > Class from the
context menu. Give the class name as RelationalProviderProxy.cs and
click ADD to add the class to the project. Copy paste the code as shown
below in listing 6 for the RelationalProviderProxy class.

```
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Synchronization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.Synchronization.Data;
using System.IO;
using ServiceInterface;

namespace SyncClient
&lbrace;
public class RelationalProviderProxy : KnowledgeSyncProvider, IDisposable
&lbrace;
protected SyncIdFormatGroup idFormatGroup;
protected string scopeName;
protected DirectoryInfo localBatchingDirectory;
protected bool disposed = false;
ISyncService proxy;
protected string serviceURL;

private string batchingDirectory =
Environment.ExpandEnvironmentVariables("%TEMP%");

public string BatchingDirectory
&lbrace;
get &lbrace; return batchingDirectory; &rbrace;
set
&lbrace;
if (string.IsNullOrEmpty(value))
&lbrace;
throw new
ArgumentException("value cannot be null or empty");
&rbrace;
try
&lbrace;
Uri uri = new Uri(value);
if (!uri.IsFile || uri.IsUnc)
&lbrace;
throw new
ArgumentException("value must be a local directory");
&rbrace;
batchingDirectory = value;
&rbrace;
catch (Exception e)
&lbrace;
throw new ArgumentException("Invalid batching directory.", e);
&rbrace;
&rbrace;
&rbrace;

public RelationalProviderProxy(string scopeName, string serviceURL)
&lbrace;
this.scopeName = scopeName;
this.serviceURL = serviceURL;
this.CreateProxy();
this.proxy.Initialize(scopeName);

&rbrace;

public override void BeginSession(SyncProviderPosition position,
SyncSessionContext syncSessionContext)
&lbrace;
this.proxy.BeginSession(position);
&rbrace;

public override void EndSession(SyncSessionContext syncSessionContext)
&lbrace;
proxy.EndSession();
if (this.localBatchingDirectory != null)
&lbrace;
this.localBatchingDirectory.Refresh();

if (this.localBatchingDirectory.Exists)
&lbrace;
//Cleanup batching releated files from this session
this.localBatchingDirectory.Delete(true);
&rbrace;
&rbrace;
&rbrace;

public override ChangeBatch GetChangeBatch(uint batchSize,
SyncKnowledge destinationKnowledge, out object changeDataRetriever)
&lbrace;
GetChangesParameters changesWrapper = proxy.GetChanges(batchSize,
destinationKnowledge);
//Retrieve the ChangeDataRetriever and the ChangeBatch
changeDataRetriever = changesWrapper.DataRetriever;

DbSyncContext context = changeDataRetriever as DbSyncContext;
//Check to see if the data is batched.
if (context != null && context.IsDataBatched)
&lbrace;
if (this.localBatchingDirectory == null)
&lbrace;
//Retrieve the remote peer id from the
//MadeWithKnowledge.ReplicaId. MadeWithKnowledge is the local
//knowledge of the peer that is enumerating the changes.
string remotePeerId = context.MadeWithKnowledge.ReplicaId.ToString();

//Generate a unique Id for the directory.
//We use the peer id of the store enumerating the changes so
//that the local temp directory is same for a given source
//across sync sessions. This enables us to restart a failed sync
//by not downloading already received files.
string sessionDir =
Path.Combine(this.batchingDirectory, "WebSync_" + remotePeerId);
this.localBatchingDirectory = new DirectoryInfo(sessionDir);
//Create the directory if it doesnt exist.
if (!this.localBatchingDirectory.Exists)
&lbrace;
this.localBatchingDirectory.Create();
&rbrace;
&rbrace;

string localFileName = Path.Combine(this.localBatchingDirectory.FullName,
context.BatchFileName);
FileInfo localFileInfo = new FileInfo(localFileName);

//Download the file only if doesnt exist
if (!localFileInfo.Exists)
&lbrace;
byte[] remoteFileContents =
this.proxy.DownloadBatchFile(context.BatchFileName);
using (FileStream localFileStream = new FileStream(localFileName,
FileMode.Create, FileAccess.Write))
&lbrace;
localFileStream.Write(remoteFileContents, 0, remoteFileContents.Length);
&rbrace;
&rbrace;
//Set DbSyncContext.Batchfile name to the new local file name
context.BatchFileName = localFileName;
&rbrace;

return changesWrapper.ChangeBatch;
&rbrace;

public override FullEnumerationChangeBatch
GetFullEnumerationChangeBatch(uint batchSize, SyncId lowerEnumerationBound,
SyncKnowledge knowledgeForDataRetrieval, out object changeDataRetriever)
&lbrace;
throw new NotImplementedException();
&rbrace;

public override void GetSyncBatchParameters(out uint batchSize,
out SyncKnowledge knowledge)
&lbrace;
SyncBatchParameters wrapper = proxy.GetKnowledge();
batchSize = wrapper.BatchSize;
knowledge = wrapper.DestinationKnowledge;
&rbrace;

public override SyncIdFormatGroup IdFormats
&lbrace;
get
&lbrace;
if (idFormatGroup == null)
&lbrace;
idFormatGroup = new SyncIdFormatGroup();

//
//1 byte change unit id (Harmonica default before flexible ids)
//
idFormatGroup.ChangeUnitIdFormat.IsVariableLength = false;
idFormatGroup.ChangeUnitIdFormat.Length = 1;

//
// Guid replica id
//
idFormatGroup.ReplicaIdFormat.IsVariableLength = false;
idFormatGroup.ReplicaIdFormat.Length = 16;

//
// Sync global id for item ids
//
idFormatGroup.ItemIdFormat.IsVariableLength = true;
idFormatGroup.ItemIdFormat.Length = 10 * 1024;
&rbrace;

return idFormatGroup;
&rbrace;
&rbrace;

public override void ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy,
ChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks,
SyncSessionStatistics sessionStatistics)
&lbrace;
DbSyncContext context = changeDataRetriever as DbSyncContext;
if (context != null && context.IsDataBatched)
&lbrace;
string fileName = new FileInfo(context.BatchFileName).Name;

//Retrieve the remote peer id from the MadeWithKnowledge.ReplicaId.
//MadeWithKnowledge is the local knowledge of the peer
//that is enumerating the changes.
string peerId = context.MadeWithKnowledge.ReplicaId.ToString();

//Check to see if service already has this file
if (!this.proxy.HasUploadedBatchFile(fileName, peerId))
&lbrace;
//Upload this file to remote service
FileStream stream = new FileStream(context.BatchFileName, FileMode.Open,
FileAccess.Read);
byte[] contents = new byte[stream.Length];
using (stream)
&lbrace;
stream.Read(contents, 0, contents.Length);
&rbrace;
this.proxy.UploadBatchFile(fileName, contents, peerId);
&rbrace;

context.BatchFileName = fileName;
&rbrace;

SyncSessionStatistics stats = this.proxy.ApplyChanges(resolutionPolicy,
sourceChanges, changeDataRetriever);
sessionStatistics.ChangesApplied += stats.ChangesApplied;
sessionStatistics.ChangesFailed += stats.ChangesFailed;
&rbrace;

public override void ProcessFullEnumerationChangeBatch(
ConflictResolutionPolicy resolutionPolicy, FullEnumerationChangeBatch sourceChanges,
object changeDataRetriever, SyncCallbacks syncCallbacks,
SyncSessionStatistics sessionStatistics)
&lbrace;
throw new NotImplementedException();
&rbrace;

protected void CreateProxy()
&lbrace;
WSHttpBinding binding = new WSHttpBinding();
//BasicHttpBinding binding = new BasicHttpBinding();
binding.ReaderQuotas.MaxArrayLength = 10485760;
binding.MaxReceivedMessageSize = 10485760;
binding.Security.Mode = SecurityMode.None;
binding.ReliableSession.Enabled = true;
ChannelFactory<ISyncService> factory =
new ChannelFactory<ISyncService>(binding, new EndpointAddress(serviceURL));
this.proxy = factory.CreateChannel();
&rbrace;

public DbSyncScopeDescription GetScopeDescription()
&lbrace;
return this.proxy.GetScopeDescription();
&rbrace;

~RelationalProviderProxy()
&lbrace;
Dispose(false);
&rbrace;

public virtual void Dispose()
&lbrace;
Dispose(true);
GC.SuppressFinalize(this);
&rbrace;

protected virtual void Dispose(bool disposing)
&lbrace;
if (!this.disposed)
&lbrace;
if (disposing)
&lbrace;
if (proxy != null)
&lbrace;
CloseProxy();
&rbrace;
&rbrace;

disposed = true;
&rbrace;
&rbrace;
/// <summary>
///  Clean up and close proxy.
/// </summary>
public virtual void CloseProxy()
&lbrace;
if (proxy != null)
&lbrace;
proxy.Cleanup();
ICommunicationObject channel = proxy as ICommunicationObject;
if (channel != null)
&lbrace;
try
&lbrace;
channel.Close();
&rbrace;
catch (TimeoutException)
&lbrace;
channel.Abort();
&rbrace;
catch (CommunicationException)
&lbrace;
channel.Abort();
&rbrace;
&rbrace;

proxy = null;
&rbrace;
&rbrace;
&rbrace;
&rbrace;

```

Listing 6: RelationalProviderProxy.cs

The code for the **RelationalProviderProxy** class is also from
the Microsoft sample, it provides the client side proxy for invoking the WCF
web service. Since the WCF web service uses sessions, we can’t rely on Visual
Studio to auto create a proxy class for us. Instead we define our own client
proxy class which will ultimately call the wcf service and perform synchronization.

Next we need to create a SQL CE 3.5 database for the client application.
We use the SQL Server Compact Toolbox, Visual Studio plug-in to create
the SqlCE database. In visual studio got to **View** menu –>
**Other Windows** –> **SQL Server Compact Toolbox** to bring up the toolbox window. Right click on **SQL Server Compact Data Connections** and select **Add SQL Server Compact 3.5 Connection** as shown
in figure 12 below.

Figure 12: Add SQL Server Compact 3.5 Connection

On the **Add SQL Server Compact 3.5** dialog click on **Create** and provide the path to the project to store the SQL CE database in the
project folder and the database file name as **ClientDB**.
We have provided the path **C:\SystenicsBlog\SyncSample\SyncSample\SyncClient\ClientDB.sdf** to create the database. You can optionally choose to provide a password
for your CE database. Click **OK** to create the database, you
will also see the database listed in the **SQL Server Compact Toolbox** window. You should follow these steps every time you wish to reset the client
database, creating a new one so that it can synchronize with the server and
fetch fresh schema.

We need to add the path to the client database and the Sync Web Service
in the settings file so that we do not hardcode them. In solution
explorer expand the **Properties** section under the **SyncClient** project and double click on **Settings.settings** item to open
it. Add a new setting named **DBConnection**, **Type** as **Connection String**, **Scope** as **Application** and **Value** as **Data
Source=C:\SystenicsBlog\SyncSample\SyncSample\SyncClient\ClientDB.sdf**. Add another setting named **ServiceUrl**, **Type** as **string**, **Scope** as **Application** and **Value** as **http://localhost/SyncService/SqlWebSyncService.svc**. Save the settings, these need to be updated at time of client
deployment to point to the folder where the SQL CE database exists and
the URL where the WCF service has been hosted.

Figure 13: Settings.settings file

Once the settings are configured, right click the solution explorer and
choose **Add** – > **Class** from the context
menu. Provide the class name as **SynchronizationHelper.cs** and click **Add** to add a new class to the project. Copy paste
the code from listing 7 below to populate the code for the **SynchronizationHelper** class.

```
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Data.SqlServerCe;
using System.IO;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Data.SqlServerCe;
using Microsoft.Synchronization.Data;
using System.Threading;

namespace SyncClient
&lbrace;
class SyncResults
&lbrace;
private SyncOperationStatistics stats;

public SyncOperationStatistics Stats
&lbrace;
get &lbrace; return stats; &rbrace;
set &lbrace; stats = value; &rbrace;
&rbrace;
private string message;

public string Message
&lbrace;
get &lbrace; return message; &rbrace;
set &lbrace; message = value; &rbrace;
&rbrace;
public SyncResults(string message,
SyncOperationStatistics stats)
&lbrace;
this.message = message;
this.stats = stats;
&rbrace;
&rbrace;

class SynchronizationHelper
&lbrace;
public void SynchronizeAsync(
System.ComponentModel.BackgroundWorker worker,
System.ComponentModel.DoWorkEventArgs e)
&lbrace;

SyncResults results;
results = SynchronizeProductsEmployees();
worker.ReportProgress(0, results);
results = SynchronizeEmployeeSales();
worker.ReportProgress(0, results);
&rbrace;

public SyncResults SynchronizeProductsEmployees()
&lbrace;
SyncResults results = null;
// Create the SQL CE Sync Provider for the given scope name
SqlCeSyncProvider localProvider =
ConfigureCESyncProvider("ProductsEmployees");
// Create the remote provider for the given scope name
RelationalProviderProxy destinationProxy =
new RelationalProviderProxy("ProductsEmployees",
Properties.Settings.Default.ServiceUrl);
// Synchronize and collect results
results = new SyncResults("ProductsEmployees",
SynchronizeProviders(localProvider, destinationProxy,
SyncDirectionOrder.Download));
destinationProxy.Dispose();
localProvider.Dispose();
return results;
&rbrace;

public SyncResults SynchronizeEmployeeSales()
&lbrace;
SyncResults results = null;
// Create the SQL CE Sync Provider for the given scope name
SqlCeSyncProvider localProvider =
ConfigureCESyncProvider("EmployeeSales");
// Create the remote provider for the given scope name
RelationalProviderProxy destinationProxy =
new RelationalProviderProxy("EmployeeSales",
Properties.Settings.Default.ServiceUrl);
// Synchronize and collect results
results = new SyncResults("EmployeeSales",
SynchronizeProviders(localProvider,
destinationProxy, SyncDirectionOrder.UploadAndDownload));
destinationProxy.Dispose();
localProvider.Dispose();
return results;
&rbrace;

/// <summary>
/// Utility function that configures a CE provider
/// </summary>
/// <param name="sqlCeConnection"></param>
/// <returns></returns>
private SqlCeSyncProvider ConfigureCESyncProvider(string scopeName)
&lbrace;
SqlCeConnection sqlCeConnection = new SqlCeConnection(
Properties.Settings.Default.DbConnection);
SqlCeSyncProvider provider = new SqlCeSyncProvider();
//Set the scope name
provider.ScopeName = scopeName;

//Set the connection.
provider.Connection = sqlCeConnection;

//Thats it. We are done configuring the CE provider.
return provider;
&rbrace;

/// <summary>
/// Utility function that will create a SyncOrchestrator and
/// synchronize the two passed in providers
/// </summary>
/// <param name="localProvider">Local store provider</param>
/// <param name="remoteProvider">Remote store provider</param>
/// <returns></returns>
private SyncOperationStatistics SynchronizeProviders(
KnowledgeSyncProvider localProvider, KnowledgeSyncProvider remoteProvider,
SyncDirectionOrder direction)
&lbrace;
SyncOrchestrator orchestrator = new SyncOrchestrator();
orchestrator.LocalProvider = localProvider;
orchestrator.RemoteProvider = remoteProvider;
orchestrator.Direction = direction;
//Check to see if any provider is a SqlCe provider and if it needs schema
CheckIfProviderNeedsSchema(localProvider as SqlCeSyncProvider);
SyncOperationStatistics stats = orchestrator.Synchronize();
return stats;
&rbrace;

/// <summary>
/// Check to see if the passed in CE provider needs Schema from server
/// </summary>
/// <param name="localProvider"></param>
private void CheckIfProviderNeedsSchema(SqlCeSyncProvider localProvider)
&lbrace;

if (localProvider != null)
&lbrace;
SqlCeConnection ceConn = (SqlCeConnection)localProvider.Connection;
SqlCeSyncScopeProvisioning ceConfig =
new SqlCeSyncScopeProvisioning(ceConn);

string scopeName = localProvider.ScopeName;

//if the scope does not exist in this store
if (!ceConfig.ScopeExists(scopeName))
&lbrace;
//create a reference to the server proxy
RelationalProviderProxy serverProxy =
new RelationalProviderProxy(scopeName,
Properties.Settings.Default.ServiceUrl);

//retrieve the scope description from the server
DbSyncScopeDescription scopeDesc = serverProxy.GetScopeDescription();
serverProxy.Dispose();

//use scope description from server to intitialize the client
ceConfig.PopulateFromScopeDescription(scopeDesc);
ceConfig.Apply();
&rbrace;
&rbrace;
&rbrace;
&rbrace;
&rbrace;

```

Listing 7: SynchronizationHelper class

The code for listing 7 is also from the Microsoft sample, but we have
modified it to just have synchronization code for syncing our CE
database with the remote database calling the web service. There are two
methods defined **SynchronizeProductsEmployees** and **SynchronizeEmployeeSales** which are used to synchronize our two different scopes that we defined at
the start of the blog. The first method only downloads the changes from the
server while the second one uploads as well as downloads data between both
databases. These methods are invoked from the **BackgroundWorker** thread defined in the **MainWindow** class listed below so
that synchronization is performed without blocking the UI thread making the
WPF application responsive while synchronization is in progress. The Main
WPF app is a simple application with two buttons and a richtextbox control,
since the goal here is not to demo WPF capabilities but to showcase synchronization.
The **Synchronize** button will synchronize the client SQL CE
database with the SQL Server database, bringing down botch schema and data
from the server as well as update the main server with any changes made in
the **EmployeeSales** table. The **Insert Record** button inserts a new record in **EmployeeSales** table in the
local SQL CE database (Please ensure that you have first clicked on **Synchronize** before inserting data else error will be thrown for inserting data into
non existent table). After inserting records into the local SQL CE database
you can click the **Synchronize** button again to see the newly
added record being synchronized with the server, you can then manually check
the MS SQL database to find the newly added record. If you make data changes
to the MS SQL database and synchronize, you should find the data updated in
the SQL CE database locally. This makes the synchronize process pretty pain
free once you have it all setup correctly. The listing below show the code
for MainWindows.xaml.cs file.

```
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using Microsoft.Synchronization;
using System.Data.SqlServerCe;

namespace SyncClient
&lbrace;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
&lbrace;
// Create a BackgroundWorker object to synchronize without blocking
// the UI thread
private System.ComponentModel.BackgroundWorker backgroundWorker1;
public MainWindow()
&lbrace;
InitializeComponent();
this.backgroundWorker1 =
new System.ComponentModel.BackgroundWorker();
this.backgroundWorker1.WorkerReportsProgress = true;
// Register the various BackgroundWorker events
this.backgroundWorker1.DoWork
+= new System.ComponentModel.DoWorkEventHandler(
this.backgroundWorker1_DoWork);
this.backgroundWorker1.ProgressChanged
+= new System.ComponentModel.ProgressChangedEventHandler(
this.backgroundWorker1_ProgressChanged);
this.backgroundWorker1.RunWorkerCompleted
+= new System.ComponentModel.RunWorkerCompletedEventHandler(
this.backgroundWorker1_RunWorkerCompleted);

&rbrace;

private void btnSync_Click(object sender, RoutedEventArgs e)
&lbrace;
// Check if sync is already in progress
if (!backgroundWorker1.IsBusy)
&lbrace;

msg.AppendText(
"Starting Data Synchronization Process...\r\n Please wait till the process compeletes.\r\n");
Application.Current.MainWindow.Cursor = Cursors.Wait;

SynchronizationHelper syncHelper = new SynchronizationHelper();
// Start synchronization
backgroundWorker1.RunWorkerAsync(syncHelper);
&rbrace;
&rbrace;

private void btnInsert_Click(object sender, RoutedEventArgs e)
&lbrace;
// Insert a test value in local CE database
using (SqlCeConnection con =
new SqlCeConnection(Properties.Settings.Default.DbConnection))
&lbrace;
SqlCeCommand cmd =
new SqlCeCommand("INSERT INTO [EmployeeSales] ([Id],[ProductId],[EmployeeId],[Unit],[Date])"
+" Values (@Id,@ProductId,@EmployeeId,@Unit,@Date)");
cmd.Parameters.Add("@Id", Guid.NewGuid());
//Hardcoded ProductId
cmd.Parameters.Add("@ProductId", "F4FC70E6-5DC7-4982-95F0-32E806E21D84");
//Hardcoded EmployeeId
cmd.Parameters.Add("@EmployeeId", "0A5F6AD1-F345-48D4-B6D6-0850A7C930C9");
// Hardcoded Units
cmd.Parameters.Add("@Unit", 10);
cmd.Parameters.Add("@Date", DateTime.Now);
cmd.Connection = con;
try
&lbrace;
con.Open();
cmd.ExecuteNonQuery();
con.Close();
&rbrace;
catch (Exception ed)
&lbrace;

MessageBox.Show("Error Occurred while inserting.\r\n" + ed.ToString());
&rbrace;

&rbrace;
&rbrace;

// Method to start syncthonization in background
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
&lbrace;
System.ComponentModel.BackgroundWorker worker;
worker = (System.ComponentModel.BackgroundWorker)sender;
SynchronizationHelper syncHelper = (SynchronizationHelper)e.Argument;
syncHelper.SynchronizeAsync(worker, e);

&rbrace;

//Method to report synchronization progress
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
&lbrace;
SyncResults results = (SyncResults)e.UserState;
if (results != null)
&lbrace;
DisplayStats(results);
&rbrace;
&rbrace;

//Method runs when synchtonization compeletes
private void backgroundWorker1_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e)
&lbrace;
Application.Current.MainWindow.Cursor = Cursors.Arrow;
if (e.Error != null)
&lbrace;
msg.AppendText(
"An Error has occurred. Please try synchronization later.\r\nThe error:" + e.Error.Message);
MessageBox.Show("Error: " + e.Error.Message);
&rbrace;
else
&lbrace;
msg.AppendText("Synchronization Finished Successfully\r\n");
MessageBox.Show("Finished Synchronization");
&rbrace;
&rbrace;

// Method to format the SyncResults for display
private void DisplayStats(SyncResults results)
&lbrace;

TimeSpan diff =
results.Stats.SyncEndTime.Subtract(results.Stats.SyncStartTime);

msg.AppendText(
string.Format(
"&lbrace;4&rbrace;:  - Total Time To Synchronize = &lbrace;0&rbrace;:&lbrace;1&rbrace;:&lbrace;2&rbrace;:&lbrace;3&rbrace;\r\nTotal Records Uploaded: &lbrace;5&rbrace;  Total Records Downloaded: &lbrace;6&rbrace;\r\n",
diff.Hours, diff.Minutes, diff.Seconds,
diff.Milliseconds, results.Message,
results.Stats.UploadChangesTotal,
results.Stats.DownloadChangesTotal));
&rbrace;

&rbrace;
&rbrace;

```

Listing 8 – MainWindows.xaml.cs

Update the c# code as well as XAML code for the default **MainWindow** class copy pasting code from listing 8 and 9. In the above listing you can
see that we have used the **BackgroundWorker** object to invoke
the WCF services in the background, as there are updates from the WCF server
we update the richtextbox with the synchronization results. Listing 9 below
shows the XAML for **MainWindow**.

```
<Window x:Class="SyncClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="btnSync" Content="Synchronize" HorizontalAlignment="Left"
Margin="54,29,0,0" VerticalAlignment="Top" Width="75" Click="btnSync_Click"/>
<Button x:Name="btnInsert" Content="Insert Record" HorizontalAlignment="Left"
Margin="288,29,0,0" VerticalAlignment="Top" Width="111" Click="btnInsert_Click"/>
<RichTextBox x:Name="msg" Margin="0,85,0.4,-0.2">
<FlowDocument>
<Paragraph>
<Run Text=""/>
</Paragraph>
</FlowDocument>
</RichTextBox>

</Grid>
</Window>

```

Listing 9 – MainWindow.XAML

This completes the project source code. You can save all the file build
and test your application. In case there are issues in synchronization
then the first step would be to ensure that your build is platform
specific i.e. x64 or x86 based on the platform of your test or
deployment machine. You can create a new build configuration for x64 in
visual studio by going to **Build** – > **Configuration Manager** menu. In the **Configuration Manager**, expand the **Active Solution Platforms** and select **<NEW>** provide the name as **x64** or **x86** as required and click **OK** and then
recompile your build to ensure it gets compiled platform specific.

## Conclusion

In this blog post we looked at building a WCF based web service to
support synchronization between MS SQL and SQL CE databases using MS
Sync Framework v2.1 over the internet. The codebase is generic enough to
ensure that you can reuse it in your projects. We also looked at the
various issues related to building such and application. I hope this
blog post helps simplify your synchronization issues.

### Source Code Download

[Click here](https://www.systenics.com/media/6260/20130401-SyncSample.zip) to download the source code for this project.

## Related pages on Systenics

-  [AI-powered listing description generation](https://www.systenics.com/ai-enhanced-listings-description-generation/)
-  [Industry solutions](https://www.systenics.com/solutions/)

## Need help implementing this for your business?

Our team helps plan, build, and scale marketplace and auction workflows.
Explore
[industry solutions](https://www.systenics.com/solutions/),
[platform products](https://www.systenics.com/products/), and
[custom development services](https://www.systenics.com/auctionworx-development-and-customization/)
tailored to your goals.

[Book a quick consultation](https://outlook.office365.com/owa/calendar/sales1@systenics.net/bookings/).