/Main/src/UniSync.Core/AzureSync.cs
C# | 277 lines | 176 code | 48 blank | 53 comment | 16 complexity | fb44b8ffe086da1f73700dce02d9d6d2 MD5 | raw file
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Configuration;
- using System.Data.SqlClient;
- using Microsoft.Synchronization.Data;
- using Microsoft.Synchronization.Data.SqlServer;
- using Microsoft.Synchronization;
- using System.Data;
- using System.IO;
-
- namespace UniSync.Core
- {
- /// <summary>
- ///
- /// </summary>
- public class AzureSync
- {
- #region == Publics Members ========================
-
- /// <summary>
- /// The connectionString to the main Database
- /// </summary>
- public String LocalConnectionString { get; set; }
-
- /// <summary>
- /// The connectionString to the AzureSql Database
- /// </summary>
- public String AzureConnectionString { get; set; }
-
- /// <summary>
- /// The Name of the Synchronisation scope
- /// </summary>
- public String ScopeName { get; set; }
-
- /// <summary>
- /// All the table names that need to be sync for the current ScopeName
- /// Separated by a comma
- /// </summary>
- public string[] TableNamesToSync { get; set; }
-
- #endregion
-
- #region == Constructor ============================
-
- /// <summary>
- /// Constructor
- /// </summary>
- /// <param name="localConnectionString">Connection String to a MS SQL Database</param>
- /// <param name="azureConnectionString">Connection String to a SQLAzure Database</param>
- /// <param name="scopeName">The scope name of this sychronization</param>
- /// <param name="tableNamesToSync">List of table name separated by a ","</param>
- public AzureSync(String localConnectionString, String azureConnectionString, String scopeName, String[] tableNamesToSync)
- {
- LocalConnectionString = localConnectionString;
- AzureConnectionString = azureConnectionString;
- ScopeName = scopeName;
- TableNamesToSync = tableNamesToSync;
- }
-
- #endregion
-
- #region == Publics Functions ======================
-
- /// <summary>
- /// Run once this will add the table and the tools needed
- /// BAse on the LocalConnection
- /// </summary>
- public void Setup()
- {
-
- using (var _sqlServerConn = new SqlConnection(LocalConnectionString))
- {
- using (var _sqlAzureConn = new SqlConnection(AzureConnectionString))
- {
- var _scope = new DbSyncScopeDescription(ScopeName);
-
- // Get the table infos from the Local database
- foreach (var _tblName in TableNamesToSync)
- {
- var _tblToSync = SqlSyncDescriptionBuilder.GetDescriptionForTable(_tblName, _sqlServerConn);
- _scope.Tables.Add(_tblToSync);
- }
-
- // Prepare Local Provisioning
- var _sqlServerProv = new SqlSyncScopeProvisioning(_sqlServerConn, _scope);
- if (!_sqlServerProv.ScopeExists(ScopeName))
- _sqlServerProv.Apply();
-
- // Prepare Cloud Provisioning
- var _sqlAzureProv = new SqlSyncScopeProvisioning(_sqlAzureConn, _scope);
- if (!_sqlAzureProv.ScopeExists(ScopeName))
- _sqlAzureProv.Apply();
- }
- }
- }
-
- /// <summary>
- /// Do the synchronisation
- /// </summary>
- public void Sync()
- {
- using (
- var _sqlServerConn = new SqlConnection(LocalConnectionString))
- {
- using (var _sqlAzureConn = new SqlConnection(AzureConnectionString))
- {
- var localProvider = new SqlSyncProvider(ScopeName, _sqlAzureConn);
- var remoteProvider = new SqlSyncProvider(ScopeName, _sqlServerConn);
-
- localProvider.ChangesSelected += localProvider_ChangesSelected;
- remoteProvider.ChangesSelected += remoteProvider_ChangesSelected;
- localProvider.ApplyChangeFailed += localProvider_ApplyChangeFailed;
- remoteProvider.ApplyChangeFailed += remoteProvider_ApplyChangeFailed;
-
- var syncOrchestrator = new SyncOrchestrator
- {
- LocalProvider = localProvider,
- RemoteProvider = remoteProvider,
- Direction = SyncDirectionOrder.UploadAndDownload
- };
-
- var syncStats = syncOrchestrator.Synchronize();
- }
- }
- }
-
- private void localProvider_ChangesSelected(object sender, DbChangesSelectedEventArgs e)
- {
- }
-
- void remoteProvider_ChangesSelected(object sender, DbChangesSelectedEventArgs e)
- {
- }
-
- private void localProvider_ApplyChangeFailed(object sender, DbApplyChangeFailedEventArgs e)
- {
- if (this.LocalApplyChangeFailed != null)
- this.LocalApplyChangeFailed(sender, e);
-
- LogConflict(e);
- }
-
- private void remoteProvider_ApplyChangeFailed(object sender, DbApplyChangeFailedEventArgs e)
- {
- if (this.AzureApplyChangeFailed != null)
- this.AzureApplyChangeFailed(sender, e);
-
- LogConflict(e);
- }
-
- /// <summary>
- /// Delete the metadata for the current Scope, older than the specified number of days
- /// </summary>
- /// <remarks>Cleanup is retention-based, which means that metadata that is older
- /// than the specified number of days is deleted</remarks>
- public void CleanDataScopeOlderThan(int nbDays)
- {
- CleanDataScopeOlderThanByDB(this.LocalConnectionString, nbDays);
- CleanDataScopeOlderThanByDB(this.AzureConnectionString, nbDays);
- }
-
- /// <summary>
- /// Reset the MetaData for the current Scope.
- /// </summary>
- public void ScopeReset()
- {
- ScopeResetByDB(this.LocalConnectionString);
- ScopeResetByDB(this.AzureConnectionString);
-
- this.Setup();
- }
-
- /// <summary>
- /// Delete ALL table related to the Sync Framework
- /// <remarks>All scopes are affected, and metadata tables too.</remarks>
- /// </summary>
- public void DeleteSync() {
- DeleteSyncByBD(this.LocalConnectionString);
- DeleteSyncByBD(this.AzureConnectionString);
- }
-
- #endregion
-
- #region == Privates Functions =====================
-
- private void CleanDataScopeOlderThanByDB(String connectionString, int nbDays)
- {
- using (var _sqlConn = new SqlConnection(connectionString))
- {
- var _cleaner = new SqlSyncStoreMetadataCleanup(_sqlConn);
- _cleaner.RetentionInDays = nbDays;
- _cleaner.PerformCleanup();
- }
- }
-
- private void ScopeResetByDB(String connectionString)
- {
- using (var _sqlConn = new SqlConnection(connectionString))
- {
- var _cleaner = new SqlSyncScopeDeprovisioning(_sqlConn);
- _cleaner.DeprovisionScope(this.ScopeName);
- }
- }
-
- private void DeleteSyncByBD(String connectionString)
- {
- using (var _sqlConn = new SqlConnection(connectionString))
- {
- var _cleaner = new SqlSyncScopeDeprovisioning(_sqlConn);
- _cleaner.DeprovisionStore();
- }
- }
-
-
- private void LogConflict(DbApplyChangeFailedEventArgs e)
- {
- string DbConflictDetected = e.Connection.Database.ToString();
-
- DataTable conflictingRemoteChange = e.Conflict.RemoteChange;
- DataTable conflictingLocalChange = e.Conflict.LocalChange;
- int remoteColumnCount = conflictingRemoteChange.Columns.Count;
- int localColumnCount = conflictingLocalChange.Columns.Count;
-
- var _sb = new StringBuilder();
- _sb.Append("Row from " + DbConflictDetected + System.Environment.NewLine);
- for (int i = 0; i < localColumnCount; i++)
- {
- _sb.Append(conflictingLocalChange.Rows[0][i] + "| ");
- }
-
- _sb.Append(System.Environment.NewLine + "Row from OtherDB " + System.Environment.NewLine);
- for (int i = 0; i < remoteColumnCount; i++)
- {
- _sb.Append(conflictingRemoteChange.Rows[0][i] + "| ");
- }
-
-
- e.Action = ApplyAction.RetryWithForceWrite;
-
- using (var _sqlServerConn = new SqlConnection(LocalConnectionString))
- {
- var _cmdInsLog = new SqlCommand("SyncErrorLog_INSERT", _sqlServerConn);
- _cmdInsLog.CommandType = CommandType.StoredProcedure;
- _cmdInsLog.Parameters.Add(new SqlParameter("@ConflitType", e.Conflict.Type.ToString()));
- _cmdInsLog.Parameters.Add(new SqlParameter("@Message", (e.Conflict.ErrorMessage != null) ? e.Conflict.ErrorMessage : String.Empty));
- _cmdInsLog.Parameters.Add(new SqlParameter("@DbConflictDetected", e.Connection.Database.ToString()));
- _cmdInsLog.Parameters.Add(new SqlParameter("@EventDateTime", DateTime.Now.ToString()));
- _cmdInsLog.Parameters.Add(new SqlParameter("@Detail", _sb.ToString()));
-
- if (_sqlServerConn.State != ConnectionState.Open)
- _cmdInsLog.Connection.Open();
- _cmdInsLog.ExecuteNonQuery();
-
- _cmdInsLog.Connection.Close();
- }
- }
-
- #endregion
-
- #region == Event Handlers =========================
- /// <summary>
- /// Called when a change from the local database is in conflict with a change from the Azure database.
- /// To override the sync framework's default behavior, the event handler should set e.Action to the desired action.
- /// </summary>
- public event EventHandler<DbApplyChangeFailedEventArgs> LocalApplyChangeFailed;
-
- /// <summary>
- /// Called when a change from the Azure database is in conflict with a change from the local database.
- /// To override the sync framework's default behavior, the event handler should set e.Action to the desired action.
- /// </summary>
- public event EventHandler<DbApplyChangeFailedEventArgs> AzureApplyChangeFailed;
- #endregion
-
- }
- }