Der datenzentrische Offline Client, das Microsoft Sync Framework und ADO.NET Sync Services

In meinem letzten Blogpost https://blogs.msdn.com/mtcmuc/archive/2008/12/19/occasionally-connected-clients-zeitweise-verbundene-clients.aspx habe ich über zeitweise verbundene Clients geschrieben und 2 prinzipielle Lösungsansätze vorgestellt.

In diesem Eintrag möchte ich über den datenzentrischen Lösungsansatz schreiben.

Daz

Die Synchronisation der Daten zwischen Client und Server findet hier nah der Datenbank statt. Deswegen sind in Datenbank-Produkten vorhandenen Replikationslösungen =Datenbank-Replikation hier eine potentielle Lösung. Allerdings setzt das auf dem Client und auf dem Server eine Datenbank voraus, die solche Mechanismen unterstützt. Weiterhin muss die Netzwerkverbindung und eventuell notwendige spezielle IP-Ports zwischen der Client und Server-Datenbank in Onlineszenario gewährleistet sein.

Und natürlich muss auch die Auflösen von Synchronisationskonflikten unterstützt werden.

Ein weitere interessante Framework, was für Synchronisation in Frage kommt, ist das Microsoft Sync Framework --> https://msdn.microsoft.com/en-us/sync/default.aspx und --> https://msdn.microsoft.com/en-us/sync/bb821992.aspx

Das Syncframework ist ein erweiterbares Framework für Datensynchronisation, das für folgende Anwendungsbereiche fertige Provider bereitstellt:

  • Sync Services für ADO.NET – Synchronisation von relationalen Datenbanken.
  • Sync Services für Files – Synchronisation des Filesystems
  • Sync Services für RSS – Synchronisation von RSS Feeds.

Wie schon erwähnt, handelt es sich um ein erweiterbares Framework, das durch Bereitstellen eigener Sync-Provider auch auf andere Synchronisationsdaten ausgedehnt werden kann.

Im Kontext des Datenzentrischen Offline Client bieten sich die ADO.NET Sync Services an, mit dem eine Client- und Serverdatenbank synchronisiert werden kann. Als Clientdatenbank kann SqlServerCE dienen als Serverdatenbank kann eine beliebige ADO.NET zugreifbare Datenbank sein. Andere Client- und Server Datencontainer können, wie gesagt, durch eigene Provider ermöglicht werden.

Die ADO.NET Sync Services können sehr einfach im Visual Studio 2008 .NET 3.5 SP1 Lösung genutzt werden. Man muss dazu zu einem ClientProjekt nur "Add New Item --> Local Database Cache".

Daz2

Für eine verteilte Anwendung sieht die Synchronisation Architektur wie folgt aus.

Daz1

Es gibt einen .NET Client mit SqlServer CE als Datenbank. Der Synchronisationsserver stellt ein WCF Serviceschnittstelle zu Verfügung, die vom Client zu Synchronisation aufgerufen werden kann.

Der Client kann wie eine ganz normale, lokale Anwendung programmiert werden. Nur für das Synchronisationsszenario muss ein bisschen Code am Client eingefügt werden.

       private void SyncWithServer(SyncDirection sd)
        {
            try
            {                
                // Anlegen des Sync agents 
                PhonebookLCSyncAgent syncAgent = new PhonebookLCSyncAgent();
                // Anlegen des WCF proxies für den Synchronisations Server 
                SyncSrv.PhonebookLCSyncContractClient syncSrvClient = new SyncSrv.PhonebookLCSyncContractClient("PhonebookSyncSvcEndpoint");
                
                // setzen des WCF proxies als remote provider 
                syncAgent.RemoteProvider = new ServerSyncProviderProxy(syncSrvClient);
                // in welchen Richtungen soll synchronisiert werden / upload / download / up & download
                syncAgent.PhonebookEntries.SyncDirection = sd;
   
                // synchronisieren
                SyncStatistics syncStats = syncAgent.Synchronize();
                System.Diagnostics.Trace.WriteLine(" => new downloads = " + syncStats.TotalChangesDownloaded + "  uploads = " + syncStats.TotalChangesUploaded);
                System.Diagnostics.Trace.WriteLine(" => Download changes applied = " + syncStats.DownloadChangesApplied + " Down changes failed = " + syncStats.DownloadChangesFailed);
                System.Diagnostics.Trace.WriteLine(" => Upload changes applied = " + syncStats.UploadChangesApplied + " Upload changes failed = " + syncStats.UploadChangesFailed);
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.ToString());
                MessageBox.Show(ex.ToString());
            }
            dataGrid1.ItemsSource = null;
        }

Der aufzurufende Servercode sieht so aus:

    public partial class PhonebookLCSyncService : object, IPhonebookLCSyncContract
    {
        private PhonebookLCServerSyncProvider _serverSyncProvider;
        public PhonebookLCSyncService()
        {
            this._serverSyncProvider = new PhonebookLCServerSyncProvider();
            this._serverSyncProvider.ApplyChangeFailed += new EventHandler<ApplyChangeFailedEventArgs>(_serverSyncProvider_ApplyChangeFailed);
        }
        void _serverSyncProvider_ApplyChangeFailed(object sender, ApplyChangeFailedEventArgs e)
        {
            // TODO: implementieren der Sync Konflikte
            Trace.WriteLine("SYNC CONFLICT ---> Action =" + e.Action.ToString() + "   ConflictType =" + e.Conflict.ConflictType.ToString() + "  SyncStage =" + e.Conflict.SyncStage);
            if (e.Error != null)
            {
                Trace.WriteLine("SYNC CONFLICT ---> " + e.Error.ToString());
            }
        }
        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public virtual SyncContext ApplyChanges(SyncGroupMetadata groupMetadata, DataSet dataSet, SyncSession syncSession)
        {
            Trace.WriteLine("PhonebookLCSyncService.ApplyChanges");
            return this._serverSyncProvider.ApplyChanges(groupMetadata, dataSet, syncSession);
        }
        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public virtual SyncContext GetChanges(SyncGroupMetadata groupMetadata, SyncSession syncSession)
        {
            Trace.WriteLine("PhonebookLCSyncService.GetChanges");
            return this._serverSyncProvider.GetChanges(groupMetadata, syncSession);
        }
        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public virtual SyncSchema GetSchema(Collection<string> tableNames, SyncSession syncSession)
        {
            Trace.WriteLine("PhonebookLCSyncService.GetSchema");
            return this._serverSyncProvider.GetSchema(tableNames, syncSession);
        }
        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public virtual SyncServerInfo GetServerInfo(SyncSession syncSession)
        {
            Trace.WriteLine("PhonebookLCSyncService.GetServerInfo");
            return this._serverSyncProvider.GetServerInfo(syncSession);
        }
    }
    [ServiceContractAttribute()]
    public interface IPhonebookLCSyncContract
    {
        [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);
    }

Die kompliziert e Behandlung von Synchronisationsfehler wird am Server erkannt, muss aber durch Vorgaben oder programmtisches Auflösen nach der Erkennung iM Servercode "void _serverSyncProvider_ApplyChangeFailed(object sender, ApplyChangeFailedEventArgs e)" geleistet werden.

Viel Spass beim ADO.NET synchronisieren ... GunnarD