Sie sind auf Seite 1von 41

Programming Microsoft Synchronization Services for ADO.

NET (Devices)
.NET Compact Framework 1.0
3 out of 4 rated this helpful - Rate this topic
4/7/2010
Andy Wigley, APPAMundi Limited
June 2009
Summary

One of the most significant new features in Microsoft SQL Server Compact
Edition 3.5 SP1 is the addition of support for Microsoft Synchronization Services for
ADO.NET. This data synchronization technology lets you synchronize data from
different sources over two-tier, n-tier, and service-based architectures.
Synchronization Services provides a powerful new option for offline data caching
and data synchronization in both .NET Smart Client and mobile and embedded
applications using the .NET Compact Framework. Microsoft Visual Studio 2008 SP1
includes updates to Synchronization Services and support for SQL Server Compact
Edition 3.5 SP1 for desktop clients, and an additional update extends support to
Windows Mobile devices. Synchronization Services expands the options for offline
relational data caching in a .NET Compact Framework application beyond
approaches using Merge Replication, Remote Data Access, and custom Web
services.
Microsoft Synchronization Services for ADO.NET v1.0 Service Pack 1 (devices)
Microsoft SQL Server Compact 3.5 Service Pack 1 for Windows Mobile
Sample Code
Applies To

Microsoft SQL Server Compact Edition 3.5 SP1


Microsoft SQL Server 2008
Microsoft Visual Studio 2008 SP1
Microsoft Visual C#
Windows Mobile 5.0 and later

Microsoft .NET Compact Framework 3.5

Programming Microsoft Synchronization Services for ADO.NET


Getting Started with Synchronization Services
Understanding SQL Server 2008 Change Tracking Configuration
Limitations of Synchronization Services on Devices
Implementing Batching to Reduce Data Transfer Volumes
Handling Initial Synchronization and Expired Subscriptions
Using Compression to Reduce Data Transfer over WCF
Introduction

Microsoft Synchronization Services for ADO.NET provides a new option for relational
data synchronization in mobile applications. This paper discusses how to use the
wizards in Visual Studio 2008 SP1 to add data synchronization capabilities to a .NET
Compact Framework application to synchronize a SQL Server Compact Edition 3.5
SP1 database on the device with a SQL Server 2008 database on the server. You
learn how to configure communications between device and server over Microsoft
Windows Communication Foundation Classes (WCF) and how to use batching and
compression to improve the reliability of the application.
Programming Microsoft Synchronization Services for ADO.NET (Devices)

Synchronization Services supports snapshot, download-only, upload-only, and


bidirectional synchronization.
Snapshot synchronization is typically used to download reference data, such as a
product list, to a client. A complete copy of the data is downloaded every time that
the client synchronizes.
Download-only synchronization resembles snapshot synchronization. However, the
first time that the client synchronizes, a complete copy is downloaded and on later
synchronizations, only incremental data changes that you make on the server are
downloaded.

Upload-only synchronization is used to transfer data from client to server, such as a


sales order, created on a client. Inserts and other data changes that were made in
the client database are uploaded to the server during synchronization.
Bidirectional synchronization is typically used for data, such as customer contact
information, that can be updated at the client and server. Any conflicting changes
must be handled during synchronization.
In Synchronization Services, the database used on the client-side is SQL Server
Compact Edition 3.5 SP1. Synchronization Services provides an infrastructure to
track incremental changes in the client database. The server-side database can be
any database for which there is an ADO.NET provider that includes SQL Server
2005, SQL Server 2008, Oracle, MySQL, and many other database products. The
method for tracking changes differs from database to database. In most database
products changes typically involve adding columns to a tracked table to record
some or all of creation time, last changed time, the client ID that performed the last
modification, and the addition of tombstone tables to record deleted records.
However, the easiest way to implement change tracking in a server database is to
use SQL Server 2008 which has change tracking ability built in.
For more information about how to support change tracking on different databases,
see Microsoft Synchronization Services for ADO.NET Books Online.
Getting Started with Synchronization Services
This section of this paper discusses the development of a simple data
synchronization solution by using the Occasionally Connected Systems (OCS)
Designer included in Visual Studio 2008.
Updating Visual Studio 2008 SP1
Visual Studio 2008 SP1 includes updates to Synchronization Services and SQL
Server 3.5 SP1, but only for desktop clients. You have to download and install
updates for mobile devices separately. After you have installed Visual Studio 2008
SP1, you also have to download the following from MSDN Downloads:
Microsoft Synchronization Services for ADO.NET v1.0 Service Pack 1 (devices)
Microsoft SQL Server Compact 3.5 Service Pack 1 for Windows Mobile
Installing the Demo Database in SQL Server 2008
In the sample code for this article, you can find the
instnwnd_withCustomerChangeTracking.sql script that you can use to create a
Northwind database on your SQL Server 2008 database server, with change
tracking enabled for the database and for the Customers table.

To execute it, open SQL Server Management Studio, connect to your database
server, and then use Windows Explorer to browse to where you have installed
instnwnd_withCustomerChangeTracking.sql, and double-click it.
The script opens in SQL Server Management Studio.
Choose the Execute icon on the toolbar to create the database.
Executing the OCS Designer in a Visual Studio Solution
Visual Studio 2008 SP1 includes the OCS Designer for creating and configuring
Synchronization Services solutions, and it supports code generation for projects
built for the .NET Compact Framework. To start the OCS Designer, on the Project
menu, choose Add New Item and then select the Local Database Cache template.
Many device developers cannot move past this very first step, because if you create
a smart device application, and then open the Add New Item window for the device
project, you find that Local Database Cache is not listed in the available templates.
To use the OCS Designer with .NET Compact Framework clients, you must create a
solution that contains two projects, one which is a smart devices Windows Forms
project to contain the client-side code, and one which is a WCF Service Library
which contains the server-side code. You can then add the Local Database Cache
item to the WCF Service Library project to start the OCS Designer, and configure it
to generate the client-side code in the Smart Device project, as described in the
following steps:
In Visual Studio 2008, create a new Smart Device Windows Forms project, targeting
the .NET Compact Framework 3.5.

