PageRenderTime 24ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/Main/src/UniSync.Core/AzureSync.cs

http://unisync.codeplex.com
C# | 277 lines | 176 code | 48 blank | 53 comment | 16 complexity | fb44b8ffe086da1f73700dce02d9d6d2 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Configuration;
  6. using System.Data.SqlClient;
  7. using Microsoft.Synchronization.Data;
  8. using Microsoft.Synchronization.Data.SqlServer;
  9. using Microsoft.Synchronization;
  10. using System.Data;
  11. using System.IO;
  12. namespace UniSync.Core
  13. {
  14. /// <summary>
  15. ///
  16. /// </summary>
  17. public class AzureSync
  18. {
  19. #region == Publics Members ========================
  20. /// <summary>
  21. /// The connectionString to the main Database
  22. /// </summary>
  23. public String LocalConnectionString { get; set; }
  24. /// <summary>
  25. /// The connectionString to the AzureSql Database
  26. /// </summary>
  27. public String AzureConnectionString { get; set; }
  28. /// <summary>
  29. /// The Name of the Synchronisation scope
  30. /// </summary>
  31. public String ScopeName { get; set; }
  32. /// <summary>
  33. /// All the table names that need to be sync for the current ScopeName
  34. /// Separated by a comma
  35. /// </summary>
  36. public string[] TableNamesToSync { get; set; }
  37. #endregion
  38. #region == Constructor ============================
  39. /// <summary>
  40. /// Constructor
  41. /// </summary>
  42. /// <param name="localConnectionString">Connection String to a MS SQL Database</param>
  43. /// <param name="azureConnectionString">Connection String to a SQLAzure Database</param>
  44. /// <param name="scopeName">The scope name of this sychronization</param>
  45. /// <param name="tableNamesToSync">List of table name separated by a ","</param>
  46. public AzureSync(String localConnectionString, String azureConnectionString, String scopeName, String[] tableNamesToSync)
  47. {
  48. LocalConnectionString = localConnectionString;
  49. AzureConnectionString = azureConnectionString;
  50. ScopeName = scopeName;
  51. TableNamesToSync = tableNamesToSync;
  52. }
  53. #endregion
  54. #region == Publics Functions ======================
  55. /// <summary>
  56. /// Run once this will add the table and the tools needed
  57. /// BAse on the LocalConnection
  58. /// </summary>
  59. public void Setup()
  60. {
  61. using (var _sqlServerConn = new SqlConnection(LocalConnectionString))
  62. {
  63. using (var _sqlAzureConn = new SqlConnection(AzureConnectionString))
  64. {
  65. var _scope = new DbSyncScopeDescription(ScopeName);
  66. // Get the table infos from the Local database
  67. foreach (var _tblName in TableNamesToSync)
  68. {
  69. var _tblToSync = SqlSyncDescriptionBuilder.GetDescriptionForTable(_tblName, _sqlServerConn);
  70. _scope.Tables.Add(_tblToSync);
  71. }
  72. // Prepare Local Provisioning
  73. var _sqlServerProv = new SqlSyncScopeProvisioning(_sqlServerConn, _scope);
  74. if (!_sqlServerProv.ScopeExists(ScopeName))
  75. _sqlServerProv.Apply();
  76. // Prepare Cloud Provisioning
  77. var _sqlAzureProv = new SqlSyncScopeProvisioning(_sqlAzureConn, _scope);
  78. if (!_sqlAzureProv.ScopeExists(ScopeName))
  79. _sqlAzureProv.Apply();
  80. }
  81. }
  82. }
  83. /// <summary>
  84. /// Do the synchronisation
  85. /// </summary>
  86. public void Sync()
  87. {
  88. using (
  89. var _sqlServerConn = new SqlConnection(LocalConnectionString))
  90. {
  91. using (var _sqlAzureConn = new SqlConnection(AzureConnectionString))
  92. {
  93. var localProvider = new SqlSyncProvider(ScopeName, _sqlAzureConn);
  94. var remoteProvider = new SqlSyncProvider(ScopeName, _sqlServerConn);
  95. localProvider.ChangesSelected += localProvider_ChangesSelected;
  96. remoteProvider.ChangesSelected += remoteProvider_ChangesSelected;
  97. localProvider.ApplyChangeFailed += localProvider_ApplyChangeFailed;
  98. remoteProvider.ApplyChangeFailed += remoteProvider_ApplyChangeFailed;
  99. var syncOrchestrator = new SyncOrchestrator
  100. {
  101. LocalProvider = localProvider,
  102. RemoteProvider = remoteProvider,
  103. Direction = SyncDirectionOrder.UploadAndDownload
  104. };
  105. var syncStats = syncOrchestrator.Synchronize();
  106. }
  107. }
  108. }
  109. private void localProvider_ChangesSelected(object sender, DbChangesSelectedEventArgs e)
  110. {
  111. }
  112. void remoteProvider_ChangesSelected(object sender, DbChangesSelectedEventArgs e)
  113. {
  114. }
  115. private void localProvider_ApplyChangeFailed(object sender, DbApplyChangeFailedEventArgs e)
  116. {
  117. if (this.LocalApplyChangeFailed != null)
  118. this.LocalApplyChangeFailed(sender, e);
  119. LogConflict(e);
  120. }
  121. private void remoteProvider_ApplyChangeFailed(object sender, DbApplyChangeFailedEventArgs e)
  122. {
  123. if (this.AzureApplyChangeFailed != null)
  124. this.AzureApplyChangeFailed(sender, e);
  125. LogConflict(e);
  126. }
  127. /// <summary>
  128. /// Delete the metadata for the current Scope, older than the specified number of days
  129. /// </summary>
  130. /// <remarks>Cleanup is retention-based, which means that metadata that is older
  131. /// than the specified number of days is deleted</remarks>
  132. public void CleanDataScopeOlderThan(int nbDays)
  133. {
  134. CleanDataScopeOlderThanByDB(this.LocalConnectionString, nbDays);
  135. CleanDataScopeOlderThanByDB(this.AzureConnectionString, nbDays);
  136. }
  137. /// <summary>
  138. /// Reset the MetaData for the current Scope.
  139. /// </summary>
  140. public void ScopeReset()
  141. {
  142. ScopeResetByDB(this.LocalConnectionString);
  143. ScopeResetByDB(this.AzureConnectionString);
  144. this.Setup();
  145. }
  146. /// <summary>
  147. /// Delete ALL table related to the Sync Framework
  148. /// <remarks>All scopes are affected, and metadata tables too.</remarks>
  149. /// </summary>
  150. public void DeleteSync() {
  151. DeleteSyncByBD(this.LocalConnectionString);
  152. DeleteSyncByBD(this.AzureConnectionString);
  153. }
  154. #endregion
  155. #region == Privates Functions =====================
  156. private void CleanDataScopeOlderThanByDB(String connectionString, int nbDays)
  157. {
  158. using (var _sqlConn = new SqlConnection(connectionString))
  159. {
  160. var _cleaner = new SqlSyncStoreMetadataCleanup(_sqlConn);
  161. _cleaner.RetentionInDays = nbDays;
  162. _cleaner.PerformCleanup();
  163. }
  164. }
  165. private void ScopeResetByDB(String connectionString)
  166. {
  167. using (var _sqlConn = new SqlConnection(connectionString))
  168. {
  169. var _cleaner = new SqlSyncScopeDeprovisioning(_sqlConn);
  170. _cleaner.DeprovisionScope(this.ScopeName);
  171. }
  172. }
  173. private void DeleteSyncByBD(String connectionString)
  174. {
  175. using (var _sqlConn = new SqlConnection(connectionString))
  176. {
  177. var _cleaner = new SqlSyncScopeDeprovisioning(_sqlConn);
  178. _cleaner.DeprovisionStore();
  179. }
  180. }
  181. private void LogConflict(DbApplyChangeFailedEventArgs e)
  182. {
  183. string DbConflictDetected = e.Connection.Database.ToString();
  184. DataTable conflictingRemoteChange = e.Conflict.RemoteChange;
  185. DataTable conflictingLocalChange = e.Conflict.LocalChange;
  186. int remoteColumnCount = conflictingRemoteChange.Columns.Count;
  187. int localColumnCount = conflictingLocalChange.Columns.Count;
  188. var _sb = new StringBuilder();
  189. _sb.Append("Row from " + DbConflictDetected + System.Environment.NewLine);
  190. for (int i = 0; i < localColumnCount; i++)
  191. {
  192. _sb.Append(conflictingLocalChange.Rows[0][i] + "| ");
  193. }
  194. _sb.Append(System.Environment.NewLine + "Row from OtherDB " + System.Environment.NewLine);
  195. for (int i = 0; i < remoteColumnCount; i++)
  196. {
  197. _sb.Append(conflictingRemoteChange.Rows[0][i] + "| ");
  198. }
  199. e.Action = ApplyAction.RetryWithForceWrite;
  200. using (var _sqlServerConn = new SqlConnection(LocalConnectionString))
  201. {
  202. var _cmdInsLog = new SqlCommand("SyncErrorLog_INSERT", _sqlServerConn);
  203. _cmdInsLog.CommandType = CommandType.StoredProcedure;
  204. _cmdInsLog.Parameters.Add(new SqlParameter("@ConflitType", e.Conflict.Type.ToString()));
  205. _cmdInsLog.Parameters.Add(new SqlParameter("@Message", (e.Conflict.ErrorMessage != null) ? e.Conflict.ErrorMessage : String.Empty));
  206. _cmdInsLog.Parameters.Add(new SqlParameter("@DbConflictDetected", e.Connection.Database.ToString()));
  207. _cmdInsLog.Parameters.Add(new SqlParameter("@EventDateTime", DateTime.Now.ToString()));
  208. _cmdInsLog.Parameters.Add(new SqlParameter("@Detail", _sb.ToString()));
  209. if (_sqlServerConn.State != ConnectionState.Open)
  210. _cmdInsLog.Connection.Open();
  211. _cmdInsLog.ExecuteNonQuery();
  212. _cmdInsLog.Connection.Close();
  213. }
  214. }
  215. #endregion
  216. #region == Event Handlers =========================
  217. /// <summary>
  218. /// Called when a change from the local database is in conflict with a change from the Azure database.
  219. /// To override the sync framework's default behavior, the event handler should set e.Action to the desired action.
  220. /// </summary>
  221. public event EventHandler<DbApplyChangeFailedEventArgs> LocalApplyChangeFailed;
  222. /// <summary>
  223. /// Called when a change from the Azure database is in conflict with a change from the local database.
  224. /// To override the sync framework's default behavior, the event handler should set e.Action to the desired action.
  225. /// </summary>
  226. public event EventHandler<DbApplyChangeFailedEventArgs> AzureApplyChangeFailed;
  227. #endregion
  228. }
  229. }