Today is June 3rd 2008 and I am writing this blog post to demonstrate a step by step guide to developing an ADO.NET Sync Services Sample Application that is using Service Based Architecture. Though I planned to first write a post on ADO.NET Sync Services as a whole from architecture perspective and then provide a step by step instructions for the sample code, but now, when I have completed the step by step instructions before the architecture document, I am posting the same.

In almost every practical case of disconnected applications architecture, the server side central database is exposed via http end points like web services. Moreover with the release of technologies like ADO.NET Data Services, it is a matter of few clicks to expose the entire data model to the external world using web services.

So I thought of writing down the steps that I took in Visual Studio 2008 to create a client windows application which will be talking to the central database server via a http WCF Service. If you are interested in the sample code as well, please feel free to get in touch with me.

  1. Open Visual Studio and create a new windows application which will store all the client specific files.
  2. Add a new class library project to the solution which will hold all the server side code.
  3. Add a third web application project to the solution which will expose a web service. The web service will be used as a broker between the client windows application and the server side class library. So also add a new WCF service to the web application project. You can delete the Default.aspx page which is created by default by Visual Studio 2008. Also since this will be Cassini based, right click the project and select properties. On the properties page, select “Web*” tab and select “Specific part” radio button. This will fix the port on which the web server will run and we can have a fixed URL to the web service. Click on save.
  4. In the windows application, add the SQL Server Table that you want to cache. You can do this by adding a LocalDatabaseCache file from the project templates. This will present you with a wizard where you will specify the SQL Server Tables. In the first form of wizard, specify the Server Connection to tell the Visual studio about the SQL Server with which you want to do the synchronization. Clicking on the drop down will present you with already known connections, if you want to specify a new connection, click on “New Connection” button adjacent to the Server Connection drop down and fill the required details.
  5. Once you specify the Server Connection details, it will automatically suggest you with a Client Connection name. This basically specifies the location and name of SQL CE database file to be created at client side.
  6. Once you specify the Client Connection details, you can see “Add” button enabled. This button allows you to select the tables on the SQL Server to be cached on the SQL CE database that you just specified in above steps. Going a little deep, this process creates a SQLCE database at client side and simply copies the schema from SQL Server to the CE database. So click on Add button, select tables that you want to cache/synchronize and click OK. Please explore other options shown if needed.
  7. The moment you finish this wizard, you will be prompted with another wizard of creating a DataSet for the SQL CE database just created. If you wish to write your own ADO.NET code skip it, otherwise choose the Tables for which you want Visual studio designer to write the ADO.NET code. For first demo, I would recommend to go through the dataset wizard rather than writing your own ADO.NET code.
  8. Now, we write the server side synchronization code into the class library created. We will do this by using the wizard again. Though we could have done it in previous steps but just to make it simple, I am doing it in this step. Double-click .sync file in the windows application. This will open the wizard that we have seen earlier. Here click on the “Advanced” button to see more options on the same page. Then on the “Server Project location” drop down select the class library project and click OK. This will transfer all the server side code from the client application to the Class Library which will be used by the web service.
  9. Add a reference of the ClassLibrary project into the web application project because web service will use it. Also add references to Microsoft.Synchronization.Data and Microsoft.Synchronization.Data.Server assemblies into the web application project.
  10. On the windows application add “service reference” and add the web service reference to the windows application.
  11. On the class library, you will see a .SyncContract.cs file which has code for an Interface and class. We will have to duplicate the same structure on the WCF files. So copy the method declarations of the interface of .SyncContract.cs file (along with [OperationContract()] declarations) and paste them to the Interface file of WCF Service (in the web application project you will see a file starting with “I” if you have not added any additional files starting with “I” letter). You can delete the default method called “DoWork()” provided by Visual Studio. Copy also the “using” declarations so that there are no build errors.
  12. Similarly copy all the methods and the private variable of the class from .SyncContract.cs file into the .svc.cs file of web service. Copy also the “using” declarations. The code also contains a constructor method. Just change the constructor name in .svc.cs file to the class name of the .svc.cs file. You can remove [System.Diagnostics.DebuggerNonUserCodeAttribute()] declarations (if present) from the methods.
  13. In the same .svc.cs file add another using statement for the class library project (e.g., using ClassLibrary1;). Try to compile and see if there are any errors. Remove any errors if I have missed any logical step.
  14. On the windows application, right click the .sync file and click “view Code”. Here, you will see one partial class already created with an empty OnInitialized() method. Create another class in same file (let us call it RemoteProviderClass) implementing the ServerSyncProvider class. Add following “using” declarations on the top of file:
  15. using Microsoft.Synchronization;
    using Microsoft.Synchronization.Data;
    using System.Data;
    using System.Collections.ObjectModel;

    And the class declaration looks similar to this:
    public partial class RemoteProviderClass:ServerSyncProvider

  16. After copying the using statements, copy only the methods (not private variable) of the .svc.cs file of web application to this file inside the new class created (RemoteProviderClass). Again, one of the methods will be a constructor, so change its name appropriately. Notice that each of the method is calling methods from a private variable whose name starts with an underscore. Declare a private variable inside the RemoteProviderClass corresponding to the proxy of the web service. The variable name should be same as the one that each method is using and starting with an underscore. Also, inside the constructor method, delete existing code and write a line to instantiate this proxy variable.
  17. Try to build the solution and you will see errors inside GetSchema() method. This is because Collection<string> is transferred as string[] array. So write code to convert the Collection into string array. Also notice the override keyword; this should be used with all methods instead of virtual keyword. The code should be similar to this:
  18. public override SyncSchema GetSchema(Collection<string> tableNames, SyncSession syncSession)
    {
    string[] tables = new string[tableNames.Count];
    tableNames.CopyTo(tables, 0);
    return this._serverSyncProvider.GetSchema(tables, syncSession);
    }

  19. Everything should compile well now.
  20. On the OnInitialized method write code to create an instance of this new class and assign to the RemoteProvider property of the sync agent. Also specify the sync direction of the tables. Here I have one table in my SQLCE database called Employee. The code looks similar to this
  21. partial void OnInitialized()
    {
    this.RemoteProvider = new ProxyForWebServiceCall();
    this.Employee.SyncDirection = SyncDirection.Snapshot;
    }