In Solution Explorer, right-click the solution, and then choose Add New Project.
You are adding a second project to the same solution, which must be a WCF Service
Library project. Enter a suitable name for the WCF Service Library project, such as
MobileSyncServiceLibrary.

After the WCF Service Library is added, delete the project files IService1.cs and
Service1.cs. Those files are the example WCF service implementation files the
wizard created for you. Next, use the OCS Designer to generate the WCF Service
interface and implementation.
In Solution Explorer, right-click the WCF Service Library project, and then choose
Add New Item. Select Local Database Cache and give it a suitable name, such as
NorthwindCache.sync.

The OCS Designer displays the Configure Data Synchronization window. First, use
the Server connection drop-down list to select an existing database connection to
the back-end database server where the Northwind database is located, or choose
New to create a new connection. The OCS Designer automatically generates the
client database connection for you.

Next, choose Add underneath the Cached Tables pane. Here is where you select
which tables to copy to the client database cache. For this example, just select the
Customers table. In the Data to download drop-down list, select New and
incremental changes after first synchronization. This option causes Synchronization
Services to only transfer incremental changes between the client and server when
synchronizing, instead of transferring a complete new snapshot of the table every
time.

After you choose OK, you return to the main OCS Designer window. Here you must
choose Advanced for additional configuration options. Make sure that you select the
WCF Service Library project for the Server Project location, and the Smart Device
project as the Client project location.

Now choose OK. The wizard generates the code for synchronization between the
SQL Server Compact 3.5 SP1 database on the client side, and the SQL Server 2008
database on the server side. It also performs an initial synchronization and adds the
synchronized SQL Server Compact 3.5 SP1 database to your client-side project.

Next, Visual Studio 2008 displays the Data Source Configuration window. It prompts
you to set up a typed DataSet in your client-side project to work with the data in the
client-side database. You can cancel that if you intend to write your own data access
code to use the synchronized data in the SQL Server Compact Edition database in
your client-side application, but for this walkthrough you can generate a typed
DataSet by selecting the Tables check box to select all tables (there is only one, of
course, the Customers table which was synchronized from the master copy on the
server) and accept the default name of NorthwindDataSet, and then choose Finish.
At this point, the OCS Designer has generated most of the code to synchronize the
tables that you selected, and has generated the required code in your client-side
smart device, and in your server-side WCF Service Library. You still have some work
to do to create a working solution that works over a network, but before you do

that, it is worth pausing to examine the code that the OCS Designer has generated
for you.
Note:
One usage of the OCS Designer is as a tool to generate a fully populated
database for test or demo purposes. Using the OCS Designer in the way just
described is an excellent and easy way of copying a snapshot of data from a
server and inserting it into a SQL Server Compact Edition database for testing, or
where synchronization is not required.
Understanding the Architecture of a Synchronization Services Solution
The OCS Designer generates code in both the server project and the client project.
It is worth spending some time to examine the designer-generated code to
understand the objects it has created and the role each one plays.
The following illustration represents the architecture.

In an n-tier architecture for devices, synchronization components are used as


follows:
The server synchronization provider (DbServerSyncProvider) resides on the server
or another tier.
The server synchronization provider aggregates synchronization adapters
(SyncAdapter), which is an object that contains the definition of the commands to
select, insert, update, and delete records in the server database. You use one
synchronization adapter for each table that you are synchronizing.

The client synchronization provider (SqlCeClientSyncProvider) and the


synchronization agent (SyncAgent) both reside on the device. The synchronization
agent orchestrates the overall synchronization process. The client synchronization
provider interacts with the SQL Server Compact 3.5 SP1 database to update data
records and store synchronization metadata.
The SyncAgent owns a collection of synchronization table (SyncTable) objects. You
have one SyncTable for each table in the database that you want to synchronize.
You set properties on these objects to determine the kind of synchronization to
perform.
You can also group SyncTable objects into synchronization groups (SyncGroup),
typically where a parent/child relationship exists between tables. Synchronization
Services synchronizes changes to tables in a SyncGroup all together to make sure
that referential integrity between the tables in the group is maintained.
Communication between the server synchronization provider and the
synchronization agent is handled by the server synchronization provider proxy
(ServerSyncProviderProxy) and a service. Synchronization Services does not specify
the kind of service that you must use. However, applications typically use a Web
service or a WCF service.

Configuring the WCF Service for .NET Compact Framework


Clients
The OCS Designer has generated most of the code that you must have for this
Synchronization Services solution. But there is still quite a lot more work that you
have to do so that this solution works over a network.
Configuring DataSet Serialization
The OCS Designer added a file to your WCF Library project named
NorthwindCache.SyncContract.cs. This contains the definition of the interface your
WCF Service exposes to clients. Synchronization Services uses DataSet objects to
communicate changed data from client to server and from server to client. These
DataSet objects must be serialized for communication and by default a WCF Service
uses the System.Runtime.Serialization.DataContractSerializer class for this which
does not have an equivalent in the .NET Compact Framework to (de)serialize to the
same format. In order to force this service to use XML serialization instead, which is
supported by the .NET Compact Framework, edit the
NorthwindCache.SyncContract.cs file to add the XmlSerializationFormat attribute to
this interface definition. The following code example shows the added attribute in
the interface definition.
Copy

