Sync Guru Sync Guru
 
spacer
 
       
    Home
    Projects
    Articles
    Links
    About
 
   4734
  

  spacer

spacer
 
Synchronization Services for ADO.NET
   

 

Demo VI: Offline Application - Decoupled Change Tracking
 
In many situations adding extra columns to base table for tracking changes is not desired. This is understandable. System architects spend considerable amount time building the schema for large system, normalize it, optimize, performance and scalability, and build a host of applications for end users. After this kind of investment, the attempt to change the schema becomes hard since the risk of breaking exiting applications or adversely affecting the performance is high.  Given these limitations, how one go about enabling tracking on given set of tables for the propose of synchronization? This is a good question. In all the pervious demos I showed you what I call "coupled change tracking" approach, also known as "inline change tracking", by that I mean enabling tracking by adding extra column to the data table. In this demo, I will show you a different approach called "decoupled change tracking". While there is no escape from using triggers, we can decouple the tracking columns from the base data table and put it in a separate table.

The diagrams below summarizes change tracking approaches, inline and decoupled:

 

 

To build this demo, I started with demo II source code and made modifications to the TSQL code in the demo.sql and server_procs.sql files. The commands for selecting inserts, updates, and deletes were part of the code I needed to change them as well. The rest of the code remain intact. So, let's start by looking at the change tracking table and triggers code in the demo.sql. I will focus on 'orders' table only but the same logic goes for 'order_details'.

Change Tracking Table and Triggers

The goal of decupled change tracking model is not to make any changes to the base table schema and thus we will maintain all tracking metadata in a separate table that I named 'orders_tracking'; TSQL code below shows the schema of the table. Notice that, the 'operation' column takes one of the following values

  • 'I' - for insert

  • 'U' - for update

  • 'D' - for delete

Another thing to note here is that the tracking table has 'order_id' as primary key. That means deleting a row in base table and inserting a new row with same PK will result in an error. You can fix that error by altering the insert trigger to update existing old row. I will leave that as an exercise to the reader.

--
-- Create tracking tables
--   the tracking table will store the last operation (I, U, D) to a given row. Rows are identified
--   by PK
--
CREATE TABLE pub_decoupled..orders_tracking(
    order_id int NOT NULL primary key,       
    operation char NOT NULL,
    update_originator_id int default 0,
    update_timestamp timestamp,   
    create_timestamp bigint default @@DBTS + 1)

Recall that in coupled change tracking we only needed update and delete triggers. However, with decoupled tracking we will need another one for inserts too. The main role the tracking triggers play here, other than inserting the row in the tracking table, is to reset the originator_id to the one of the server (remember that server_id is 0). If the change was made outside the sync application, then the originator id of 0 is the right value. If the sync did the change, then it is responsible of updating the originator id with the synchronizing client_id as you will see later when we discuss the insert, update and delete commands in the next section.

Notice that the triggers are responsible for updating the 'operation' column.

-- insert triggers
CREATE TRIGGER orders_insert_trigger on orders for insert
as
    set nocount on
    declare @key int   
    select @key = order_id from inserted     
    insert into orders_tracking(order_id, operation, update_originator_id, create_timestamp)
    values (@key, 'I', 0, @@DBTS+1)
    set nocount off
go

-- update triggers
CREATE TRIGGER orders_update_trigger on orders for update
as
    set nocount on
    declare @key int
    select @key = order_id from inserted     
    update orders_tracking set operation = 'U', update_originator_id = 0 where order_id = @key
    set nocount off
go

-- delete triggers
CREATE TRIGGER orders_delete_trigger on orders for delete
as
    set nocount on
    declare @key int
    select @key = order_id from deleted
    update orders_tracking set operation = 'D', update_originator_id = 0 where order_id = @key
    set nocount off
go   


Synchronization Commands

With the data in one table and the metadata in another, our sync adapter commands will need to change. Again, I will stick with writing the insert, update, and delete commands as stored procedures, while the select incremental commands as a direct TSQL statements.

If you compare the code from the demo II for coupled change tracking with the one below, you see that the code is largely the same except that here I need to use join operation to get hold of the tracking metadata for the row in question. Another difference here is that all commands needs to update the tracking table with the originator id of the client as I mentioned before. In demo two, this was only needed with the delete command.

create procedure dbo.sp_orders_applyinsert (
    @sync_last_received_anchor binary(8) ,
    @sync_client_id_hash int ,
    @sync_rowcount int out,
    @order_id int = NULL ,
    @order_date datetime = NULL)       
as
    insert into [orders] ([order_id], [order_date])
    values (@order_id, @order_date)
    set @sync_rowcount = @@rowcount

    -- update tracking table
    if @sync_rowcount > 0
        update orders_tracking
        set update_originator_id = @sync_client_id_hash           
        where order_id = @order_id     
go