Now we will create a UI on the windows form and invoke the synchronization process on timer tick event. Calling synchronization is straightforward but calling inside a timer that is invoked every 1 second is a little bit tricky. Practically, web service might return response in 1 second, and also till web service returns back, the UI thread will be unresponsive, so we will invoke the synchronization code asynchronously and only when one web service call has returned. I will illustrate this in following steps and also show the sample code to make things more comprehensive.

  1. Open Form1 design and from data menu click on “Show Data Sources”. Drag and drop the table on the Form designer surface (in my case Employee table). Visual Studio will automatically create UI for end user editing and also write ADO.NET code for performing those operations on the local SQLCE database. Also drag and drop a timer control and set its enabled property to true and set its interval to 1000 (1 second). On the code behind of Form1 add using Microsoft.Synchronization.Data; declaration.
  2. On the code behind of Form1, declare three private variables belonging to the class of the Form. A Boolean variable to see whether we have to call the synchronization code or not. A delegate to the synchronize method of the Sync Agent class. A variable of the sync agent class. To see the class name of your sync agent navigate to the .cs file of the .sync file and see the class name of the OnInitialized() method. The declarations look similar to this:
  3. public partial class Form1 : Form
    {
    bool IsContinue = true;
    private delegate SyncStatistics SynchronizeMethod();
    LocalDataCache1SyncAgent syncAgent = new LocalDataCache1SyncAgent();

  4. Now we will write code in the timer tick event handler to invoke synchronization. Here on the very first line we will check whether IsContinue is true or not, if it is not true, this means another web service call is pending and we have to wait, so we will return. But if IsTrue allowed us to enter further into synchronization code, let us block all other ticks of timer to enter. So the following code:
  5. if (!IsContinue)
    return;
    else
    IsContinue = false;

  6. Then we will specify the synchronization direction of our global sync agent variable. I have set it to Bidirectional here. So this code:
  7. syncAgent.Employee.SyncDirection = Microsoft.Synchronization.Data.SyncDirection.Bidirectional;

  8. Next we will instantiate the delegate with the sync agent’s synchronize() method so that we can invoke it asynchronously. Then invoke it asynchronously. Notice that in the second line, we have passes a method name, MyCallBack, (which we will create in coming steps) which will be invoked once web service returns response. MyCallBack will simply set the IsContinue to true so that now timer can start for a fresh synchronization execution. So the code below:
  9. SynchronizeMethod syncMethod = new SynchronizeMethod(syncAgent.Synchronize);
    IAsyncResult result = call.BeginInvoke(new AsyncCallback(MyCallBack), null);

  10. After invoking the webs ervice, we can collect the resulty of web service by using EndInvoke. The result will be used to see if the DataGrid should be refreshed or not. If there were changes between two databases, then only form should be reloaded. So the code below:
  11. Microsoft.Synchronization.Data.SyncStatistics syncStats = syncMethod.EndInvoke(result);
    if (syncStats.TotalChangesDownloaded > 0)
    Form1_Load(null, null);

  12. And finally the callback method:
  13. private void MyCallBack(IAsyncResult result)
    {
    IsContinue = true;
    }

    Compile and run the application by setting windows application as startup project. Make changes from windows application and you can see them at SQL server and similarly make changes in SQL Server and they will be visible in Windows Application datagrid. Make sure that you do not change primary key field. This is just a start, go ahead and implement conflict resolution and try to make changes from multiple machines at same time.

I would love to see your comments/suggestions/corrections if any.

Rahul Gangwar