[ServiceContractAttribute()]
[XmlSerializerFormat()]
public interface INorthwindCacheSyncContract {
[OperationContract()]
SyncContext ApplyChanges(SyncGroupMetadata groupMetadata,
DataSet dataSet,
SyncSession syncSession);

[OperationContract()]
SyncContext GetChanges(SyncGroupMetadata groupMetadata,
SyncSession syncSession);

[OperationContract()]
SyncSchema GetSchema(Collection<string> tableNames,
SyncSession syncSession);

[OperationContract()]
SyncServerInfo GetServerInfo(SyncSession syncSession);
}
Configuring the Service
If you scroll up to the top of this file, you see a large comment block which suggest
to you how to set up the app.config configuration file for the WCF service.
To configure the service
Replace the <service> element definition in app.config with that suggested by the
comment block.
There are two important changes that you must make to the XML you just added:

In the <endpoint address=""> element, change the binding attribute from


binding="wsHttpBinding" to binding="basicHttpBinding". The .NET Compact
Framework does not support "wsHttpBinding".
In the <baseAddresses> element, change the URL that is specified in the <add
baseaddress> statement from localhost:8080 to the correct host name and port
where the service will be running, for example some_server:8088.
Replace the <behavior> element definition in app.config with that suggested in the
comment block in NorthwindCache.SyncContract.cs.
The following code example shows what the app.config code should look like.
Copy
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
...
<system.serviceModel>
<services>
<service
name="MobileSyncServiceLibrary.NorthwindCacheSyncService"
behaviorConfiguration=
"MobileSyncServiceLibrary.NorthwindCacheSyncServiceBehavior">
<host>
<baseAddresses>
<add baseAddress=
"http://myServer:8088/NorthwindCacheSyncService/"/>
</baseAddresses>
</host>
<endpoint address ="" binding="basicHttpBinding" contract=
"MobileSyncServiceLibrary.INorthwindCacheSyncContract"/>
<endpoint address="mex" binding="mexHttpBinding" contract=

"IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name=
"MiddleTierServiceLibrary.NorthwindCacheSyncServiceBehavior">
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

Using NetCFSvcUtil to Generate the Service Proxy for the Client


The Power Toys for .NET Compact Framework 3.5 which you can download from
MSDN Download includes the NetCFSvcUtil tool. After installation, you find it located
in the following directory: <drive>:\Program
Files\Microsoft.NET\SDK\CompactFramework\v3.5\Bin. To make this tool easier to use
at a command prompt, add this path of the PATH environment variable of your
computer.
To generate the WCF service proxy for the client
Build the WCF Service Library project.
Start a command prompt, move to the Bin\Debug directory of the WCF Service
Library project, and then run the following commands in sequence:
Copy
netcfsvcutil.exe WCF_Service_Library_name.dll
netcfsvcutil *.wsdl *.xsd /language:C# /out:MobileSyncServiceProxy.cs

The first command generates the metadata documents for Service Contracts and
associated types in the WCF Service assembly and the second uses the metadata
documents as input to generate the C# code for the client proxy. If you are working
in Microsoft Visual Basic, use /language:VB /out:filename.vb arguments instead.
There will be two source files generated, CFClientBase.cs/.vb and
MobileSyncServiceProxy.cs/.vb. Copy these files to the Smart Device project
directory and add them to the project.
Add references to System.Runtime.Serialization and System.ServiceModel to the
Smart Device project.
Before you can use these classes, you must edit the generated
MobileSyncServiceProxy.cs/.vb and remove some class definitions. The problem is
that the NetCFSvcUtil tool has generated classes for every type exposed by the
Service interface. This includes many types such as SyncGroupMetadata,
SyncAnchor, and so on, that are also defined in the
Microsoft.Synchronization.Data.dll assembly which is already referenced by the
client-side project.
Edit this file and delete all classes that use names that begin with Sync and the
ConflictType enum. Add a using Microsoft.Synchronization.Data; (Imports
Microsoft.Synchronization.Data for Visual Basic) statement to the top of this file and
then you can compile successfully.
Coding the Client
It is left as an exercise for the reader to build a suitable user interface to display and
enable editing of records in the Customer table. A simple example is included in the
sample code accompanying this article.
The code that you use to synchronize on the client is fairly simple. The code that the
OCS Designer generated and added to the client-side project includes a class that
inherits from Microsoft.Synchronization.SyncAgent. It is an instance of this class that
you use to orchestrate the synchronization on the client-side.
To write the code for the synchronization on the client-side
Create an instance of the service proxy.
Pass the instance to the constructor of a
Microsoft.Synchronization.Data.ServerSyncProviderProxy instance. This object wraps
the service proxy and provides a facade that the Sync Service classes can work
with.
Create an instance of the SyncAgent-derived class, and set its RemoteProvider
property to the ServerSyncProviderProxy instance.

Call the Synchronize method of the SyncAgent-derived instance to perform the


