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); } }
/// <summary> /// Create a snapshot, based on the Setup object. /// </summary> /// <param name="syncParameters">if not parameters are found in the SyncContext instance, will use thes sync parameters instead</param> /// <returns>Instance containing all information regarding the snapshot</returns> public virtual Task <BatchInfo> CreateSnapshotAsync(SyncParameters syncParameters = null, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) => RunInTransactionAsync(SyncStage.SnapshotCreating, async(ctx, connection, transaction) => { if (string.IsNullOrEmpty(this.Options.SnapshotsDirectory) || this.Options.BatchSize <= 0) { throw new SnapshotMissingMandatariesOptionsException(); } // check parameters // If context has no parameters specified, and user specifies a parameter collection we switch them if ((ctx.Parameters == null || ctx.Parameters.Count <= 0) && syncParameters != null && syncParameters.Count > 0) { ctx.Parameters = syncParameters; } // 1) Get Schema from remote provider var schema = await this.InternalGetSchemaAsync(ctx, this.Setup, connection, transaction, cancellationToken, progress).ConfigureAwait(false); // 2) Ensure databases are ready var provision = SyncProvision.TrackingTable | SyncProvision.StoredProcedures | SyncProvision.Triggers; // 3) Provision everything var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); var exists = await this.InternalExistsScopeInfoTableAsync(ctx, DbScopeType.Server, scopeBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); if (!exists) { await this.InternalCreateScopeInfoTableAsync(ctx, DbScopeType.Server, scopeBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); } var serverScopeInfo = await this.InternalGetScopeAsync <ServerScopeInfo>(ctx, DbScopeType.Server, this.ScopeName, scopeBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); schema = await InternalProvisionAsync(ctx, false, schema, this.Setup, provision, serverScopeInfo, connection, transaction, cancellationToken, progress).ConfigureAwait(false); // 4) Getting the most accurate timestamp var remoteClientTimestamp = await this.InternalGetLocalTimestampAsync(ctx, connection, transaction, cancellationToken, progress); await this.InterceptAsync(new SnapshotCreatingArgs(ctx, schema, this.Options.SnapshotsDirectory, this.Options.BatchSize, remoteClientTimestamp, connection, transaction), cancellationToken).ConfigureAwait(false); // 5) Create the snapshot var batchInfo = await this.InternalCreateSnapshotAsync(ctx, schema, this.Setup, connection, transaction, remoteClientTimestamp, cancellationToken, progress).ConfigureAwait(false); var snapshotCreated = new SnapshotCreatedArgs(ctx, batchInfo, connection); await this.InterceptAsync(snapshotCreated, cancellationToken).ConfigureAwait(false); this.ReportProgress(ctx, progress, snapshotCreated); return(batchInfo); }, connection, transaction, cancellationToken);
GetChangesAsync(string scopeName = SyncOptions.DefaultScopeName, SyncParameters parameters = default, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { var context = new SyncContext(Guid.NewGuid(), scopeName); 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); bool exists; (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.Client, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); if (!exists) { return(default);
/// <summary> /// Create a snapshot, based on the Setup object. /// </summary> /// <param name="syncParameters">if not parameters are found in the SyncContext instance, will use thes sync parameters instead</param> /// <returns>Instance containing all information regarding the snapshot</returns> public virtual async Task <BatchInfo> CreateSnapshotAsync(string scopeName, SyncSetup setup = null, SyncParameters syncParameters = null, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { var context = new SyncContext(Guid.NewGuid(), scopeName); try { await using var runner = await this.GetConnectionAsync(context, SyncMode.Writing, SyncStage.SnapshotCreating, connection, transaction, cancellationToken, progress).ConfigureAwait(false); if (string.IsNullOrEmpty(this.Options.SnapshotsDirectory) || this.Options.BatchSize <= 0) { throw new SnapshotMissingMandatariesOptionsException(); } // check parameters // If context has no parameters specified, and user specifies a parameter collection we switch them if ((context.Parameters == null || context.Parameters.Count <= 0) && syncParameters != null && syncParameters.Count > 0) { context.Parameters = syncParameters; } // 1) Get Schema from remote provider ServerScopeInfo serverScopeInfo; (context, serverScopeInfo) = await this.InternalGetServerScopeInfoAsync(context, setup, false, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); // If we just have create the server scope, we need to provision it if (serverScopeInfo != null && serverScopeInfo.IsNewScope) { // 2) Provision var provision = SyncProvision.TrackingTable | SyncProvision.StoredProcedures | SyncProvision.Triggers; (context, _) = await this.InternalProvisionAsync(serverScopeInfo, context, false, provision, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); // Write scopes locally (context, serverScopeInfo) = await this.InternalSaveServerScopeInfoAsync(serverScopeInfo, context, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); } // 4) Getting the most accurate timestamp long remoteClientTimestamp; (context, remoteClientTimestamp) = await this.InternalGetLocalTimestampAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // 5) Create the snapshot with BatchInfo batchInfo; (context, batchInfo) = await this.InternalCreateSnapshotAsync(serverScopeInfo, context, remoteClientTimestamp, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); return(batchInfo); } catch (Exception ex) { throw GetSyncError(context, ex); } }
public virtual Task <BatchInfo> CreateSnapshotAsync(SyncSetup setup = null, SyncParameters syncParameters = null, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) => CreateSnapshotAsync(SyncOptions.DefaultScopeName, setup, syncParameters, connection, transaction, cancellationToken, progress);
/// <summary> /// Launch a synchronization with the specified mode /// </summary> public async Task <SyncResult> SynchronizeAsync(string scopeName, SyncSetup setup, SyncType syncType, SyncParameters parameters, CancellationToken cancellationToken, IProgress <ProgressArgs> progress = null) { // checkpoints dates var startTime = DateTime.UtcNow; var completeTime = DateTime.UtcNow; // Create a logger var logger = this.Options.Logger ?? new SyncLogger().AddDebug(); // Lock sync to prevent multi call to sync at the same time LockSync(); // Context, used to back and forth data between servers var context = new SyncContext(Guid.NewGuid(), scopeName) { // if any parameters, set in context Parameters = parameters, // set sync type (Normal, Reinitialize, ReinitializeWithUpload) SyncType = syncType }; // Result, with sync results stats. var result = new SyncResult(context.SessionId) { // set start time StartTime = startTime, CompleteTime = completeTime, }; this.SessionState = SyncSessionState.Synchronizing; this.SessionStateChanged?.Invoke(this, this.SessionState); //await Task.Run(async () => //{ try { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } if (setup != null) { var remoteOrchestratorType = this.RemoteOrchestrator.GetType(); var providerType = remoteOrchestratorType.Name; if (providerType.ToLowerInvariant() == "webclientorchestrator" || providerType.ToLowerInvariant() == "webremotetorchestrator") { throw new Exception("Do not set Tables (or SyncSetup) from your client. Please use SyncAgent, without any Tables or SyncSetup. The tables will come from the server side"); } } // Begin session context = await this.LocalOrchestrator.InternalBeginSessionAsync(context, cancellationToken, progress).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } // no need to check on every call to SynchronizeAsync if (!checkUpgradeDone) { var needToUpgrade = await this.LocalOrchestrator.NeedsToUpgradeAsync(default, default, cancellationToken, progress).ConfigureAwait(false);
/// <summary> /// Create a snapshot, based on the Setup object. /// </summary> /// <param name="syncParameters">if not parameters are found in the SyncContext instance, will use thes sync parameters instead</param> /// <returns>Instance containing all information regarding the snapshot</returns> public virtual Task <BatchInfo> CreateSnapshotAsync(SyncParameters syncParameters = null, ISerializerFactory serializerFactory = default, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) => RunInTransactionAsync(SyncStage.SnapshotCreating, async(ctx, connection, transaction) =>
/// <summary> /// Launch a Synchronization based on a named scope /// </summary> /// <param name="scopeName">Named scope</param> /// <param name="parameters">Parameters values for each of your setup filters</param> /// <param name="progress">IProgress instance to get a progression status during sync</param> /// <returns>Computed sync results</returns> public Task <SyncResult> SynchronizeAsync(string scopeName, SyncParameters parameters, IProgress <ProgressArgs> progress = null) => SynchronizeAsync(scopeName, null, SyncType.Normal, parameters, CancellationToken.None, progress);
/// <summary> /// Launch a Synchronization based on scope DefaultScope /// </summary> /// <param name="syncType">Synchronization mode: Normal, Reinitialize or ReinitializeWithUpload</param> /// <param name="parameters">Parameters values for each of your setup filters</param> /// <param name="progress">IProgress instance to get a progression status during sync</param> /// <returns>Computed sync results</returns> public Task <SyncResult> SynchronizeAsync(SyncType syncType, SyncParameters parameters, IProgress <ProgressArgs> progress = null) => SynchronizeAsync(SyncOptions.DefaultScopeName, null, syncType, parameters, CancellationToken.None, progress);
/// <summary> /// Launch a Synchronization based on a named scope /// </summary> /// <param name="scopeName">Named scope</param> /// <param name="setup">Setup instance containing the table list and optionnally columns</param> /// <param name="syncType">Synchronization mode: Normal, Reinitialize or ReinitializeWithUpload</param> /// <param name="parameters">Parameters values for each of your setup filters</param> /// <param name="progress">IProgress instance to get a progression status during sync</param> /// <returns>Computed sync results</returns> public Task <SyncResult> SynchronizeAsync(string scopeName, SyncSetup setup, SyncType syncType, SyncParameters parameters, IProgress <ProgressArgs> progress = default) => SynchronizeAsync(scopeName, setup, syncType, parameters, CancellationToken.None, progress);
/// <summary> /// Launch a Synchronization based on scope DefaultScope /// </summary> /// <param name="setup">Setup instance containing the table list and optionnally columns</param> /// <param name="parameters">Parameters values for each of your setup filters</param> /// <param name="progress">IProgress instance to get a progression status during sync</param> /// <returns>Computed sync results</returns> public Task <SyncResult> SynchronizeAsync(SyncSetup setup, SyncParameters parameters, IProgress <ProgressArgs> progress = default) => SynchronizeAsync(SyncOptions.DefaultScopeName, setup, SyncType.Normal, parameters, CancellationToken.None, progress);
/// <summary> /// Launch a Synchronization based on a named scope /// </summary> /// <param name="scopeName">Named scope</param> /// <param name="tables">Tables list to synchronize</param> /// <param name="parameters">Parameters values for each of your setup filters</param> /// <param name="progress">IProgress instance to get a progression status during sync</param> /// <returns>Computed sync results</returns> public Task <SyncResult> SynchronizeAsync(string scopeName, string[] tables, SyncParameters parameters, IProgress <ProgressArgs> progress = default) => SynchronizeAsync(scopeName, new SyncSetup(tables), SyncType.Normal, parameters, CancellationToken.None, progress);