SaveServerHistoryScopeInfoAsync(ServerHistoryScopeInfo serverHistoryScopeInfo, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { var context = new SyncContext(Guid.NewGuid(), serverHistoryScopeInfo.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.ServerHistory, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); if (!exists) { await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ServerHistory, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); } // Write scopes locally (context, serverHistoryScopeInfo) = await this.InternalSaveServerHistoryScopeAsync(serverHistoryScopeInfo, context, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); return(serverHistoryScopeInfo); } catch (Exception ex) { throw GetSyncError(context, ex); } }
InternalSaveServerHistoryScopeAsync(ServerHistoryScopeInfo serverHistoryScopeInfo, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); bool scopeExists; (context, scopeExists) = await InternalExistsServerHistoryScopeInfoAsync(serverHistoryScopeInfo.Id.ToString(), serverHistoryScopeInfo.Name, context, connection, transaction, cancellationToken, progress).ConfigureAwait(false); DbCommand command; if (scopeExists) { command = scopeBuilder.GetCommandAsync(DbScopeCommandType.UpdateServerHistoryScopeInfo, connection, transaction); } else { command = scopeBuilder.GetCommandAsync(DbScopeCommandType.InsertServerHistoryScopeInfo, connection, transaction); } if (command == null) { return(context, null); } InternalSetSaveServerHistoryScopeParameters(serverHistoryScopeInfo, command); var action = new ScopeSavingArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), DbScopeType.ServerHistory, serverHistoryScopeInfo, command, connection, transaction); await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); if (action.Cancel || action.Command == null) { return(default);
private ServerHistoryScopeInfo InternalReadServerHistoryScopeInfo(DbDataReader reader) { var serverScopeInfo = new ServerHistoryScopeInfo { Id = reader.GetGuid(reader.GetOrdinal("sync_scope_id")), Name = reader["sync_scope_name"] as string, LastSync = reader["scope_last_sync"] != DBNull.Value ? (DateTime?)reader.GetDateTime(reader.GetOrdinal("scope_last_sync")) : null, LastSyncDuration = reader["scope_last_sync_duration"] != DBNull.Value ? reader.GetInt64(reader.GetOrdinal("scope_last_sync_duration")) : 0L, LastSyncTimestamp = reader["scope_last_sync_timestamp"] != DBNull.Value ? reader.GetInt64(reader.GetOrdinal("scope_last_sync_timestamp")) : 0L }; return(serverScopeInfo); }
/// <summary> /// Update or Insert a server scope row /// </summary> public virtual Task <ServerHistoryScopeInfo> SaveServerHistoryScopeAsync(ServerHistoryScopeInfo scopeInfo, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) => RunInTransactionAsync(SyncStage.ScopeWriting, async(ctx, connection, transaction) => { var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); var exists = await this.InternalExistsScopeInfoTableAsync(ctx, DbScopeType.ServerHistory, scopeBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); if (!exists) { await this.InternalCreateScopeInfoTableAsync(ctx, DbScopeType.ServerHistory, scopeBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); } // Write scopes locally var scopeInfoUpdated = await this.InternalSaveScopeAsync(ctx, DbScopeType.ServerHistory, scopeInfo, scopeBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); return(scopeInfoUpdated); }, connection, transaction, cancellationToken);
InternalLoadServerHistoryScopeAsync(string scopeId, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetServerHistoryScopeInfo, connection, transaction); if (command == null) { return(context, null); } DbSyncAdapter.SetParameterValue(command, "sync_scope_id", scopeId); DbSyncAdapter.SetParameterValue(command, "sync_scope_name", context.ScopeName); await this.InterceptAsync(new DbCommandArgs(context, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); using DbDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(false); ServerHistoryScopeInfo serverHistoryScopeInfo = null; if (reader.Read()) { serverHistoryScopeInfo = InternalReadServerHistoryScopeInfo(reader); } reader.Close(); if (serverHistoryScopeInfo.Schema != null) { serverHistoryScopeInfo.Schema.EnsureSchema(); } command.Dispose(); return(context, serverHistoryScopeInfo); }
/// <summary> /// Write scope history in the remote data source /// </summary> public virtual async Task <SyncContext> WriteServerHistoryScopeAsync(SyncContext context, string scopeInfoTableName, ServerHistoryScopeInfo scope, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress <ProgressArgs> progress = null) { var scopeBuilder = this.GetScopeBuilder(); var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(scopeInfoTableName); this.Orchestrator.logger.LogDebug(SyncEventsId.WriteServerScopeHistory, scope); await scopeInfoBuilder.InsertOrUpdateServerHistoryScopeInfoAsync(scope, connection, transaction).ConfigureAwait(false); return(context); }
ApplyThenGetChangesAsync(ScopeInfo clientScope, BatchInfo clientBatchInfo, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { if (!this.StartTime.HasValue) { this.StartTime = DateTime.UtcNow; } // Get context or create a new one var ctx = this.GetContext(); long remoteClientTimestamp = 0L; DatabaseChangesSelected serverChangesSelected = null; DatabaseChangesApplied clientChangesApplied = null; BatchInfo serverBatchInfo = null; SyncSet schema = null; using var connection = this.Provider.CreateConnection(); try { ctx.SyncStage = SyncStage.ChangesApplying; //Direction set to Upload ctx.SyncWay = SyncWay.Upload; // Open connection await this.OpenConnectionAsync(connection, cancellationToken).ConfigureAwait(false); DbTransaction transaction; // Create two transactions // First one to commit changes // Second one to get changes now that everything is commited using (transaction = connection.BeginTransaction()) { await this.InterceptAsync(new TransactionOpenedArgs(ctx, connection, transaction), cancellationToken).ConfigureAwait(false); // Maybe here, get the schema from server, issue from client scope name // Maybe then compare the schema version from client scope with schema version issued from server // Maybe if different, raise an error ? // Get scope if exists // Getting server scope assumes we have already created the schema on server var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); var serverScopeInfo = await this.InternalGetScopeAsync <ServerScopeInfo>(ctx, DbScopeType.Server, clientScope.Name, scopeBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); // Should we ? if (serverScopeInfo.Schema == null) { throw new MissingRemoteOrchestratorSchemaException(); } // Deserialiaze schema schema = serverScopeInfo.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.Setup, this.Options.ConflictResolutionPolicy, this.Options.DisableConstraintsOnApplyChanges, this.Options.UseBulkOperations, this.Options.CleanMetadatas, this.Options.CleanFolder, false, clientBatchInfo, this.Options.SerializerFactory); // Call provider to apply changes (ctx, clientChangesApplied) = await this.InternalApplyChangesAsync(ctx, applyChanges, connection, transaction, cancellationToken, progress).ConfigureAwait(false); await this.InterceptAsync(new TransactionCommitArgs(ctx, connection, transaction), cancellationToken).ConfigureAwait(false); // commit first transaction transaction.Commit(); } ctx.SyncStage = SyncStage.ChangesSelecting; ctx.ProgressPercentage = 0.55; using (transaction = connection.BeginTransaction()) { await this.InterceptAsync(new TransactionOpenedArgs(ctx, connection, transaction), cancellationToken).ConfigureAwait(false); //Direction set to Download ctx.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 remoteClientTimestamp = await this.InternalGetLocalTimestampAsync(ctx, connection, transaction, cancellationToken, progress); // Get if we need to get all rows from the datasource var fromScratch = clientScope.IsNewScope || ctx.SyncType == SyncType.Reinitialize || ctx.SyncType == SyncType.ReinitializeWithUpload; var message = new MessageGetChangesBatch(clientScope.Id, Guid.Empty, fromScratch, clientScope.LastServerSyncTimestamp, schema, this.Setup, this.Options.BatchSize, this.Options.BatchDirectory, this.Options.SerializerFactory); // Call interceptor await this.InterceptAsync(new DatabaseChangesSelectingArgs(ctx, message, connection, transaction), cancellationToken).ConfigureAwait(false); // 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 (ctx, serverBatchInfo, serverChangesSelected) = await this.InternalGetChangesAsync(ctx, message, connection, transaction, cancellationToken, progress).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { 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(this.StartTime.Value).Ticks, }; // Write scopes locally var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); await this.InternalSaveScopeAsync(ctx, DbScopeType.ServerHistory, scopeHistory, scopeBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); // Commit second transaction for getting changes await this.InterceptAsync(new TransactionCommitArgs(ctx, connection, transaction), cancellationToken).ConfigureAwait(false); transaction.Commit(); } // Event progress & interceptor await this.CloseConnectionAsync(connection, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { RaiseError(ex); } finally { await this.CloseConnectionAsync(connection, cancellationToken).ConfigureAwait(false); } return(remoteClientTimestamp, serverBatchInfo, this.Options.ConflictResolutionPolicy, clientChangesApplied, serverChangesSelected); }
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); } }
public ServerHistoryScopeLoadedArgs(SyncContext context, ServerHistoryScopeInfo scope, DbConnection connection = null, DbTransaction transaction = null) : base(context, connection, transaction) { this.ScopeInfo = scope; }