synchronization.
The following C# code example shows these steps for the example described in this
paper.
Copy
private static void Sync()
{
Cursor.Current = Cursors.WaitCursor;

// Create WCF Service proxy


System.ServiceModel.BasicHttpBinding basicBinding =
new System.ServiceModel.BasicHttpBinding();
System.ServiceModel.EndpointAddress endPoint =
new System.ServiceModel.EndpointAddress(
"http://my_server:8081/NorthwindDataCacheSyncService/");
NorthwindDataCacheSyncContractClient proxy =
new NorthwindDataCacheSyncContractClient(basicBinding,
endPoint);
// Wrap in a ServerSyncProviderProxy
Microsoft.Synchronization.Data.ServerSyncProviderProxy syncProxy =
new Microsoft.Synchronization.Data.ServerSyncProviderProxy(
proxy);

// Create a SyncAgent
NorthwindDataCacheSyncAgent syncAgent =
new NorthwindDataCacheSyncAgent();
syncAgent.RemoteProvider = syncProxy;

// Set sync to be bidirectional


syncAgent.Customers.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Bidirectional;

// Synchronize
Microsoft.Synchronization.Data.SyncStatistics syncStats =
syncAgent.Synchronize();

// Show synchronization statistics


MessageBox.Show("Changes downloaded: " +
syncStats.TotalChangesDownloaded.ToString()
+ "\r\nChanges uploaded: " +
syncStats.TotalChangesUploaded.ToString());

Cursor.Current = Cursors.Default;
}
Setting the Synchronization Direction
You may have noticed that the code just shown includes the following:
Copy
// Set sync to be bidirectional
syncAgent.Customers.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Bidirectional;

By default, the code that the OCS Designer generates is download-only. Therefore, if
you want bidirectional or snapshot synchronization, you must explicitly request it. In
the Synchronization Services architecture, synchronization is orchestrated by an

instance of a SyncAgent-derived class. That object exposes a collection of SyncTable


objects in the Configuration.SyncTables property, which contains a SyncTable
instance for each table in the source database that you want to synchronize. It is
the SyncDirection property of the SyncTable instance you set to alter the sync
direction and you must do that for each table that you are synchronizing.
The SyncAgent-derived class created by the OCS Designer exposes a property for
each SyncTable it is managing so that in this case you can get syncAgent.Customers
to access the Customers SyncTable to set the SyncDirection property.
It is a good idea to spend some time studying the code created by the OCS Designer
and reading the Synchronization Services documentation to understand the
relationship between objects in a Synchronization Services solution, and the many
configuration options.
Testing the Application
Now you are ready to test the program. First, make sure that you have network
connectivity between the device or device emulator and the server, for example by
connecting the device through Microsoft ActiveSync. A good way to check whether
you have connectivity is to start the WCF Service running, which you can do by
right-clicking the WCF Library project in Solution Explorer, choose Debug, and then
choose Start New Instance. Then start Internet Explorer Mobile on the device, and
enter the URL of the service, for example:
http://myserver:8080/NorthwindDataCacheSyncService/.
Tip:
: The trailing / is important for success, and if you have connectivity, you see
the standard service Home page that the following screen shows.

When you have confirmed that you have network connectivity, you can run the
application. The simple test application that is included in the sample just reports
the results of synchronization to the screen by using a MessageBox message
window. Because the database was already synchronized when you ran the OCS
Designer, not surprisingly it reports that no records were transferred.
If you change the application to update one or more records in the client database
(the sample application includes a menu button that you can press to insert a new
record), or use Visual Studio or SQL Server Management Studio to alter some
Customers records on the server, and then synchronize again, you see the changed
records being transferred in order to keep both databases in sync.
Avoiding the Schema Exchange Bug When You Use NetCFSvcUtil-Generated WCF
Proxies
The example program just described works as long as you have an active network
connection between the device and the server. However, there is an incompatibility
between the SyncAgent class and a WCF proxy that you generate by using
NetCFSvcUtil which is only revealed when you run the application that uses an
empty database on the client.

In that case, the first thing that the client does is to call the GetSchema method on
the service interface to download the schema of the tables it wants to synchronize.
The signature of the DbServerSyncProvider.GetSchema method is SyncSchema
GetSchema(Collection<string> tableNames, SyncSession syncSession), and
naturally the OCS Designer generates a method in the service contract with the
same signature. However, when the SyncAgent class on the device calls the WCF
service proxy, instead of passing the names of the tables it is interested in as a
Collection<string>, it passes them as an object array. There is no implicit
conversion from object array to Collection<string> so that the application fails with
a runtime error at this point. Be aware that you do not see this error that occurs
with a solution generated by the OCS Designer because it populates the client
database when you run the wizard, so the tables to be synchronized already exist in
the client side database and no schema exchange is necessary at run time.
The following code example shows a workaround for this problem. Change the
GetSchema method in the service interface to take a string[] to which an object
array can be implicitly converted, instead of a Collection<string>.
Copy
[ServiceContractAttribute()]
[XmlSerializerFormat()]
public interface INorthwindDataCacheSyncContract {

[OperationContract()]
SyncContext ApplyChanges(SyncGroupMetadata groupMetadata,
DataSet dataSet,
SyncSession syncSession);

[OperationContract()]
SyncContext GetChanges(SyncGroupMetadata groupMetadata,
SyncSession syncSession);

[OperationContract()]
SyncSchema GetSchema(string[] tableNames,

SyncSession syncSession);

[OperationContract()]
SyncServerInfo GetServerInfo(SyncSession syncSession);
}
Then, in the implementation of this method, which is in the
NorthwindDataCacheSyncService class in the example described in this paper,
convert the string[] to the Collection<string> instance required by the
DbServerSyncProvider class.
Copy
public virtual SyncSchema GetSchema(
string[] tableNames, SyncSession syncSession)
{
// Convert the string array to Collection<string>
Collection<string> tableCollection =
new Collection<string>(tableNames);
return this.serverSyncProvider.GetSchema(
tableCollection, syncSession);
}
Remember to run NetCFSvcUtil again against this revised service and include the
generated files in your client-side project.
Of course, the techniques described in this paper are only one way to build a
Synchronization Services solution. If you create the service proxy by adding a Web
Reference to your project instead of generating a proxy by using NetCFSvcUtil, the
generated proxy code does not experience the same problem.
Understanding SQL Server 2008 Change Tracking Configuration

