DeleteClientScopeInfoAsync(ClientScopeInfo clientScopeInfo, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { var context = new SyncContext(Guid.NewGuid(), clientScopeInfo.Name); try { await using var runner = await this.GetConnectionAsync(context, SyncMode.Writing, SyncStage.ScopeWriting, connection, transaction, cancellationToken, progress).ConfigureAwait(false); bool exists; (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.Client, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); if (!exists) { await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.Client, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); } bool isDeleted; // Write scopes locally (context, isDeleted) = await this.InternalDeleteClientScopeInfoAsync(clientScopeInfo, context, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); return(isDeleted); } catch (Exception ex) { throw GetSyncError(context, ex); } }
public OperationArgs(SyncContext context, ServerScopeInfo serverScopeInfo, ClientScopeInfo clientScopeInfo, DbConnection connection = null, DbTransaction transaction = null) : base(context, connection, transaction) { this.ServerScopeInfo = serverScopeInfo; this.ClientScopeInfo = clientScopeInfo; }
private DbCommand InternalSetDeleteClientScopeInfoParameters(ClientScopeInfo clientScopeInfo, DbCommand command) { DbSyncAdapter.SetParameterValue(command, "sync_scope_id", clientScopeInfo.Id.ToString()); DbSyncAdapter.SetParameterValue(command, "sync_scope_name", clientScopeInfo.Name); return(command); }
private async Task <Version> UpgdrateTo094Async(ClientScopeInfo clientScopeInfo, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { var newVersion = new Version(0, 9, 4); // Sorting tables based on dependencies between them var schemaTables = clientScopeInfo.Schema.Tables .SortByDependencies(tab => tab.GetRelations() .Select(r => r.GetParentTable())); var message = $"Upgrade to {newVersion}:"; await this.InterceptAsync(new UpgradeProgressArgs(context, message, newVersion, connection, transaction), progress, cancellationToken).ConfigureAwait(false); var provision = SyncProvision.StoredProcedures | SyncProvision.Triggers; await this.DeprovisionAsync(clientScopeInfo.Name, provision, connection, transaction, cancellationToken, progress).ConfigureAwait(false); var clientScope = await this.GetClientScopeInfoAsync(clientScopeInfo.Name, connection, transaction, cancellationToken, progress).ConfigureAwait(false); var serverScope = new ServerScopeInfo { Schema = clientScope.Schema, Setup = clientScope.Setup, Version = clientScope.Version }; clientScope = await this.ProvisionAsync(serverScope, provision, true, connection, transaction, cancellationToken, progress).ConfigureAwait(false); return(newVersion); }
/// <summary> /// Make a shadow copy of an old scope to get the last sync information copied on this scope /// </summary> /// <param name="oldClientScopeInfo">old client scope that we we copy infos</param> public void ShadowScope(ClientScopeInfo oldClientScopeInfo) { this.LastServerSyncTimestamp = oldClientScopeInfo.LastServerSyncTimestamp; this.LastSyncTimestamp = oldClientScopeInfo.LastSyncTimestamp; this.LastSync = oldClientScopeInfo.LastSync; this.LastSyncDuration = oldClientScopeInfo.LastSyncDuration; }
GetChangesAsync(ClientScopeInfo clientScope, SyncParameters parameters = default, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { var context = new SyncContext(Guid.NewGuid(), clientScope.Name); if (parameters != null) { context.Parameters = parameters; } try { await using var runner = await this.GetConnectionAsync(context, SyncMode.Reading, SyncStage.ChangesSelecting, connection, transaction, cancellationToken, progress).ConfigureAwait(false); // Before getting changes, be sure we have a remote schema available ServerScopeInfo serverScopeInfo; (context, serverScopeInfo) = await this.InternalGetServerScopeInfoAsync(context, clientScope.Setup, runner.Connection, runner.Transaction, cancellationToken, progress); // TODO : if serverScope.Schema is null, should we Provision here ? // Should we ? if (serverScopeInfo.Schema == null) { throw new MissingRemoteOrchestratorSchemaException(); } //Direction set to Download context.SyncWay = SyncWay.Download; // Output // JUST Before get changes, get the timestamp, to be sure to // get rows inserted / updated elsewhere since the sync is not over long remoteClientTimestamp; (context, remoteClientTimestamp) = await this.InternalGetLocalTimestampAsync(context, runner.Connection, runner.Transaction, cancellationToken, progress); // Get if we need to get all rows from the datasource var fromScratch = clientScope.IsNewScope || context.SyncType == SyncType.Reinitialize || context.SyncType == SyncType.ReinitializeWithUpload; BatchInfo serverBatchInfo; DatabaseChangesSelected serverChangesSelected; // When we get the chnages from server, we create the batches if it's requested by the client // the batch decision comes from batchsize from client (context, serverBatchInfo, serverChangesSelected) = await this.InternalGetChangesAsync(clientScope, context, fromScratch, clientScope.LastServerSyncTimestamp, remoteClientTimestamp, clientScope.Id, this.Provider.SupportsMultipleActiveResultSets, this.Options.BatchDirectory, null, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); return(new ServerSyncChanges(remoteClientTimestamp, serverBatchInfo, serverChangesSelected)); } catch (Exception ex) { throw GetSyncError(context, ex); } }
InternalLoadClientScopeInfoAsync(SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetClientScopeInfo, connection, transaction); if (command == null) { return(context, null); } DbSyncAdapter.SetParameterValue(command, "sync_scope_name", context.ScopeName); var action = new ClientScopeInfoLoadingArgs(context, context.ScopeName, command, connection, transaction); await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); if (action.Cancel || action.Command == null) { return(context, null); } await this.InterceptAsync(new DbCommandArgs(context, action.Command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); using DbDataReader reader = await action.Command.ExecuteReaderAsync().ConfigureAwait(false); ClientScopeInfo clientScopeInfo = null; if (reader.Read()) { clientScopeInfo = InternalReadClientScopeInfo(reader); } reader.Close(); if (clientScopeInfo?.Schema != null) { clientScopeInfo.Schema.EnsureSchema(); } if (clientScopeInfo != null) { var scopeLoadedArgs = new ClientScopeInfoLoadedArgs(context, context.ScopeName, clientScopeInfo, connection, transaction); await this.InterceptAsync(scopeLoadedArgs, progress, cancellationToken).ConfigureAwait(false); clientScopeInfo = scopeLoadedArgs.ClientScopeInfo; } action.Command.Dispose(); return(context, clientScopeInfo); }
private async Task <Version> UpgdrateTo095Async(ClientScopeInfo scopeInfo, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { await using var runner = await this.GetConnectionAsync(context, SyncMode.Writing, SyncStage.Migrating, connection, transaction, cancellationToken, progress).ConfigureAwait(false); var newVersion = new Version(0, 9, 5); await this.InterceptAsync(new UpgradeProgressArgs(context, $"Upgrade client scope {scopeInfo.Name} to {newVersion}:", newVersion, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); // get scope info table name var parsedName = ParserName.Parse(this.Options.ScopeInfoTableName); var scopeClientInfoTableName = $"{parsedName.Unquoted().Normalized().ToString()}"; var syncTable = new SyncTable(scopeClientInfoTableName); var scopeClientInfoTableBuilder = this.GetTableBuilder(syncTable, scopeInfo); var pkeys = await scopeClientInfoTableBuilder.GetPrimaryKeysAsync(runner.Connection, runner.Transaction).ConfigureAwait(false); if (pkeys.Count() == 1) { if (this.Provider.GetProviderTypeName().Contains("Dotmim.Sync.SqlServer.SqlSyncProvider")) { var commandText = @$ "ALTER TABLE dbo.{scopeClientInfoTableName} DROP CONSTRAINT PK_{scopeClientInfoTableName}; ALTER TABLE dbo.{scopeClientInfoTableName} ADD CONSTRAINT PK_{scopeClientInfoTableName} PRIMARY KEY CLUSTERED (sync_scope_id, sync_scope_name);"; var command = runner.Connection.CreateCommand(); command.CommandText = commandText; command.Transaction = runner.Transaction; await command.ExecuteNonQueryAsync(); await this.InterceptAsync(new UpgradeProgressArgs(context, $"{scopeClientInfoTableName} primary keys updated on SQL Server", newVersion, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); } if (this.Provider.GetProviderTypeName().Contains("Dotmim.Sync.MySql.MySqlSyncProvider")) { var commandText = @$ "ALTER TABLE `{scopeClientInfoTableName}` CHANGE COLUMN `sync_scope_name` `sync_scope_name` VARCHAR(100) NOT NULL , DROP PRIMARY KEY, ADD PRIMARY KEY (`sync_scope_id`, `sync_scope_name`);"; var command = runner.Connection.CreateCommand(); command.CommandText = commandText; command.Transaction = runner.Transaction; await command.ExecuteNonQueryAsync(); } if (this.Provider.GetProviderTypeName().Contains("Dotmim.Sync.Sqlite.SqliteSyncProvider")) { var commandText = @$ " PRAGMA foreign_keys=off; BEGIN TRANSACTION; ALTER TABLE [{scopeClientInfoTableName}] RENAME TO old_table_{scopeClientInfoTableName};
InternalSaveClientScopeInfoAsync(ClientScopeInfo clientScopeInfo, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); bool scopeExists; (context, scopeExists) = await InternalExistsClientScopeInfoAsync(clientScopeInfo.Name, context, connection, transaction, cancellationToken, progress).ConfigureAwait(false); DbCommand command; if (scopeExists) { command = scopeBuilder.GetCommandAsync(DbScopeCommandType.UpdateClientScopeInfo, connection, transaction); } else { command = scopeBuilder.GetCommandAsync(DbScopeCommandType.InsertClientScopeInfo, connection, transaction); } if (command == null) { return(context, null); } command = InternalSetSaveClientScopeInfoParameters(clientScopeInfo, command); var action = new ScopeSavingArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), DbScopeType.Client, clientScopeInfo, command, connection, transaction); await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); if (action.Cancel || action.Command == null) { return(context, null); } await this.InterceptAsync(new DbCommandArgs(context, action.Command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); using DbDataReader reader = await action.Command.ExecuteReaderAsync().ConfigureAwait(false); reader.Read(); clientScopeInfo = InternalReadClientScopeInfo(reader); reader.Close(); await this.InterceptAsync(new ScopeSavedArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), DbScopeType.Client, clientScopeInfo, connection, transaction), progress, cancellationToken).ConfigureAwait(false); action.Command.Dispose(); return(context, clientScopeInfo); }
private DbCommand InternalSetSaveClientScopeInfoParameters(ClientScopeInfo clientScopeInfo, DbCommand command) { DbSyncAdapter.SetParameterValue(command, "sync_scope_id", clientScopeInfo.Id.ToString()); DbSyncAdapter.SetParameterValue(command, "sync_scope_name", clientScopeInfo.Name); DbSyncAdapter.SetParameterValue(command, "sync_scope_schema", clientScopeInfo.Schema == null ? DBNull.Value : (object)JsonConvert.SerializeObject(clientScopeInfo.Schema)); DbSyncAdapter.SetParameterValue(command, "sync_scope_setup", clientScopeInfo.Setup == null ? DBNull.Value : (object)JsonConvert.SerializeObject(clientScopeInfo.Setup)); DbSyncAdapter.SetParameterValue(command, "sync_scope_version", clientScopeInfo.Version); DbSyncAdapter.SetParameterValue(command, "scope_last_sync", clientScopeInfo.LastSync.HasValue ? (object)clientScopeInfo.LastSync.Value : DBNull.Value); DbSyncAdapter.SetParameterValue(command, "scope_last_sync_timestamp", clientScopeInfo.LastSyncTimestamp); DbSyncAdapter.SetParameterValue(command, "scope_last_server_sync_timestamp", clientScopeInfo.LastServerSyncTimestamp); DbSyncAdapter.SetParameterValue(command, "scope_last_sync_duration", clientScopeInfo.LastSyncDuration); return(command); }
InternalProvisionClientAsync(ServerScopeInfo serverScopeInfo, ClientScopeInfo clientScopeInfo, SyncContext context, SyncProvision provision = default, bool overwrite = true, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { try { if (serverScopeInfo.Schema == null) { throw new Exception($"No Schema in your server scope info {serverScopeInfo.Name}"); } if (serverScopeInfo.Schema == null) { throw new Exception($"No Setup in your server scope info {serverScopeInfo.Name}"); } await using var runner = await this.GetConnectionAsync(context, SyncMode.Writing, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); // Check incompatibility with the flags if (provision.HasFlag(SyncProvision.ServerHistoryScope) || provision.HasFlag(SyncProvision.ServerScope)) { throw new InvalidProvisionForLocalOrchestratorException(); } // 2) Provision if (provision == SyncProvision.None) { provision = SyncProvision.Table | SyncProvision.StoredProcedures | SyncProvision.Triggers | SyncProvision.TrackingTable; } (context, _) = await this.InternalProvisionAsync(serverScopeInfo, context, overwrite, provision, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); // set client scope setup and schema clientScopeInfo.Setup = serverScopeInfo.Setup; clientScopeInfo.Schema = serverScopeInfo.Schema; // Write scopes locally (context, clientScopeInfo) = await this.InternalSaveClientScopeInfoAsync(clientScopeInfo, context, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); return(context, clientScopeInfo); } catch (Exception ex) { throw GetSyncError(context, ex); } }
private ClientScopeInfo InternalReadClientScopeInfo(DbDataReader reader) { var clientScopeInfo = new ClientScopeInfo { Id = reader.GetGuid(reader.GetOrdinal("sync_scope_id")), Name = reader["sync_scope_name"] as string, Schema = reader["sync_scope_schema"] == DBNull.Value ? null : JsonConvert.DeserializeObject <SyncSet>((string)reader["sync_scope_schema"]), Setup = reader["sync_scope_setup"] == DBNull.Value ? null : JsonConvert.DeserializeObject <SyncSetup>((string)reader["sync_scope_setup"]), Version = reader["sync_scope_version"] as string, LastSync = reader["scope_last_sync"] != DBNull.Value ? reader.GetDateTime(reader.GetOrdinal("scope_last_sync")) : null, LastServerSyncTimestamp = reader["scope_last_server_sync_timestamp"] != DBNull.Value ? (long?)reader.GetInt64(reader.GetOrdinal("scope_last_server_sync_timestamp")) : null, LastSyncTimestamp = reader["scope_last_sync_timestamp"] != DBNull.Value ? (long?)reader.GetInt64(reader.GetOrdinal("scope_last_sync_timestamp")) : null, LastSyncDuration = reader["scope_last_sync_duration"] != DBNull.Value ? reader.GetInt64(reader.GetOrdinal("scope_last_sync_duration")) : 0L }; clientScopeInfo.IsNewScope = clientScopeInfo.LastSync == null; return(clientScopeInfo); }
InternalGetOperationAsync(ServerScopeInfo serverScopeInfo, ClientScopeInfo clientScopeInfo, SyncContext context, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { try { SyncOperation syncOperation = SyncOperation.Normal; await using var runner = await this.GetConnectionAsync(context, SyncMode.Writing, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); var operationArgs = new OperationArgs(context, serverScopeInfo, clientScopeInfo, runner.Connection, runner.Transaction); await this.InterceptAsync(operationArgs, runner.Progress, runner.CancellationToken).ConfigureAwait(false); syncOperation = operationArgs.Operation; await runner.CommitAsync().ConfigureAwait(false); return(context, syncOperation); } catch (Exception ex) { throw GetSyncError(context, ex); } }
InternalApplySnapshotAsync(ClientScopeInfo clientScopeInfo, SyncContext context, BatchInfo serverBatchInfo, long clientTimestamp, long remoteClientTimestamp, DatabaseChangesSelected databaseChangesSelected, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { try { if (serverBatchInfo == null) { return(context, new DatabaseChangesApplied(), clientScopeInfo); } // Get context or create a new one context.SyncStage = SyncStage.SnapshotApplying; await this.InterceptAsync(new SnapshotApplyingArgs(context, this.Provider.CreateConnection()), progress, cancellationToken).ConfigureAwait(false); if (clientScopeInfo.Schema == null) { throw new ArgumentNullException(nameof(clientScopeInfo.Schema)); } // Applying changes and getting the new client scope info var(syncContext, changesApplied, newClientScopeInfo) = await this.InternalApplyChangesAsync(clientScopeInfo, context, serverBatchInfo, clientTimestamp, remoteClientTimestamp, ConflictResolutionPolicy.ServerWins, false, databaseChangesSelected, connection, transaction, cancellationToken, progress).ConfigureAwait(false); await this.InterceptAsync(new SnapshotAppliedArgs(context, changesApplied), progress, cancellationToken).ConfigureAwait(false); // re-apply scope is new flag // to be sure we are calling the Initialize method, even for the delta // in that particular case, we want the delta rows coming from the current scope newClientScopeInfo.IsNewScope = true; return(context, changesApplied, newClientScopeInfo); } catch (Exception ex) { throw GetSyncError(context, ex); } }
InternalDeleteClientScopeInfoAsync(ClientScopeInfo clientScopeInfo, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); bool scopeExists; (context, scopeExists) = await InternalExistsClientScopeInfoAsync(clientScopeInfo.Name, context, connection, transaction, cancellationToken, progress).ConfigureAwait(false); if (!scopeExists) { return(context, true); } using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.DeleteClientScopeInfo, connection, transaction); InternalSetDeleteClientScopeInfoParameters(clientScopeInfo, command); var action = new ScopeSavingArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), DbScopeType.Client, clientScopeInfo, command, connection, transaction); await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); if (action.Cancel || action.Command == null) { return(context, false); } await this.InterceptAsync(new DbCommandArgs(context, action.Command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); await this.InterceptAsync(new ScopeSavedArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), DbScopeType.Client, clientScopeInfo, connection, transaction), progress, cancellationToken).ConfigureAwait(false); action.Command.Dispose(); return(context, true); }
public ClientScopeInfoLoadedArgs(SyncContext context, string scopeName, ClientScopeInfo clientScopeInfo, DbConnection connection = null, DbTransaction transaction = null) : base(context, connection, transaction) { this.ScopeName = scopeName; this.ClientScopeInfo = clientScopeInfo; }
InternalApplyThenGetChangesAsync(ClientScopeInfo clientScope, SyncContext context, BatchInfo clientBatchInfo, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { try { if (Provider == null) { throw new MissingProviderException(nameof(InternalApplyThenGetChangesAsync)); } long remoteClientTimestamp = 0L; DatabaseChangesSelected serverChangesSelected = null; DatabaseChangesApplied clientChangesApplied = null; BatchInfo serverBatchInfo = null; IScopeInfo serverClientScopeInfo = null; // Create two transactions // First one to commit changes // Second one to get changes now that everything is commited await using (var runner = await this.GetConnectionAsync(context, SyncMode.Writing, SyncStage.ChangesApplying, connection, transaction, cancellationToken, progress).ConfigureAwait(false)) { //Direction set to Upload context.SyncWay = SyncWay.Upload; // Getting server scope assumes we have already created the schema on server // Scope name is the scope name coming from client // Since server can have multiples scopes (context, serverClientScopeInfo) = await this.InternalLoadServerScopeInfoAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // Should we ? if (serverClientScopeInfo == null || serverClientScopeInfo.Schema == null) { throw new MissingRemoteOrchestratorSchemaException(); } // Deserialiaze schema var schema = serverClientScopeInfo.Schema; // Create message containing everything we need to apply on server side var applyChanges = new MessageApplyChanges(Guid.Empty, clientScope.Id, false, clientScope.LastServerSyncTimestamp, schema, this.Options.ConflictResolutionPolicy, this.Options.DisableConstraintsOnApplyChanges, this.Options.CleanMetadatas, this.Options.CleanFolder, false, clientBatchInfo); // Call provider to apply changes (context, clientChangesApplied) = await this.InternalApplyChangesAsync(serverClientScopeInfo, context, applyChanges, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); await this.InterceptAsync(new TransactionCommitArgs(context, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); // commit first transaction await runner.CommitAsync().ConfigureAwait(false); } await using (var runner = await this.GetConnectionAsync(context, SyncMode.Reading, SyncStage.ChangesSelecting, connection, transaction, cancellationToken, progress).ConfigureAwait(false)) { context.ProgressPercentage = 0.55; //Direction set to Download context.SyncWay = SyncWay.Download; // JUST Before get changes, get the timestamp, to be sure to // get rows inserted / updated elsewhere since the sync is not over (context, remoteClientTimestamp) = await this.InternalGetLocalTimestampAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress); // Get if we need to get all rows from the datasource var fromScratch = clientScope.IsNewScope || context.SyncType == SyncType.Reinitialize || context.SyncType == SyncType.ReinitializeWithUpload; // When we get the chnages from server, we create the batches if it's requested by the client // the batch decision comes from batchsize from client (context, serverBatchInfo, serverChangesSelected) = await this.InternalGetChangesAsync(serverClientScopeInfo, context, fromScratch, clientScope.LastServerSyncTimestamp, remoteClientTimestamp, clientScope.Id, this.Provider.SupportsMultipleActiveResultSets, this.Options.BatchDirectory, null, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (runner.CancellationToken.IsCancellationRequested) { runner.CancellationToken.ThrowIfCancellationRequested(); } // generate the new scope item this.CompleteTime = DateTime.UtcNow; var scopeHistory = new ServerHistoryScopeInfo { Id = clientScope.Id, Name = clientScope.Name, LastSyncTimestamp = remoteClientTimestamp, LastSync = this.CompleteTime, LastSyncDuration = this.CompleteTime.Value.Subtract(context.StartTime).Ticks, }; // Write scopes locally await this.InternalSaveServerHistoryScopeAsync(scopeHistory, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // Commit second transaction for getting changes await this.InterceptAsync(new TransactionCommitArgs(context, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); } var serverSyncChanges = new ServerSyncChanges(remoteClientTimestamp, serverBatchInfo, serverChangesSelected); return(context, serverSyncChanges, clientChangesApplied, this.Options.ConflictResolutionPolicy); } catch (Exception ex) { throw GetSyncError(context, ex); } }
/// <summary> /// Check /// </summary> public virtual async Task <(SyncContext, bool, ClientScopeInfo, ServerScopeInfo)> IsConflictingSetupAsync(SyncContext context, SyncSetup inputSetup, ClientScopeInfo clientScopeInfo, ServerScopeInfo serverScopeInfo, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { if (clientScopeInfo.IsNewScope || clientScopeInfo.Schema == null) { return(context, false, clientScopeInfo, serverScopeInfo); } if (inputSetup != null && clientScopeInfo.Setup != null && !clientScopeInfo.Setup.EqualsByProperties(inputSetup)) { var conflictingSetupArgs = new ConflictingSetupArgs(context, inputSetup, clientScopeInfo, serverScopeInfo); await this.InterceptAsync(conflictingSetupArgs, progress, cancellationToken).ConfigureAwait(false); if (conflictingSetupArgs.Action == ConflictingSetupAction.Rollback) { throw new Exception("Seems you are trying another Setup tables that what is stored in your client scope database. Please create a new scope or deprovision and provision again your client scope."); } if (conflictingSetupArgs.Action == ConflictingSetupAction.Abort) { return(context, true, clientScopeInfo, serverScopeInfo); } // re affect scope infos clientScopeInfo = conflictingSetupArgs.ClientScopeInfo; serverScopeInfo = conflictingSetupArgs.ServerScopeInfo; } if (clientScopeInfo.Setup != null && serverScopeInfo.Setup != null && !clientScopeInfo.Setup.EqualsByProperties(serverScopeInfo.Setup)) { var conflictingSetupArgs = new ConflictingSetupArgs(context, inputSetup, clientScopeInfo, serverScopeInfo); await this.InterceptAsync(conflictingSetupArgs, progress, cancellationToken).ConfigureAwait(false); if (conflictingSetupArgs.Action == ConflictingSetupAction.Rollback) { throw new Exception("Seems your client setup is different from your server setup. Please create a new scope or deprovision and provision again your client scope with the server scope."); } if (conflictingSetupArgs.Action == ConflictingSetupAction.Abort) { return(context, true, clientScopeInfo, serverScopeInfo); } // re affect scope infos clientScopeInfo = conflictingSetupArgs.ClientScopeInfo; serverScopeInfo = conflictingSetupArgs.ServerScopeInfo; } // We gave 2 chances to user to edit the setup and fill correct values. // Final check, but if not valid, raise an error if (clientScopeInfo.Setup != null && serverScopeInfo.Setup != null && !clientScopeInfo.Setup.EqualsByProperties(serverScopeInfo.Setup)) { throw new Exception("Seems your client setup is different from your server setup. Please create a new scope or deprovision and provision again your client scope with the server scope."); } return(context, false, clientScopeInfo, serverScopeInfo); }
public ConflictingSetupArgs(SyncContext context, SyncSetup setup, ClientScopeInfo clientScopeInfo, ServerScopeInfo serverScopeInfo, DbConnection connection = null, DbTransaction transaction = null) : base(context, connection, transaction) { this.Setup = setup; this.ClientScopeInfo = clientScopeInfo; this.ServerScopeInfo = serverScopeInfo; }
InternalApplyChangesAsync(ClientScopeInfo clientScopeInfo, SyncContext context, BatchInfo serverBatchInfo, long clientTimestamp, long remoteClientTimestamp, ConflictResolutionPolicy policy, bool snapshotApplied, DatabaseChangesSelected allChangesSelected, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { try { // lastSyncTS : apply lines only if they are not modified since last client sync var lastTimestamp = clientScopeInfo.LastSyncTimestamp; // isNew : if IsNew, don't apply deleted rows from server var isNew = clientScopeInfo.IsNewScope; // We are in downloading mode // Create the message containing everything needed to apply changes var applyChanges = new MessageApplyChanges(clientScopeInfo.Id, Guid.Empty, isNew, lastTimestamp, clientScopeInfo.Schema, policy, this.Options.DisableConstraintsOnApplyChanges, this.Options.CleanMetadatas, this.Options.CleanFolder, snapshotApplied, serverBatchInfo); DatabaseChangesApplied clientChangesApplied; await using var runner = await this.GetConnectionAsync(context, SyncMode.Writing, SyncStage.ChangesApplying, connection, transaction, cancellationToken, progress).ConfigureAwait(false); context.SyncWay = SyncWay.Download; // Call apply changes on provider (context, clientChangesApplied) = await this.InternalApplyChangesAsync(clientScopeInfo, context, applyChanges, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } // check if we need to delete metadatas if (this.Options.CleanMetadatas && clientChangesApplied.TotalAppliedChanges > 0 && lastTimestamp.HasValue) { List <ClientScopeInfo> allScopes; (context, allScopes) = await this.InternalLoadAllClientScopesInfoAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (allScopes.Count > 0) { // Get the min value from LastSyncTimestamp from all scopes var minLastTimeStamp = allScopes.Min(scope => scope.LastSyncTimestamp.HasValue ? scope.LastSyncTimestamp.Value : Int64.MaxValue); minLastTimeStamp = minLastTimeStamp > lastTimestamp.Value ? lastTimestamp.Value : minLastTimeStamp; (context, _) = await this.InternalDeleteMetadatasAsync(allScopes, context, minLastTimeStamp, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); } } // now the sync is complete, remember the time this.CompleteTime = DateTime.UtcNow; // generate the new scope item clientScopeInfo.IsNewScope = false; clientScopeInfo.LastSync = this.CompleteTime; clientScopeInfo.LastSyncTimestamp = clientTimestamp; clientScopeInfo.LastServerSyncTimestamp = remoteClientTimestamp; clientScopeInfo.LastSyncDuration = this.CompleteTime.Value.Subtract(context.StartTime).Ticks; // Write scopes locally (context, clientScopeInfo) = await this.InternalSaveClientScopeInfoAsync(clientScopeInfo, context, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); return(context, clientChangesApplied, clientScopeInfo); } catch (Exception ex) { throw GetSyncError(context, ex); } }