create procedure dbo.sp_orders_applyupdate (   
    @sync_last_received_anchor binary(8) ,
    @sync_client_id_hash int ,
    @sync_rowcount int out,
    @order_id int,
    @order_date datetime = NULL )       
as         
    update o
    set o.order_date = @order_date       
    from [orders] o join [orders_tracking] t on o.order_id = t.order_id
    where (t.update_timestamp <= @sync_last_received_anchor or t.update_originator_id =
           @sync_client_id_hash) and t.order_id = @order_id                 
    set @sync_rowcount = @@rowcount

    -- the update trigger will reset the originator id to 0, update it to reflect the synchronizing
       client
    if @sync_rowcount > 0
        update orders_tracking
        set update_originator_id = @sync_client_id_hash           
        where order_id = @order_id     
go 

create procedure dbo.sp_orders_applydelete
      @sync_client_id_hash int,
      @sync_last_received_anchor timestamp,
      @sync_rowcount int out,
      @order_id int,
      @order_date datetime = NULL
as
    delete o
    from [orders] o join [orders_tracking] t on o.order_id = t.order_id
    where (t.update_timestamp <= @sync_last_received_anchor or t.update_originator_id =
           @sync_client_id_hash) and t.order_id = @order_id
    set @sync_rowcount = @@rowcount

    -- the delete trigger will reset the originator id to 0, update it to reflect the synchronizing
       client
    if @sync_rowcount > 0
         update orders_tracking
         set update_originator_id = @sync_client_id_hash           
         where order_id = @order_id                
go

Now let's swing to the code to see how the select incremental commands are set on the adapter. No surprises here too, just use the magic of join to access the metadata to apply the sync logic:

// select incremental inserts command
SqlCommand incInsOrdersCmd = new SqlCommand();
incInsOrdersCmd.CommandType = CommandType.Text;
incInsOrdersCmd.CommandText =
    "select o.order_id, o.order_date " +
    "from [orders] o join [orders_tracking] t on o.order_id = t.order_id " +
    "where t.create_timestamp > @sync_last_received_anchor " +
    "and t.create_timestamp <= @sync_new_received_anchor " +
    "and t.update_originator_id <> @sync_client_id_hash " +
    "order by t.create_timestamp desc "
incInsOrdersCmd.Parameters.Add("@" + SyncSession.SyncClientIdHash, SqlDbType.Int);
incInsOrdersCmd.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Binary, 8);
incInsOrdersCmd.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Binary, 8);

adaptorOrders.SelectIncrementalInsertsCommand = incInsOrdersCmd;

// select incremental updates command
SqlCommand incUpdOrdersCmd = incInsOrdersCmd.Clone();
incUpdOrdersCmd.CommandText =
    "select o.order_id, o.order_date "
+
    "from [orders] o join [orders_tracking] t on o.order_id = t.order_id " +
    "where t.create_timestamp <= @sync_last_received_anchor " +
    "and t.update_timestamp > @sync_last_received_anchor " +
    "and t.update_timestamp <= @sync_new_received_anchor " +
    "and t.update_originator_id <> @sync_client_id_hash " +
    "order by t.update_timestamp desc ";
adaptorOrders.SelectIncrementalUpdatesCommand = incUpdOrdersCmd;

// select incremental deletes command
SqlCommand incDelOrdersCmd = incInsOrdersCmd.Clone();
incDelOrdersCmd.CommandText =
    "select order_id "
+
    "from [orders_tracking] " +
    "where update_timestamp > @sync_last_received_anchor " +
    "and update_timestamp <= @sync_new_received_anchor " +
    "and update_originator_id <> @sync_client_id_hash " +
    "and operation = 'D' " +
    "order by update_timestamp desc ";
adaptorOrders.SelectIncrementalDeletesCommand = incDelOrdersCmd;
 

That's it! Now you can take data offline without touching the schema of the table. At any point, you can take all the sync overhead away from the system without affecting any sync-unaware applications.

There is no one-size-fits-all. As shown in this demo, the sync framework is designed to be flexible and extensible. Find that tracking logic that fits your scenario best and your are ready to tap into the sync framework to give your customers the offline experience that keeps them productive anywhere anytime!
 

Install OfflineAppDemo Application Steps:

  • Fire SQL server management studio and load demo.sql file
  • Execute the script until the "test sample" marker
  • Load server_procs.sql file and execute it to create sync stored procedures on the server
  • Load VS solution (OfflineAppDemo-DecoupledTracking Project)
  • Build the project
  • You are ready to go


Version 1.2 [C#]: Download Now
 


 
     
 
Revision History

     January 26, 2007 - Initial release based on CTP1
     May 16, 2007 - Minor updates for beta 1.0 release
     August 17, 2007 - Update for beta 2.0 release
     November 26, 2007 - Update for RTM

Resources

     Download Synchronization Services for ADO.NET V1 RTM
     Contact me through my blog 
     Communicate feedback through the MSDN forum 
 
 
 
  
 

Copyright © 2007  -  Rafik Robeal
 All Rights Reserved