Before you examine some potential problems with a Sync Services solution, it is
helpful to examine how you configure change tracking in a SQL Server 2008
database.

SQL Server 2008 has support for change tracking built in. However, by default, it is
disabled. You enable change tracking for a SQL Server 2008 database by using a
Transact-SQL command. The following code example shows such a command.
Copy
ALTER DATABASE database_name
SET CHANGE_TRACKING = ON
(AUTO_CLEANUP = ON, CHANGE_RETENTION = 14 DAYS)

The change retention value specifies the time period for which change tracking
information is kept. Change tracking information that is older than this time period
is removed periodically. When you are setting this value, you should consider how
frequently applications synchronize with the tables in the database. The specified
retention period must be at least as long as the maximum time period between
synchronizations. See "Handling Initial Synchronization and Expired Subscriptions"
later in this paper for a discussion on how to handle the situation where a client
attempts to synchronize but the time that has elapsed because its previous
synchronization was greater than the change retention period of the database
server.
After you have enabled change tracking on the database server, you must enable
change tracking for any tables that you want to synchronize. The following code
example shows how to use such a command.
Copy
ALTER TABLE "table_name"
ENABLE CHANGE_TRACKING
WITH ( TRACK_COLUMNS_UPDATED = OFF )

When the TRACK_COLUMNS_UPDATED option is set to ON, the SQL Server Database
Engine stores additional information about which columns were updated to the
internal change tracking table. Column tracking can enable an application to
synchronize only those columns that were updated. This can improve efficiency and
performance. However, because maintaining column tracking information adds
some additional storage overhead, by default, this option is set to OFF.
The script that you ran to install the Northwind database at the beginning of this
tutorial includes these commands within it. Notice that the OCS Designer

automatically enables change tracking in the server database if it is not already


enabled.
Limitations of Synchronization Services on Devices

The example of a Synchronization Services solution that has just been described
may leave you with the impression that Synchronization Services solutions are fairly
easy to set up and program. However, there are some pitfalls waiting to catch the
unwary mobile application developer, because of the way that Synchronization
Services performs its work and the solution that the OCS Designer creates for you.
The first problem is that by default, when a client synchronizes Synchronization
Services sends all changes that have occurred since the clients previous
synchronization in one go. By default, no throttling or batching is implemented.
Therefore, if there are many incremental changes, you may find that the Dataset
sent from server to client or vice versa is very large and can easily lead to out-ofmemory exceptions on the mobile device when it struggles to deserialize the data
on receipt, or serialize it before sending.
The second problem is that change history is not retained indefinitely on the server
or on the client. You have to make sure that clients synchronize frequently enough
that they can be refreshed before the change data expires and is removed. The
Transact-SQL commands that the OCS Designer generates to select changed
records from the SQL Server database include a check for expired subscriptions; if
you study the NorthwindDataCache.Designer.cs file in the example project, you find
the definition of the Transact-SQL commands that are executed to select
incremental changes from the server database. The following code example shows
part of those commands.
Copy
...IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(N'dbo.Customers')) >
@sync_last_received_anchor RAISERROR (N'SQL Server Change Tracking has
cleaned up tracking information for table ''%s''. To recover from this error, the client
must reinitialize its local database and try again',16,3,N'dbo.Customers') ";

You have to configure change tracking with an appropriate change retention period
that is longer than the longest period between synchronizations of clients. You also
have to write code to detect synchronization failure because of this error being
raised and you need a strategy to reinitialize such clients.
The third problem is one of efficiency. Synchronization Services uses the DataSet
object as the vehicle for sending changes back and forth. This serializes to XML so

can be sent easily over HTTP network links. However, it is also a verbose format for
sending data. Transmitting unnecessary data over a mobile phone network is costly
due to the charges you pay to your mobile operator, but also leads to more frequent
failures if the network connection is of poor quality. The smaller the quantity of data
that you can send in these situations, the better your chance of a successful
transmission. Because of this, you should consider implementing compression in the
WCF channel, although remember that if you use compression you increase the
processing the mobile device must perform. Therefore you will decrease battery life.
The next sections describe some possible remedies for these problems.
Implementing Batching to Reduce Data Transfer Volumes
Synchronization Services supports a limited batching capability which performs
multiple transfers of the changed data in smaller batches from server to client,
instead of all the changed rows in one go. In order to understand how batching
works, you have to understand how SQL Server 2008 change tracking tracks
changes, and how Synchronization Services uses that information.
Synchronization Services Using Change Tracking Version Numbers
When you enable change tracking on a SQL Server 2008 database, it starts to
maintain a change version number which increments from zero. Also it maintains a
special internal changetable for each table for which you have enabled change
tracking. When a row in the table is updated, a record is written to the internal
changetable that contains the primary key of the changed record and the
associated change version number.
When a client synchronizes, the server returns to the client the current value of its
server-wide change version number. This is available by calling the SQL Server 2008
CHANGE_TRACKING_CURRENT_VERSION function. You define the Transact-SQL
command to use to retrieve this server-wide change version number by setting the
SelectNewAnchorCommand property of a
Microsoft.Synchronization.Data.Server.DbServerSyncProvider instance, which exists
in the server-side code of your sync solution. The following code example shows the
standard way to define this, which is similar to the code that the OCS Designer
generates in the DbServerSyncProvider class initialization logic.
Copy
// selectNewAnchorCmd command.
SqlCommand selectNewAnchorCommand = new SqlCommand();
selectNewAnchorCommand = new System.Data.SqlClient.SqlCommand();
selectNewAnchorCommand.CommandText =

"Select @sync_new_received_anchor = CHANGE_TRACKING_CURRENT_VERSION()";


selectNewAnchorCommand.CommandType = System.Data.CommandType.Text;
System.Data.SqlClient.SqlParameter selectnewanchorParameter =
new System.Data.SqlClient.SqlParameter(
"@sync_new_received_anchor",
System.Data.SqlDbType.BigInt);

selectnewanchorParameter.Direction =
System.Data.ParameterDirection.Output;
selectNewAnchorCommand.Parameters.Add(selectnewanchorParameter);

// Set to the DbServerSyncProvider


this.SelectNewAnchorCommand = selectNewAnchorCommand;

The client also sends to the server the change tracking version it received from the
server the previous time it synchronized or 0 (zero) if this is the first
synchronization. Therefore, now all the server-side code has to do is select all
changed rows with a change version later than the change tracking version the
client received at the previous synchronization, and less than or equal to the
databases current change version number. This actual logic required is more
complex than this, because this article does not describe how the logic to track
inserts, updates, and deletions separately is implemented, nor how the ID of the
client that made the last update is tracked, but this article simplifies to make the
general process easier to understand. Refer to the SQL Server 2008 Documentation
for full details.
Implementing Batching in Synchronization Services
The batch mechanism in Synchronization Services lets you limit the range of change
versions to be sent to the client in each response to a client synchronization
request. For example, if the client previously synchronized to version 50, the
servers change tracking version is currently 120, and you define a batch size of 30,
then the changed rows is sent in three separate batches: 51-80 in the first transfer,
81-110 in the second, and 111-120 in the third.

One way of enabling this behavior is to define a stored procedure such as the
following. This procedure takes five parameters which are required by
Synchronization Services:
@sync_last_received_anchor which is the change version associated with the
clients previous synchronization
@sync_batch_size which is the requested range of change version numbers
requested for each batch
@sync_max_received_anchor which is an out parameter which returns the current
change version on the server
@sync_new_received_anchor which returns the actual maximum change version
number of the set of records selected for the current batch
@sync_batch_count which returns the number of batches that are required to
complete the synchronization
The following code example shows the logic of the stored procedure described in
the comments included in the script.
Copy
USE [Northwind]
GO

/**** Object: StoredProcedure [dbo].[usp_GetNewBatchAnchor] ****/


CREATE PROCEDURE [dbo].[usp_GetNewBatchAnchor] (
@sync_last_received_anchor bigint,
@sync_batch_size int,
@sync_max_received_anchor bigint out,
@sync_new_received_anchor bigint out,
@sync_batch_count int output)
AS
-- Set a default batch size if a valid one is not passed in.
IF @sync_batch_size IS NULL OR @sync_batch_size <= 0

SET @sync_batch_size = 1000

-- Before selecting the first batch of changes,


-- set the maximum anchor value for this synchronization
-- session. After the first time that this procedure is
-- called, Synchronization Services passes a value for
-- @sync_max_received_anchor to the procedure. Batches of
-- changes are synchronized until this value is reached.
IF @sync_max_received_anchor IS NULL
SELECT @sync_max_received_anchor =
change_tracking_current_version()

-- If this is the first synchronization session for a database,


-- get the lowest change version value from the tables. By
-- default, Synchronization Services uses a value of 0 for
-- sync_last_received_anchor on the first synchronization. If
-- you do not set @sync_last_received_anchor,
-- this can cause empty batches to be downloaded until the
-- lowest change version value is reached.
IF @sync_last_received_anchor IS NULL OR
@sync_last_received_anchor = 0
BEGIN
SELECT @sync_last_received_anchor = 0

-- Changes are only retained in the change table for a limited


-- period of time set by the CHANGE_RETENTION parameter

-- (on ALTER DATABASE).


-- Check that we haven't had changes cleaned up on this table
-- (i.e. CHANGE_TRACKING_MIN_VALID_VERSION returns > 0)
IF CHANGE_TRACKING_MIN_VALID_VERSION(
object_id(N'dbo.Customers')) >
@sync_last_received_anchor
RAISERROR (N'SQL Server Change Tracking has cleaned up
tracking information for table ''%s''. To recover from this error, the
client must reinitialize its local database and try
again',16,3,N'dbo.Customers')

SET @sync_new_received_anchor =
@sync_last_received_anchor + @sync_batch_size

-- Determine how many batches are required during the


-- initial synchronization.
IF @sync_batch_count <= 0
SET @sync_batch_count = (
(@sync_max_received_anchor / @sync_batch_size) (@sync_last_received_anchor / @sync_batch_size)
)
END
ELSE
BEGIN
SET @sync_new_received_anchor =
@sync_last_received_anchor + @sync_batch_size

-- Determine how many batches are required during subsequent


-- synchronizations.
IF @sync_batch_count <= 0
SET @sync_batch_count = (
(@sync_max_received_anchor / @sync_batch_size) (@sync_new_received_anchor / @sync_batch_size)) + 1

END

-- Check whether this is the last batch.


@sync_max_received_anchor

IF @sync_new_received_anchor >=

BEGIN
SET @sync_new_received_anchor = @sync_max_received_anchor
IF @sync_batch_count <= 0
SET @sync_batch_count = 1
END
GO

The following code example shows how to activate batch support by configuring the
SelectNewAnchorCommand property of the
Microsoft.Synchronization.Data.Server.DbServerSyncProvider instance to call the
stored procedure, specifying the requested batch size.
Copy
SqlCommand selectNewAnchorCommand = new SqlCommand();
selectNewAnchorCommand.CommandText = "usp_GetNewBatchAnchor";
selectNewAnchorCommand.CommandType = CommandType.StoredProcedure;
selectNewAnchorCommand.Parameters.Add("@" +

SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt, 8);


selectNewAnchorCommand.Parameters.Add("@" +
SyncSession.SyncMaxReceivedAnchor, SqlDbType.BigInt, 8);
selectNewAnchorCommand.Parameters.Add("@" +
SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt, 8);
selectNewAnchorCommand.Parameters.Add("@" +
SyncSession.SyncBatchSize, SqlDbType.Int, 4);
selectNewAnchorCommand.Parameters.Add("@" +
SyncSession.SyncBatchCount, SqlDbType.Int, 4);
selectNewAnchorCommand.Parameters["@" +
SyncSession.SyncMaxReceivedAnchor].Direction =
ParameterDirection.Output;
selectNewAnchorCommand.Parameters["@" +
SyncSession.SyncNewReceivedAnchor].Direction =
ParameterDirection.Output;
selectNewAnchorCommand.Parameters["@" +
SyncSession.SyncBatchCount].Direction =
ParameterDirection.InputOutput;
// Set to the DbServerSyncProvider
this.SelectNewAnchorCommand = selectNewAnchorCommand;
// Set the requested batch size
this.BatchSize = 10;

Batching for Client Incremental Changes


There is no support in Synchronization Services for sending changes from a client up
to the server in smaller batches. You should make sure that the client code
synchronizes with the server frequently enough so that the volume of changes

cached on the client does not grow to a large number. Many changes cached on the
client could lead to out-of-memory exceptions when you try to serialize the DataSet
to be sent to the server, and to problems in transmitting large amounts of data over
slow connections such as cell data networks.
Why Batching Might Not Work After Enabling SQL Server 2008 Change Tracking on
Populated Tables
There is one particular problem that may occur with batching. If you enable change
tracking on a database table before any data is inserted in that table, then, as each
row is added to the source table, the change is recorded in the internal changetable
and is assigned a new, unique change version number. In this case, batching can
limit the number of rows that are sent to the client on each communication.
However, if you enable change tracking on a table that already has many rows of
data, then the initial state of the table is recorded by adding records to the
changetable for all rows with the same initial change version number. Therefore,
even with batching implemented, when a client synchronizes for the first time, the
Synchronization Services server-side code attempts to return all the rows in the
requested table.
In this circumstance, you must initialize the client in a different way, as described in
the next section.
Handling Initial Synchronization and Expired Subscriptions
There are some situations where you cannot synchronize a client database by using
the techniques described to this point in this paper.
When a client synchronizes for the very first time starting with an empty client
database and you do not implement batching, the dataset the server builds that
contains the initial snapshot of all rows in the tracked tables will be too large for the
device to process given its limited resources. Even if you do implement batching,
the process will be slow and prolonged, and not possible over a communications link
with poor quality. In this circumstance, you cannot use standard synchronization to
create the initial client database.
When synchronization of a client fails because the time interval since the clients
previous synchronization is greater than the change retention period configured on
the server, you must reinitialize the client database.
Reinitializing the Client Database
In order to reinitialize the client database, you must create a synchronized SQL
Server Compact Edition database. Because this cannot realistically be done by
running a process on the client device because of its limited resources, it must be
performed on the server.

SQL Server Compact Edition is supported on desktop and server versions of


Windows. Therefore, you can easily write a simple program to run on a computer to
synchronize a client database. You can even run it one time, and use the same copy
of the database on multiple clients. If you are using download-only synchronization
where no changes on the client are synchronized back to the server, then you do
not even have to worry about how to set a unique GUID client ID for each client. The
server does not store metadata for each of its clients. Therefore, for download-only
scenarios, the only piece of client data the server is interested in is the
SyncLastReceivedAnchor value, the change version number associated with the
clients previous synchronization.
If you synchronize changes from client to server, then you must make sure each
client database has a unique client ID. The server needs this because the change
data that is stored for each row includes the ID of the client that made the change.
It needs to know the identity of the client that made the change, because, when
synchronization occurs, the data changed on the client is uploaded first and
inserted into the server database, and then the changed rows on the server are
downloaded to the client. The server has to make sure on the download that it does
not send back the same changed rows that the client just sent it, and it does that
when you select changed rows by filtering out all changes with a client ID that is the
same as that of the requesting client.
You can reset the client ID associated with an existing client database by setting the
SqlCeClientSyncProvider property of the
Microsoft.Synchronization.Data.SqlCeClientSyncProvider class to a new GUID. The
following code example shows that in an OCS Designer-generated solution, the
instance of this class you must change is exposed in the LocalProvider property of
the SyncAgent.
Copy
((SqlCeClientSyncProvider)(syncAgent.LocalProvider)).ClientId =
Guid.NewGuid();

You have to think of some method of transferring a newly-initialized client database


on to the intended client. There are many ways of doing this, such as using FTP, or a
custom solution over a WAN, or returning the device to base and copying over an
ActiveSync connection, or a wireless LAN.
Using Compression to Reduce Data Transfer Volumes over WCF
One other optimization you can introduce is to compress the data in transit. This
does not reduce the memory requirement for processing data on the device, and in
fact it requires additional processing and so uses more battery power. But because

you transfer fewer bytes, it can improve the chances of a successful synchronization
if you are operating over poor-quality cell data connections.
With WCF, you use a custom binding to perform the compression and
decompression. There are two samples available for download from MSDN that you
can use to get started with building compression bindings for your solution:
Custom Message Encoder sample is an example of a custom encoder for desktop
and server platforms. You can read how it works at Custom Message Encoder:
Compression Encoder where there is also a link to download a collection of samples
that includes this encoder. After installation, you can find the gZipEncoder project at
<installation path>\WCF
Samples\WCF_WF_CardSpace_Samples\WCF\Extensibility
\MessageEncoder\Compression\CS\Server. Copy this project and include it in your
solution, then add a reference to this library to the server-side project.
You can find a GZip encoder sample for the .NET Compact Framework at .NET
Compact Framework Technology Samples: GZip Encoder Sample. Copy the
IndigoGZipEncoder project to your solution, and add a reference to this project to
the client-side code project.
The following code example shows how to start using the GZip encoders in your
solution, where you first change the configuration in the app.config file of the server
project.
Copy
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
...
<system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="gzipMessageEncoding"
type="Microsoft.ServiceModel.Samples.GZipMessageEncodingElement,
GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bindingElementExtensions>
</extensions>

<services>
<service
name="MobileSyncServiceLibrary.NorthwindDataCacheSyncService"
behaviorConfiguration=
"MobileSyncServiceLibrary.NorthwindDataCacheSyncServiceBehavior">
<host>
<baseAddresses>
<add baseAddress=
"http://myserver:8080/NorthwindDataCacheSyncService/"/>
</baseAddresses>
</host>
<endpoint
address =""
binding="customBinding"
bindingConfiguration="BufferedHttpSampleServer"
bindingName="BufferedHttpSampleServer"
contract=
"MobileSyncServiceLibrary.INorthwindDataCacheSyncContract"/>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
<bindings>
<customBinding>
<binding name="BufferedHttpSampleServer">
<gzipMessageEncoding
innerMessageEncoding="textMessageEncoding" />

<httpTransport hostNameComparisonMode="StrongWildcard"
manualAddressing="False"
maxReceivedMessageSize="65536"
authenticationScheme="Anonymous"
bypassProxyOnLocal="False"
realm=""
useDefaultWebProxy="True" />
</binding>
</customBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name=
"MobileSyncServiceLibrary.NorthwindDataCacheSyncServiceBehavior">
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Read the description of this custom encoder at Custom Message Encoder:
Compression Encoder to understand the meaning of these configuration elements.
On the client, configuration through config files is not supported. Therefore, use of
the custom binding by the WCF proxy must be configured in code. The following
code example shows how to do this.
Copy

// Create a CustomBinding
System.ServiceModel.Channels.CustomBinding customBinding =
new System.ServiceModel.Channels.CustomBinding();
// Create a compression binding element
CompressionMessageEncodingBindingElement compressionBindingElmnt =
new CompressionMessageEncodingBindingElement();
// ..and add to the custom binding
customBinding.Elements.Add(compressionBindingElmnt);

// Create an HttpTransportBindingElement and add that as well


HttpTransportBindingElement httpBindingElement =
new HttpTransportBindingElement();
customBinding.Elements.Add(httpBindingElement);

System.ServiceModel.EndpointAddress endPoint =
new System.ServiceModel.EndpointAddress(
"http://myServer:8080/NorthwindDataCacheSyncService/");

// Create the WCF proxy using the custom binding


NorthwindDataCacheSyncContractClient proxy =
new NorthwindDataCacheSyncContractClient(
customBinding, endPoint);
By using a compression binding such as this, you can achieve large reductions in
the volume of data transferred over the network.
Conclusion

Microsoft Synchronization Services for ADO.NET provides a developer-centric data


synchronization solution. It is very flexible and enables synchronization with any
server database for which there is an ADO.NET provider. The Occasionally
Connected Systems Designer in Visual Studio 2008 SP1 is a wizard that helps build
Synchronization Services solutions.
Whereas Synchronization Services solutions are fairly easy to build, developers have
to be aware of the need to implement batching to reduce the data to be processed
to a size that is suitable for mobile clients. Also, compression should be considered
on the communication link to reduce the volume of data being transferred.
Implementers also have to think of a strategy to handle new client initialization and
expired subscriptions by existing clients.
Whereas it is undoubtedly a useful data synchronization tool, Microsoft
Synchronization Services is best suited to data where the rate of change is fairly
low. It is highly customizable so it is useful for custom synchronization solutions and
can be used with any database for which there is an ADO.NET provider. For most
synchronization solutions involving mission-critical data on a SQL Server database,
SQL Server merge replication provides a more reliable and efficient solution.
Author Bio
Andy Wigley, Device Application Development MVP, is a co-founder of APPAMundi
Limited and is a technical project leader and consultant at the company. He has
been a Device Application Development MVP since 2002, and is an author and
frequent speaker at major conferences. He is the co-author of several books
including the Microsoft Mobile Development Handbook, published by Microsoft
Press. He can be contacted at andy.wigley@appamundi.com.
Additional Information
Main Microsoft SQL Server Compact 3.5 product Web site at this Web site.
Microsoft Synchronization Services for ADO.NET in SQL Server Compact 3.5 SP1
Books Online at this Web site.
Microsoft SQL Server Compact Edition 3.5 Books Online at this Web site.
Top of Form
ekry2eUkZqhNmJ

Did you find this helpful?

Yes

No

Not accurate

Not enough depth

Need more code examples

Tell us more...

(1500 characters remaining)


http://msdn.micro Submit

Bottom of Form
2012 Microsoft. All rights reserved.
Terms of Use | Trademarks | Privacy Statement| Site Feedback Site Feedback

Top of Form
PtIACJbmnViKrnjh

Site Feedback
x
Tell us about your experience...
Did the page load quickly?
Yes

No

Do you like the page design?


Yes

No

Tell us more
Enter description here.

Send

http://msdn.micro

Bottom of Form

Das könnte Ihnen auch gefallen