From 3748fd42e66c9f8ce9617e785d65266fe2bda0d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Pertus?= Date: Mon, 28 Nov 2022 12:44:26 +0100 Subject: [PATCH] Trying to improve errors handling --- .../Args/ApplyChangesConflictOccuredArgs.cs | 2 +- .../Args/TableChangesApplyArgs.cs | 1 - .../BaseOrchestrator.ApplyChanges.cs | 14 +- .../LocalOrchestrator.ApplyChanges.cs | 7 +- .../RemoteOrchestrator.ApplyChanges.cs | 9 +- .../BaseOrchestrator.Conflicts.cs | 181 ++-- .../BaseOrchestrator.Constraints.cs | 134 ++- .../Orchestrators/BaseOrchestrator.Errors.cs | 4 +- .../Orchestrators/BaseOrchestrator.Schema.cs | 495 ++++++----- .../BaseOrchestrator.Timestamp.cs | 47 +- .../Orchestrators/BaseOrchestrator.cs | 44 +- .../BaseOrchestrator.StoredProcedures.cs | 316 ++++--- .../Builders/BaseOrchestrator.Table.cs | 413 ++++++--- .../BaseOrchestrator.TrackingTable.cs | 261 ++++-- .../Builders/BaseOrchestrator.Triggers.cs | 263 ++++-- .../Orchestrators/DbConnectionRunner.cs | 41 +- .../GetChanges/BaseOrchestrator.GetChanges.cs | 837 ++++++++++-------- .../LocalOrchestrator.GetChanges.cs | 2 - .../RemoteOrchestrator.GetChanges.cs | 1 - .../LocalOrchestrator.Untracked.cs | 2 +- .../Metadatas/BaseOrchestrator.Metadatas.cs | 120 +-- .../Metadatas/LocalOrchestrator.Metadatas.cs | 90 +- .../Metadatas/RemoteOrchestrator.Metadatas.cs | 64 +- .../Provision/BaseOrchestrator.Provision.cs | 300 ++++--- .../Provision/LocalOrchestrator.Provision.cs | 27 +- .../Provision/RemoteOrchestrator.Provision.cs | 40 +- .../BaseOrchestrator.ScopeInfoClients.cs | 166 ++-- .../Scopes/BaseOrchestrator.ScopeInfos.cs | 256 +++--- .../Scopes/BaseOrchestrator.ScopesTables.cs | 143 +-- .../Scopes/LocalOrchestrator.ScopeInfos.cs | 131 +-- .../Scopes/RemoteOrchestrator.ScopeInfos.cs | 177 ++-- Projects/Dotmim.Sync.Core/SyncAgent.cs | 40 +- Projects/Dotmim.Sync.Core/SyncException.cs | 82 +- Samples/Dotmim.Sync.SampleConsole/Program.cs | 65 +- docs/Interceptors.rst | 55 ++ docs/Orchestrators.rst | 40 +- 36 files changed, 2917 insertions(+), 1953 deletions(-) diff --git a/Projects/Dotmim.Sync.Core/Args/ApplyChangesConflictOccuredArgs.cs b/Projects/Dotmim.Sync.Core/Args/ApplyChangesConflictOccuredArgs.cs index d29d0aba7..cc90d8b5b 100644 --- a/Projects/Dotmim.Sync.Core/Args/ApplyChangesConflictOccuredArgs.cs +++ b/Projects/Dotmim.Sync.Core/Args/ApplyChangesConflictOccuredArgs.cs @@ -50,7 +50,7 @@ public class ApplyChangesConflictOccuredArgs : ProgressArgs public async Task GetSyncConflictAsync() { var (_, localRow) = await orchestrator.InternalGetConflictRowAsync(scopeInfo, Context, schemaChangesTable, conflictRow, this.Connection, this.Transaction).ConfigureAwait(false); - this.conflict = orchestrator.InternalGetConflict(conflictRow, localRow); + this.conflict = orchestrator.InternalGetConflict(Context, conflictRow, localRow); return conflict; } diff --git a/Projects/Dotmim.Sync.Core/Args/TableChangesApplyArgs.cs b/Projects/Dotmim.Sync.Core/Args/TableChangesApplyArgs.cs index 36dfbb0d1..815dff618 100644 --- a/Projects/Dotmim.Sync.Core/Args/TableChangesApplyArgs.cs +++ b/Projects/Dotmim.Sync.Core/Args/TableChangesApplyArgs.cs @@ -88,7 +88,6 @@ public static partial class InterceptorsExtensions /// { /// Console.WriteLine($"Changes for table /// {args.SchemaTable.GetFullName()}. Rows:{syncTable.Rows.Count}"); - /// foreach (var row in syncTable.Rows) /// Console.WriteLine(row); /// } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/BaseOrchestrator.ApplyChanges.cs b/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/BaseOrchestrator.ApplyChanges.cs index 17bd6473d..47e2c953b 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/BaseOrchestrator.ApplyChanges.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/BaseOrchestrator.ApplyChanges.cs @@ -28,15 +28,14 @@ internal virtual async Task InternalApplyCleanErrorsAsync(ScopeInfo scopeInfo, S { if (lastSyncErrorsBatchInfo == null) return; + try + { + context.SyncStage = SyncStage.ChangesApplying; - context.SyncStage = SyncStage.ChangesApplying; - - var schemaTables = message.Schema.Tables.SortByDependencies(tab => tab.GetRelations().Select(r => r.GetParentTable())); + var schemaTables = message.Schema.Tables.SortByDependencies(tab => tab.GetRelations().Select(r => r.GetParentTable())); - this.Logger.LogInformation($@"[InternalApplyCleanErrorsAsync]. Directory name {{directoryName}}. BatchParts count {{BatchPartsInfoCount}}", lastSyncErrorsBatchInfo.DirectoryName, lastSyncErrorsBatchInfo.BatchPartsInfo.Count); + this.Logger.LogInformation($@"[InternalApplyCleanErrorsAsync]. Directory name {{directoryName}}. BatchParts count {{BatchPartsInfoCount}}", lastSyncErrorsBatchInfo.DirectoryName, lastSyncErrorsBatchInfo.BatchPartsInfo.Count); - try - { foreach (var schemaTable in schemaTables) { var tableChangesApplied = message.ChangesApplied?.TableChangesApplied?.FirstOrDefault(tca => @@ -50,7 +49,6 @@ internal virtual async Task InternalApplyCleanErrorsAsync(ScopeInfo scopeInfo, S sn.Equals(otherSn, sc); }); - // tmp sync table with only writable columns var changesSet = schemaTable.Schema.Clone(false); var schemaChangesTable = CreateChangesTable(schemaTable, changesSet); @@ -159,7 +157,6 @@ internal virtual async Task if (!string.IsNullOrEmpty(message.BatchDirectory) && !Directory.Exists(message.BatchDirectory)) Directory.CreateDirectory(message.BatchDirectory); - // Disable check constraints // Because Sqlite does not support "PRAGMA foreign_keys=OFF" Inside a transaction // Report this disabling constraints brefore opening a transaction @@ -357,7 +354,6 @@ internal virtual async Task InternalApplyTableChangesAsync(ScopeInfo try { - foreach (var batchPartInfo in bpiTables) { var batchChangesApplyingArgs = new BatchChangesApplyingArgs(context, message.Changes, batchPartInfo, schemaTable, applyType, command, connection, transaction); diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/LocalOrchestrator.ApplyChanges.cs b/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/LocalOrchestrator.ApplyChanges.cs index bc0c1f4e4..338a8b9e6 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/LocalOrchestrator.ApplyChanges.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/LocalOrchestrator.ApplyChanges.cs @@ -14,6 +14,8 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -29,7 +31,8 @@ public partial class LocalOrchestrator : BaseOrchestrator internal virtual async Task<(SyncContext context, ClientSyncChanges clientSyncChange, ScopeInfoClient CScopeInfoClient)> InternalApplyChangesAsync(ScopeInfo cScopeInfo, ScopeInfoClient cScopeInfoClient, SyncContext context, ServerSyncChanges serverSyncChanges, ClientSyncChanges clientSyncChanges, ConflictResolutionPolicy policy, bool snapshotApplied, - DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress progress = null) + DbConnection connection = default, DbTransaction transaction = default, + CancellationToken cancellationToken = default, IProgress progress = null) { // Connection & Transaction runner DbConnectionRunner runner = null; @@ -103,7 +106,6 @@ public partial class LocalOrchestrator : BaseOrchestrator if (failureException != null) throw failureException; - if (serverBatchInfo != null && serverBatchInfo.HasData()) { // Call apply changes on provider @@ -111,7 +113,6 @@ public partial class LocalOrchestrator : BaseOrchestrator failureException = await this.InternalApplyChangesAsync(cScopeInfo, context, applyChanges, connection, transaction, cancellationToken, progress).ConfigureAwait(false); } - if (failureException != null) throw failureException; diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/RemoteOrchestrator.ApplyChanges.cs b/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/RemoteOrchestrator.ApplyChanges.cs index 956ac5606..aaebaca4a 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/RemoteOrchestrator.ApplyChanges.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/ApplyChanges/RemoteOrchestrator.ApplyChanges.cs @@ -172,14 +172,14 @@ public partial class RemoteOrchestrator : BaseOrchestrator await runner.CommitAsync().ConfigureAwait(false); } - catch (Exception) + catch (Exception ex) { if (runner != null) { await runner.RollbackAsync().ConfigureAwait(false); await runner.DisposeAsync().ConfigureAwait(false); } - throw; + throw GetSyncError(context, ex); } try @@ -263,11 +263,12 @@ public partial class RemoteOrchestrator : BaseOrchestrator return (context, serverSyncChanges, this.Options.ConflictResolutionPolicy); } - catch (Exception) + catch (Exception ex) { if (runner != null) await runner.RollbackAsync().ConfigureAwait(false); - throw; + + throw GetSyncError(context, ex); } finally { diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Conflicts.cs b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Conflicts.cs index f14e94ce6..17d645db9 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Conflicts.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Conflicts.cs @@ -56,7 +56,7 @@ await this.GetConflictResolutionAsync(scopeInfo, context, localScopeId, conflict case ConflictType.RemoteExistsLocalNotExists: case ConflictType.RemoteExistsLocalIsDeleted: case ConflictType.UniqueKeyConstraint: - (context, operationComplete, exception) = await this.InternalApplyUpdateAsync(scopeInfo, context, + (_, operationComplete, exception) = await this.InternalApplyUpdateAsync(scopeInfo, context, conflictRow, schemaChangesTable, lastTimestamp, nullableSenderScopeId, true, connection, transaction).ConfigureAwait(false); applied = operationComplete; @@ -65,7 +65,7 @@ await this.GetConflictResolutionAsync(scopeInfo, context, localScopeId, conflict // Conflict, but both have delete the row, so just update the metadata to the right winner case ConflictType.RemoteIsDeletedLocalIsDeleted: - (context, operationComplete, exception) = await this.InternalUpdateMetadatasAsync(scopeInfo, context, + (_, operationComplete, exception) = await this.InternalUpdateMetadatasAsync(scopeInfo, context, conflictRow, schemaChangesTable, nullableSenderScopeId, true, connection, transaction).ConfigureAwait(false); applied = false; @@ -87,7 +87,7 @@ await this.GetConflictResolutionAsync(scopeInfo, context, localScopeId, conflict // Conflict, but both have delete the row, so just update the metadata to the right winner if (!operationComplete && exception == null) { - (context, operationComplete, exception) = await this.InternalUpdateMetadatasAsync(scopeInfo, context, + (_, operationComplete, exception) = await this.InternalUpdateMetadatasAsync(scopeInfo, context, conflictRow, schemaChangesTable, nullableSenderScopeId, true, connection, transaction); applied = false; conflictResolved = operationComplete && exception == null; @@ -108,7 +108,7 @@ await this.GetConflictResolutionAsync(scopeInfo, context, localScopeId, conflict // We don't update metadatas so the row is updated locally and is marked as updated by the trigger // and will be returned back to client if occurs on server // and will be returned to the server on next sync if occurs on client - (context, operationComplete, exception) = await this.InternalApplyUpdateAsync(scopeInfo, context, + (_, operationComplete, exception) = await this.InternalApplyUpdateAsync(scopeInfo, context, finalRow, schemaChangesTable, lastTimestamp, null, true, connection, transaction).ConfigureAwait(false); if (!operationComplete && exception == null) @@ -177,44 +177,57 @@ await this.GetConflictResolutionAsync(scopeInfo, context, localScopeId, conflict /// /// We have a conflict, try to get the source row and generate a conflict /// - internal SyncConflict InternalGetConflict(SyncRow remoteConflictRow, SyncRow localConflictRow) + internal SyncConflict InternalGetConflict(SyncContext context, SyncRow remoteConflictRow, SyncRow localConflictRow) { + try + { + var dbConflictType = ConflictType.ErrorsOccurred; + + if (remoteConflictRow == null) + throw new UnknownException("Remote Conflict Row Should Exists."); + + // local row is null + if (localConflictRow == null && remoteConflictRow.RowState == SyncRowState.Modified) + dbConflictType = ConflictType.RemoteExistsLocalNotExists; + else if (localConflictRow == null && remoteConflictRow.RowState == SyncRowState.Deleted) + dbConflictType = ConflictType.RemoteIsDeletedLocalNotExists; + + //// remote row is null. Can't happen + //else if (remoteConflictRow == null && localConflictRow.RowState == SyncRowState.Modified) + // dbConflictType = ConflictType.RemoteNotExistsLocalExists; + //else if (remoteConflictRow == null && localConflictRow.RowState == SyncRowState.Deleted) + // dbConflictType = ConflictType.RemoteNotExistsLocalIsDeleted; + + else if (remoteConflictRow.RowState == SyncRowState.Deleted && localConflictRow.RowState == SyncRowState.Deleted) + dbConflictType = ConflictType.RemoteIsDeletedLocalIsDeleted; + else if (remoteConflictRow.RowState == SyncRowState.Modified && localConflictRow.RowState == SyncRowState.Deleted) + dbConflictType = ConflictType.RemoteExistsLocalIsDeleted; + else if (remoteConflictRow.RowState == SyncRowState.Deleted && localConflictRow.RowState == SyncRowState.Modified) + dbConflictType = ConflictType.RemoteIsDeletedLocalExists; + else if (remoteConflictRow.RowState == SyncRowState.Modified && localConflictRow.RowState == SyncRowState.Modified) + dbConflictType = ConflictType.RemoteExistsLocalExists; + + // Generate the conflict + var conflict = new SyncConflict(dbConflictType); + conflict.AddRemoteRow(remoteConflictRow); + + if (localConflictRow != null) + conflict.AddLocalRow(localConflictRow); + + return conflict; + } + catch (Exception ex) + { + string message = null; - var dbConflictType = ConflictType.ErrorsOccurred; - - if (remoteConflictRow == null) - throw new UnknownException("THAT can't happen..."); - - - // local row is null - if (localConflictRow == null && remoteConflictRow.RowState == SyncRowState.Modified) - dbConflictType = ConflictType.RemoteExistsLocalNotExists; - else if (localConflictRow == null && remoteConflictRow.RowState == SyncRowState.Deleted) - dbConflictType = ConflictType.RemoteIsDeletedLocalNotExists; - - //// remote row is null. Can't happen - //else if (remoteConflictRow == null && localConflictRow.RowState == SyncRowState.Modified) - // dbConflictType = ConflictType.RemoteNotExistsLocalExists; - //else if (remoteConflictRow == null && localConflictRow.RowState == SyncRowState.Deleted) - // dbConflictType = ConflictType.RemoteNotExistsLocalIsDeleted; - - else if (remoteConflictRow.RowState == SyncRowState.Deleted && localConflictRow.RowState == SyncRowState.Deleted) - dbConflictType = ConflictType.RemoteIsDeletedLocalIsDeleted; - else if (remoteConflictRow.RowState == SyncRowState.Modified && localConflictRow.RowState == SyncRowState.Deleted) - dbConflictType = ConflictType.RemoteExistsLocalIsDeleted; - else if (remoteConflictRow.RowState == SyncRowState.Deleted && localConflictRow.RowState == SyncRowState.Modified) - dbConflictType = ConflictType.RemoteIsDeletedLocalExists; - else if (remoteConflictRow.RowState == SyncRowState.Modified && localConflictRow.RowState == SyncRowState.Modified) - dbConflictType = ConflictType.RemoteExistsLocalExists; - - // Generate the conflict - var conflict = new SyncConflict(dbConflictType); - conflict.AddRemoteRow(remoteConflictRow); + if (localConflictRow != null) + message += $"Local Row:{localConflictRow}."; - if (localConflictRow != null) - conflict.AddLocalRow(localConflictRow); + if (remoteConflictRow != null) + message += $"Remote Row:{remoteConflictRow}."; - return conflict; + throw GetSyncError(context, ex, message); + } } /// @@ -223,62 +236,74 @@ internal SyncConflict InternalGetConflict(SyncRow remoteConflictRow, SyncRow loc internal async Task<(SyncContext context, SyncRow syncRow)> InternalGetConflictRowAsync(ScopeInfo scopeInfo, SyncContext context, SyncTable schemaTable, SyncRow primaryKeyRow, DbConnection connection, DbTransaction transaction) { + try + { + var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, schemaTable, scopeInfo.Setup); - var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, schemaTable, scopeInfo.Setup); + // Get the row in the local repository + var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.SelectRow, null, + connection, transaction, default, default).ConfigureAwait(false); - // Get the row in the local repository - var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.SelectRow, null, - connection, transaction, default, default).ConfigureAwait(false); + if (command == null) return (context, null); - if (command == null) return (context, null); + // set the primary keys columns as parameters + this.SetColumnParametersValues(command, primaryKeyRow); - // set the primary keys columns as parameters - this.SetColumnParametersValues(command, primaryKeyRow); + // Create a select table based on the schema in parameter + scope columns + var changesSet = schemaTable.Schema.Clone(false); + var selectTable = CreateChangesTable(schemaTable, changesSet); - // Create a select table based on the schema in parameter + scope columns - var changesSet = schemaTable.Schema.Clone(false); - var selectTable = CreateChangesTable(schemaTable, changesSet); + await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.SelectRow, connection, transaction)).ConfigureAwait(false); - await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.SelectRow, connection, transaction)).ConfigureAwait(false); + using var dataReader = await command.ExecuteReaderAsync().ConfigureAwait(false); - using var dataReader = await command.ExecuteReaderAsync().ConfigureAwait(false); + if (!dataReader.Read()) + { + dataReader.Close(); + command.Dispose(); + return (context, null); + } - if (!dataReader.Read()) - { - dataReader.Close(); - command.Dispose(); - return (context, null); - } + // Create a new empty row + var syncRow = selectTable.NewRow(); + for (var i = 0; i < dataReader.FieldCount; i++) + { + var columnName = dataReader.GetName(i); - // Create a new empty row - var syncRow = selectTable.NewRow(); - for (var i = 0; i < dataReader.FieldCount; i++) - { - var columnName = dataReader.GetName(i); + // if we have the tombstone value, do not add it to the table + if (columnName == "sync_row_is_tombstone") + { + var isTombstone = Convert.ToInt64(dataReader.GetValue(i)) > 0; + syncRow.RowState = isTombstone ? SyncRowState.Deleted : SyncRowState.Modified; + continue; + } + if (columnName == "sync_update_scope_id") + continue; - // if we have the tombstone value, do not add it to the table - if (columnName == "sync_row_is_tombstone") - { - var isTombstone = Convert.ToInt64(dataReader.GetValue(i)) > 0; - syncRow.RowState = isTombstone ? SyncRowState.Deleted : SyncRowState.Modified; - continue; + var columnValueObject = dataReader.GetValue(i); + var columnValue = columnValueObject == DBNull.Value ? null : columnValueObject; + syncRow[columnName] = columnValue; } - if (columnName == "sync_update_scope_id") - continue; - var columnValueObject = dataReader.GetValue(i); - var columnValue = columnValueObject == DBNull.Value ? null : columnValueObject; - syncRow[columnName] = columnValue; + // if syncRow is not a deleted row, we can check for which kind of row it is. + if (syncRow != null && syncRow.RowState == SyncRowState.None) + syncRow.RowState = SyncRowState.Modified; + + dataReader.Close(); + command.Dispose(); + + return (context, syncRow); } + catch (Exception ex) + { + string message = null; - // if syncRow is not a deleted row, we can check for which kind of row it is. - if (syncRow != null && syncRow.RowState == SyncRowState.None) - syncRow.RowState = SyncRowState.Modified; + if (primaryKeyRow != null) + message += $"Primary Key:{primaryKeyRow}."; - dataReader.Close(); - command.Dispose(); + throw GetSyncError(context, ex, message); + } - return (context, syncRow); } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Constraints.cs b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Constraints.cs index da77ba044..3a75f50f3 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Constraints.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Constraints.cs @@ -49,9 +49,13 @@ public virtual async Task ResetTableAsync(ScopeInfo scopeInfo, SyncContext conte } catch (Exception ex) { - throw GetSyncError(context, ex); - } + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + throw GetSyncError(context, ex, message); + } } /// @@ -88,7 +92,6 @@ public virtual async Task DisableConstraintsAsync(ScopeInfo scopeInfo, string ta var context = new SyncContext(Guid.NewGuid(), scopeInfo.Name); try { - if (scopeInfo.Schema == null || !scopeInfo.Schema.HasTables || !scopeInfo.Schema.HasColumns) return; @@ -106,9 +109,13 @@ public virtual async Task DisableConstraintsAsync(ScopeInfo scopeInfo, string ta } catch (Exception ex) { - throw GetSyncError(context, ex); - } + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + throw GetSyncError(context, ex, message); + } } /// @@ -145,7 +152,6 @@ public virtual async Task EnableConstraintsAsync(ScopeInfo scopeInfo, string tab var context = new SyncContext(Guid.NewGuid(), scopeInfo.Name); try { - if (scopeInfo.Schema == null || !scopeInfo.Schema.HasTables || !scopeInfo.Schema.HasColumns) return; @@ -163,7 +169,12 @@ public virtual async Task EnableConstraintsAsync(ScopeInfo scopeInfo, string tab } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + throw GetSyncError(context, ex, message); } } @@ -173,24 +184,36 @@ public virtual async Task EnableConstraintsAsync(ScopeInfo scopeInfo, string tab internal async Task InternalDisableConstraintsAsync(ScopeInfo scopeInfo, SyncContext context, SyncTable schemaTable, DbConnection connection, DbTransaction transaction = null) { - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ChangesApplying, connection, transaction).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ChangesApplying, connection, transaction).ConfigureAwait(false); + + var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, schemaTable, scopeInfo.Setup); - var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, schemaTable, scopeInfo.Setup); + var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.DisableConstraints, null, + runner.Connection, runner.Transaction, default, default).ConfigureAwait(false); - var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.DisableConstraints, null, - runner.Connection, runner.Transaction, default, default).ConfigureAwait(false); + if (command == null) return context; - if (command == null) return context; + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + command.CommandTimeout = this.Options.DbCommandTimeout.Value; - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - command.CommandTimeout = this.Options.DbCommandTimeout.Value; + await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.DisableConstraints, runner.Connection, runner.Transaction)).ConfigureAwait(false); + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + command.Dispose(); - await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.DisableConstraints, runner.Connection, runner.Transaction)).ConfigureAwait(false); - await command.ExecuteNonQueryAsync().ConfigureAwait(false); - command.Dispose(); + return context; + } + catch (Exception ex) + { + string message = null; + + if (schemaTable != null) + message += $"Table:{schemaTable.GetFullName()}."; - return context; + throw GetSyncError(context, ex, message); + } } /// @@ -199,25 +222,37 @@ internal async Task InternalDisableConstraintsAsync(ScopeInfo scope internal async Task InternalEnableConstraintsAsync(ScopeInfo scopeInfo, SyncContext context, SyncTable schemaTable, DbConnection connection, DbTransaction transaction) { - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ChangesApplying, connection, transaction).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ChangesApplying, connection, transaction).ConfigureAwait(false); - var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, schemaTable, scopeInfo.Setup); + var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, schemaTable, scopeInfo.Setup); - var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.EnableConstraints, null, - runner.Connection, runner.Transaction, default, default).ConfigureAwait(false); + var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.EnableConstraints, null, + runner.Connection, runner.Transaction, default, default).ConfigureAwait(false); - if (command == null) return context; + if (command == null) return context; - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - command.CommandTimeout = this.Options.DbCommandTimeout.Value; + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + command.CommandTimeout = this.Options.DbCommandTimeout.Value; - await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.EnableConstraints, runner.Connection, runner.Transaction)).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.EnableConstraints, runner.Connection, runner.Transaction)).ConfigureAwait(false); - await command.ExecuteNonQueryAsync().ConfigureAwait(false); - command.Dispose(); + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + command.Dispose(); - return context; + return context; + } + catch (Exception ex) + { + string message = null; + + if (schemaTable != null) + message += $"Table:{schemaTable.GetFullName()}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -226,25 +261,36 @@ internal async Task InternalEnableConstraintsAsync(ScopeInfo scopeI internal async Task InternalResetTableAsync(ScopeInfo scopeInfo, SyncContext context, SyncTable schemaTable, DbConnection connection, DbTransaction transaction) { - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ChangesApplying, connection, transaction).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ChangesApplying, connection, transaction).ConfigureAwait(false); - var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, schemaTable, scopeInfo.Setup); + var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, schemaTable, scopeInfo.Setup); - var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.Reset, null, - runner.Connection, runner.Transaction, default, default).ConfigureAwait(false); + var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.Reset, null, + runner.Connection, runner.Transaction, default, default).ConfigureAwait(false); - if (command != null) - { - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - command.CommandTimeout = this.Options.DbCommandTimeout.Value; + if (command != null) + { + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + command.CommandTimeout = this.Options.DbCommandTimeout.Value; - await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.Reset, runner.Connection, runner.Transaction)).ConfigureAwait(false); - await command.ExecuteNonQueryAsync().ConfigureAwait(false); - command.Dispose(); + await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.Reset, runner.Connection, runner.Transaction)).ConfigureAwait(false); + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + command.Dispose(); + } + return context; } + catch (Exception ex) + { + string message = null; + + if (schemaTable != null) + message += $"Table:{schemaTable.GetFullName()}."; - return context; + throw GetSyncError(context, ex, message); + } } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Errors.cs b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Errors.cs index bce998255..f7a7d8e2b 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Errors.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Errors.cs @@ -57,10 +57,10 @@ public abstract partial class BaseOrchestrator bool operationComplete; if (applyType == SyncRowState.Deleted) - (context, operationComplete, operationException) = await this.InternalApplyDeleteAsync(scopeInfo, context, errorRow, schemaChangesTable, lastTimestamp, senderScopeId, true, + (_, operationComplete, operationException) = await this.InternalApplyDeleteAsync(scopeInfo, context, errorRow, schemaChangesTable, lastTimestamp, senderScopeId, true, connection, transaction).ConfigureAwait(false); else - (context, operationComplete, operationException) = await this.InternalApplyUpdateAsync(scopeInfo, context, errorRow, schemaChangesTable, lastTimestamp, senderScopeId, true, + (_, operationComplete, operationException) = await this.InternalApplyUpdateAsync(scopeInfo, context, errorRow, schemaChangesTable, lastTimestamp, senderScopeId, true, connection, transaction).ConfigureAwait(false); diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Schema.cs b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Schema.cs index ea9e9e26d..72fe549a2 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Schema.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Schema.cs @@ -48,12 +48,11 @@ public virtual async Task GetSchemaAsync(string scopeName, SyncSetup se { throw GetSyncError(context, ex); } - } /// - public virtual Task GetSchemaAsync(SyncSetup setup, DbConnection connection = null, DbTransaction transaction = null) + public virtual Task GetSchemaAsync(SyncSetup setup, DbConnection connection = null, DbTransaction transaction = null) => GetSchemaAsync(SyncOptions.DefaultScopeName, setup, connection, transaction); @@ -85,7 +84,6 @@ public virtual async Task GetAllTablesAsync(DbConnection connection = { throw GetSyncError(context, ex); } - } /// @@ -93,45 +91,53 @@ public virtual async Task GetAllTablesAsync(DbConnection connection = /// internal async Task<(SyncContext context, SyncSet schema)> InternalGetSchemaAsync(SyncContext context, SyncSetup setup, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (setup == null || setup.Tables.Count <= 0) - throw new MissingTablesException(context.ScopeName); + try + { + if (setup == null || setup.Tables.Count <= 0) + throw new MissingTablesException(); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - await this.InterceptAsync(new SchemaLoadingArgs(context, setup, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new SchemaLoadingArgs(context, setup, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - // Create the schema - var schema = new SyncSet(); + // Create the schema + var schema = new SyncSet(); - // copy filters from setup - foreach (var filter in setup.Filters) - schema.Filters.Add(filter); + // copy filters from setup + foreach (var filter in setup.Filters) + schema.Filters.Add(filter); - var relations = new List(20); + var relations = new List(20); - foreach (var setupTable in setup.Tables) - { - var (syncTable, tableRelations) = await InternalGetTableSchemaAsync(context, setupTable, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress); + foreach (var setupTable in setup.Tables) + { + var (syncTable, tableRelations) = await InternalGetTableSchemaAsync(context, setupTable, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress); - // Add this table to schema - schema.Tables.Add(syncTable); + // Add this table to schema + schema.Tables.Add(syncTable); - // Since we are not sure of the order of reading tables - // create a tmp relations list - relations.AddRange(tableRelations); + // Since we are not sure of the order of reading tables + // create a tmp relations list + relations.AddRange(tableRelations); - } + } - // Parse and affect relations to schema - SetRelations(relations, schema); + // Parse and affect relations to schema + SetRelations(context, relations, schema); - // Ensure all objects have correct relations to schema - schema.EnsureSchema(); + // Ensure all objects have correct relations to schema + schema.EnsureSchema(); - var schemaArgs = new SchemaLoadedArgs(context, schema, runner.Connection); - await this.InterceptAsync(schemaArgs, runner.Progress, runner.CancellationToken).ConfigureAwait(false); + var schemaArgs = new SchemaLoadedArgs(context, schema, runner.Connection); + await this.InterceptAsync(schemaArgs, runner.Progress, runner.CancellationToken).ConfigureAwait(false); + + return (context, schema); + } + catch (Exception exception) + { + throw GetSyncError(context, exception); + } - return (context, schema); } @@ -141,13 +147,20 @@ public virtual async Task GetAllTablesAsync(DbConnection connection = /// internal async Task<(SyncContext context, SyncSetup setup)> InternalGetAllTablesAsync(SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - var dbBuilder = this.Provider.GetDatabaseBuilder(); + var dbBuilder = this.Provider.GetDatabaseBuilder(); - var setup = await dbBuilder.GetAllTablesAsync(runner.Connection, runner.Transaction).ConfigureAwait(false); + var setup = await dbBuilder.GetAllTablesAsync(runner.Connection, runner.Transaction).ConfigureAwait(false); - return (context, setup); + return (context, setup); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } @@ -155,252 +168,296 @@ public virtual async Task GetAllTablesAsync(DbConnection connection = (SyncContext context, SetupTable setupTable, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (Provider == null) - throw new MissingProviderException(nameof(InternalGetTableSchemaAsync)); + try + { + + if (Provider == null) + throw new MissingProviderException(nameof(InternalGetTableSchemaAsync)); + + // ensure table is compliante with name / schema with provider + var syncTable = await this.Provider.GetDatabaseBuilder().EnsureTableAsync(setupTable.TableName, setupTable.SchemaName, connection, transaction); + + // tmp scope info + var scopeInfo = this.InternalCreateScopeInfo(context.ScopeName); + scopeInfo.Setup = new SyncSetup(); + scopeInfo.Setup.Tables.Add(setupTable); + + var tableBuilder = this.GetTableBuilder(syncTable, scopeInfo); - // ensure table is compliante with name / schema with provider - var syncTable = await this.Provider.GetDatabaseBuilder().EnsureTableAsync(setupTable.TableName, setupTable.SchemaName, connection, transaction); + bool exists; + (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - // tmp scope info - var scopeInfo = this.InternalCreateScopeInfo(context.ScopeName); - scopeInfo.Setup = new SyncSetup(); - scopeInfo.Setup.Tables.Add(setupTable); + if (!exists) + throw new MissingTableException(setupTable.TableName, setupTable.SchemaName); - var tableBuilder = this.GetTableBuilder(syncTable, scopeInfo); + // get columns list + var lstColumns = await tableBuilder.GetColumnsAsync(connection, transaction).ConfigureAwait(false); - bool exists; - (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + // Validate the column list and get the dmTable configuration object. + this.FillSyncTableWithColumns(context, setupTable, syncTable, lstColumns); - if (!exists) - throw new MissingTableException(setupTable.TableName, setupTable.SchemaName, scopeInfo.Name); + // Check primary Keys + await SetPrimaryKeysAsync(context, syncTable, tableBuilder, connection, transaction).ConfigureAwait(false); - // get columns list - var lstColumns = await tableBuilder.GetColumnsAsync(connection, transaction).ConfigureAwait(false); + // get all relations + var tableRelations = await tableBuilder.GetRelationsAsync(connection, transaction).ConfigureAwait(false); - // Validate the column list and get the dmTable configuration object. - this.FillSyncTableWithColumns(setupTable, syncTable, lstColumns); + return (syncTable, tableRelations); + } + catch (Exception ex) + { + string message = null; - // Check primary Keys - await SetPrimaryKeysAsync(syncTable, tableBuilder, connection, transaction).ConfigureAwait(false); + if (setupTable != null) + message += $"Table:{setupTable.GetFullName()}."; - // get all relations - var tableRelations = await tableBuilder.GetRelationsAsync(connection, transaction).ConfigureAwait(false); + throw GetSyncError(context, ex, message); + } - return (syncTable, tableRelations); } /// /// Generate the DmTable configuration from a given columns list /// Validate that all columns are currently supported by the provider /// - private void FillSyncTableWithColumns(SetupTable setupTable, SyncTable schemaTable, IEnumerable columns) + private void FillSyncTableWithColumns(SyncContext context, SetupTable setupTable, SyncTable schemaTable, IEnumerable columns) { - schemaTable.OriginalProvider = this.Provider.GetProviderTypeName(); - //schemaTable.SyncDirection = setupTable.SyncDirection; - - var ordinal = 0; + try + { + schemaTable.OriginalProvider = this.Provider.GetProviderTypeName(); - // Eventually, do not raise exception here, just we don't have any columns - if (columns == null || columns.Any() == false) - return; + var ordinal = 0; - // Delete all existing columns - if (schemaTable.PrimaryKeys.Count > 0) - schemaTable.PrimaryKeys.Clear(); + // Eventually, do not raise exception here, just we don't have any columns + if (columns == null || columns.Any() == false) + return; - if (schemaTable.Columns.Count > 0) - schemaTable.Columns.Clear(); + // Delete all existing columns + if (schemaTable.PrimaryKeys.Count > 0) + schemaTable.PrimaryKeys.Clear(); - IEnumerable lstColumns; + if (schemaTable.Columns.Count > 0) + schemaTable.Columns.Clear(); - // Validate columns list from setup table if any - if (setupTable.Columns != null && setupTable.Columns.Count > 1) - { - lstColumns = new List(); + IEnumerable lstColumns; - foreach (var setupColumn in setupTable.Columns) + // Validate columns list from setup table if any + if (setupTable.Columns != null && setupTable.Columns.Count > 1) { - // Check if the columns list contains the column name we specified in the setup - var column = columns.FirstOrDefault(c => c.ColumnName.Equals(setupColumn, SyncGlobalization.DataSourceStringComparison)); + lstColumns = new List(); + + foreach (var setupColumn in setupTable.Columns) + { + // Check if the columns list contains the column name we specified in the setup + var column = columns.FirstOrDefault(c => c.ColumnName.Equals(setupColumn, SyncGlobalization.DataSourceStringComparison)); - if (column == null) - throw new MissingColumnException(setupColumn, schemaTable.TableName); - else - ((List)lstColumns).Add(column); + if (column == null) + throw new MissingColumnException(setupColumn, schemaTable.TableName); + else + ((List)lstColumns).Add(column); + } + } + else + { + lstColumns = columns; } - } - else - { - lstColumns = columns; - } - foreach (var column in lstColumns.OrderBy(c => c.Ordinal)) - { - // First of all validate if the column is currently supported - if (!this.Provider.GetMetadata().IsValid(column)) - throw new UnsupportedColumnTypeException(setupTable.GetFullName(), column.ColumnName, column.OriginalTypeName, this.Provider.GetProviderTypeName()); - - var columnNameLower = column.ColumnName.ToLowerInvariant(); - if (columnNameLower == "sync_scope_id" - || columnNameLower == "changeTable" - || columnNameLower == "sync_scope_name" - || columnNameLower == "sync_min_timestamp" - || columnNameLower == "sync_row_count" - || columnNameLower == "sync_force_write" - || columnNameLower == "sync_update_scope_id" - || columnNameLower == "sync_timestamp" - || columnNameLower == "sync_row_is_tombstone" - ) - throw new UnsupportedColumnNameException(setupTable.GetFullName(), column.ColumnName, column.OriginalTypeName, this.Provider.GetProviderTypeName()); - - // Gets the max length - column.MaxLength = this.Provider.GetMetadata().GetMaxLength(column); - - // Gets the owner dbtype (SqlDbType, OracleDbType, MySqlDbType, NpsqlDbType & so on ...) - // Sqlite does not have it's own type, so it's DbType too - column.OriginalDbType = this.Provider.GetMetadata().GetOwnerDbType(column).ToString(); - - // get the downgraded DbType - column.DbType = (int)this.Provider.GetMetadata().GetDbType(column); - - // Gets the column readonly's propertye - column.IsReadOnly = this.Provider.GetMetadata().IsReadonly(column); - - // set position ordinal - column.Ordinal = ordinal; - ordinal++; - - // Validate the precision and scale properties - if (this.Provider.GetMetadata().IsNumericType(column)) + foreach (var column in lstColumns.OrderBy(c => c.Ordinal)) { - if (this.Provider.GetMetadata().IsSupportingScale(column)) - { - var (p, s) = this.Provider.GetMetadata().GetPrecisionAndScale(column); - column.Precision = p; - column.PrecisionIsSpecified = true; - column.Scale = s; - column.ScaleIsSpecified = true; - } - else + // First of all validate if the column is currently supported + if (!this.Provider.GetMetadata().IsValid(column)) + throw new UnsupportedColumnTypeException(setupTable.GetFullName(), column.ColumnName, column.OriginalTypeName, this.Provider.GetProviderTypeName()); + + var columnNameLower = column.ColumnName.ToLowerInvariant(); + if (columnNameLower == "sync_scope_id" + || columnNameLower == "changeTable" + || columnNameLower == "sync_scope_name" + || columnNameLower == "sync_min_timestamp" + || columnNameLower == "sync_row_count" + || columnNameLower == "sync_force_write" + || columnNameLower == "sync_update_scope_id" + || columnNameLower == "sync_timestamp" + || columnNameLower == "sync_row_is_tombstone" + ) + throw new UnsupportedColumnNameException(setupTable.GetFullName(), column.ColumnName, column.OriginalTypeName, this.Provider.GetProviderTypeName()); + + // Gets the max length + column.MaxLength = this.Provider.GetMetadata().GetMaxLength(column); + + // Gets the owner dbtype (SqlDbType, OracleDbType, MySqlDbType, NpsqlDbType & so on ...) + // Sqlite does not have it's own type, so it's DbType too + column.OriginalDbType = this.Provider.GetMetadata().GetOwnerDbType(column).ToString(); + + // get the downgraded DbType + column.DbType = (int)this.Provider.GetMetadata().GetDbType(column); + + // Gets the column readonly's propertye + column.IsReadOnly = this.Provider.GetMetadata().IsReadonly(column); + + // set position ordinal + column.Ordinal = ordinal; + ordinal++; + + // Validate the precision and scale properties + if (this.Provider.GetMetadata().IsNumericType(column)) { - column.Precision = this.Provider.GetMetadata().GetPrecision(column); - column.PrecisionIsSpecified = true; - column.ScaleIsSpecified = false; + if (this.Provider.GetMetadata().IsSupportingScale(column)) + { + var (p, s) = this.Provider.GetMetadata().GetPrecisionAndScale(column); + column.Precision = p; + column.PrecisionIsSpecified = true; + column.Scale = s; + column.ScaleIsSpecified = true; + } + else + { + column.Precision = this.Provider.GetMetadata().GetPrecision(column); + column.PrecisionIsSpecified = true; + column.ScaleIsSpecified = false; + } + } - } + // Get the managed type + // Important to set it at the end, because we are altering column.DataType here + column.SetType(this.Provider.GetMetadata().GetType(column)); - // Get the managed type - // Important to set it at the end, because we are altering column.DataType here - column.SetType(this.Provider.GetMetadata().GetType(column)); + // if setup table has no columns, we add all columns from db + // otherwise check if columns exist in the data source + if (setupTable.Columns == null || setupTable.Columns.Count <= 0 || setupTable.Columns.Contains(column.ColumnName)) + schemaTable.Columns.Add(column); - // if setup table has no columns, we add all columns from db - // otherwise check if columns exist in the data source - if (setupTable.Columns == null || setupTable.Columns.Count <= 0 || setupTable.Columns.Contains(column.ColumnName)) - schemaTable.Columns.Add(column); + // If column does not allow null value and is not compute + // We will not be able to insert a row, so raise an error + else if (!column.AllowDBNull && !column.IsCompute && !column.IsReadOnly && string.IsNullOrEmpty(column.DefaultValue)) + throw new Exception($"In table {setupTable.GetFullName()}, column {column.ColumnName} is not part of your setup. But it seems this columns is mandatory in your data source."); + + } + } + catch (Exception ex) + { + string message = null; - // If column does not allow null value and is not compute - // We will not be able to insert a row, so raise an error - else if (!column.AllowDBNull && !column.IsCompute && !column.IsReadOnly && string.IsNullOrEmpty(column.DefaultValue)) - throw new Exception($"In table {setupTable.GetFullName()}, column {column.ColumnName} is not part of your setup. But it seems this columns is mandatory in your data source."); + if (setupTable != null) + message += $"Table:{setupTable.GetFullName()}."; + throw GetSyncError(context, ex, message); } } /// /// Check then add primary keys to schema table /// - private async Task SetPrimaryKeysAsync(SyncTable schemaTable, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction) + private async Task SetPrimaryKeysAsync(SyncContext context, SyncTable schemaTable, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction) { - // Get PrimaryKey - var schemaPrimaryKeys = await tableBuilder.GetPrimaryKeysAsync(connection, transaction).ConfigureAwait(false); + try + { - if (schemaPrimaryKeys == null || schemaPrimaryKeys.Any() == false) - throw new MissingPrimaryKeyException(schemaTable.TableName); + // Get PrimaryKey + var schemaPrimaryKeys = await tableBuilder.GetPrimaryKeysAsync(connection, transaction).ConfigureAwait(false); - // Set the primary Key - foreach (var rowColumn in schemaPrimaryKeys.OrderBy(r => r.Ordinal)) - { - // Find the column in the schema columns - var columnKey = schemaTable.Columns.FirstOrDefault(sc => sc.EqualsByName(rowColumn)); + if (schemaPrimaryKeys == null || schemaPrimaryKeys.Any() == false) + throw new MissingPrimaryKeyException(schemaTable.TableName); + + // Set the primary Key + foreach (var rowColumn in schemaPrimaryKeys.OrderBy(r => r.Ordinal)) + { + // Find the column in the schema columns + var columnKey = schemaTable.Columns.FirstOrDefault(sc => sc.EqualsByName(rowColumn)); - if (columnKey == null) - throw new MissingPrimaryKeyColumnException(rowColumn.ColumnName, schemaTable.TableName); + if (columnKey == null) + throw new MissingPrimaryKeyColumnException(rowColumn.ColumnName, schemaTable.TableName); - var columnNameLower = columnKey.ColumnName.ToLowerInvariant(); - if (columnNameLower == "update_scope_id" - || columnNameLower == "timestamp" - || columnNameLower == "timestamp_bigint" - || columnNameLower == "sync_row_is_tombstone" - || columnNameLower == "last_change_datetime" - ) - throw new UnsupportedPrimaryKeyColumnNameException(schemaTable.GetFullName(), columnKey.ColumnName, columnKey.OriginalTypeName, this.Provider.GetProviderTypeName()); + var columnNameLower = columnKey.ColumnName.ToLowerInvariant(); + if (columnNameLower == "update_scope_id" + || columnNameLower == "timestamp" + || columnNameLower == "timestamp_bigint" + || columnNameLower == "sync_row_is_tombstone" + || columnNameLower == "last_change_datetime" + ) + throw new UnsupportedPrimaryKeyColumnNameException(schemaTable.GetFullName(), columnKey.ColumnName, columnKey.OriginalTypeName, this.Provider.GetProviderTypeName()); - schemaTable.PrimaryKeys.Add(columnKey.ColumnName); + schemaTable.PrimaryKeys.Add(columnKey.ColumnName); + } + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + throw GetSyncError(context, ex, message); } } /// /// For all relations founded, create the SyncRelation and add it to schema /// - private void SetRelations(List relations, SyncSet schema) + private void SetRelations(SyncContext context, List relations, SyncSet schema) { - if (relations == null || relations.Count <= 0) - return; - - foreach (var r in relations) + try { - // Get table from the relation where we need to work on - var schemaTable = schema.Tables[r.TableName, r.SchemaName]; - - // get SchemaColumn from SchemaTable, based on the columns from relations - var schemaColumns = r.Columns.OrderBy(kc => kc.Order) - .Select(kc => - { - var schemaColumn = schemaTable.Columns[kc.KeyColumnName]; - - if (schemaColumn == null) - return null; - - return new SyncColumnIdentifier(schemaColumn.ColumnName, schemaTable.TableName, schemaTable.SchemaName); - }) - .Where(sc => sc != null) - .ToList(); - - // if we don't find the column, maybe we just dont have this column in our setup def - if (schemaColumns == null || schemaColumns.Count == 0) - continue; - - // then Get the foreign table as well - var foreignTable = schemaTable.Schema.Tables[r.ReferenceTableName, r.ReferenceSchemaName]; - - // Since we can have a table with a foreign key but not the parent table - // It's not a problem, just forget it - if (foreignTable == null || foreignTable.Columns.Count == 0) - continue; - var foreignColumns = r.Columns.OrderBy(kc => kc.Order) - .Select(fc => - { - var schemaColumn = foreignTable.Columns[fc.ReferenceColumnName]; - if (schemaColumn == null) - return null; - return new SyncColumnIdentifier(schemaColumn.ColumnName, foreignTable.TableName, foreignTable.SchemaName); - }) - .Where(sc => sc != null) - .ToList(); + if (relations == null || relations.Count <= 0) + return; - if (foreignColumns == null || foreignColumns.Count == 0) - continue; - - var schemaRelation = new SyncRelation(r.ForeignKey, schemaColumns, foreignColumns); - - schema.Relations.Add(schemaRelation); + foreach (var r in relations) + { + // Get table from the relation where we need to work on + var schemaTable = schema.Tables[r.TableName, r.SchemaName]; + + // get SchemaColumn from SchemaTable, based on the columns from relations + var schemaColumns = r.Columns.OrderBy(kc => kc.Order) + .Select(kc => + { + var schemaColumn = schemaTable.Columns[kc.KeyColumnName]; + + if (schemaColumn == null) + return null; + + return new SyncColumnIdentifier(schemaColumn.ColumnName, schemaTable.TableName, schemaTable.SchemaName); + }) + .Where(sc => sc != null) + .ToList(); + + // if we don't find the column, maybe we just dont have this column in our setup def + if (schemaColumns == null || schemaColumns.Count == 0) + continue; + + // then Get the foreign table as well + var foreignTable = schemaTable.Schema.Tables[r.ReferenceTableName, r.ReferenceSchemaName]; + + // Since we can have a table with a foreign key but not the parent table + // It's not a problem, just forget it + if (foreignTable == null || foreignTable.Columns.Count == 0) + continue; + + var foreignColumns = r.Columns.OrderBy(kc => kc.Order) + .Select(fc => + { + var schemaColumn = foreignTable.Columns[fc.ReferenceColumnName]; + if (schemaColumn == null) + return null; + return new SyncColumnIdentifier(schemaColumn.ColumnName, foreignTable.TableName, foreignTable.SchemaName); + }) + .Where(sc => sc != null) + .ToList(); + + if (foreignColumns == null || foreignColumns.Count == 0) + continue; + + var schemaRelation = new SyncRelation(r.ForeignKey, schemaColumns, foreignColumns); + + schema.Relations.Add(schemaRelation); + } + } + catch (Exception ex) + { + throw GetSyncError(context, ex); } } - - } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Timestamp.cs b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Timestamp.cs index aa24e6ddd..47d1204ef 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Timestamp.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.Timestamp.cs @@ -35,9 +35,9 @@ public async virtual Task GetLocalTimestampAsync(string scopeName, DbConne try { await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); - + long timestamp; - (context, timestamp) = await this.InternalGetLocalTimestampAsync(context, + (context, timestamp) = await this.InternalGetLocalTimestampAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress); return timestamp; @@ -60,34 +60,41 @@ public virtual Task GetLocalTimestampAsync(DbConnection connection = null, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress = null) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + try + { + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); + // we don't care about DbScopeType. That's why we are using a random value DbScopeType.Client... + using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetLocalTimestamp, runner.Connection, runner.Transaction); - // we don't care about DbScopeType. That's why we are using a random value DbScopeType.Client... - using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetLocalTimestamp, runner.Connection, runner.Transaction); + if (command == null) + return (context, 0L); - if (command == null) - return (context, 0L); + var action = await this.InterceptAsync(new LocalTimestampLoadingArgs(context, command, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - var action = await this.InterceptAsync(new LocalTimestampLoadingArgs(context, command, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + if (action.Cancel || action.Command == null) + return (context, 0L); - if (action.Cancel || action.Command == null) - return (context, 0L); + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - - long result = Convert.ToInt64(await action.Command.ExecuteScalarAsync().ConfigureAwait(false)); + long result = Convert.ToInt64(await action.Command.ExecuteScalarAsync().ConfigureAwait(false)); - var loadedArgs = await this.InterceptAsync(new LocalTimestampLoadedArgs(context, result, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + var loadedArgs = await this.InterceptAsync(new LocalTimestampLoadedArgs(context, result, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - action.Command.Dispose(); + action.Command.Dispose(); - return (context, loadedArgs.LocalTimestamp); + return (context, loadedArgs.LocalTimestamp); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.cs b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.cs index 31eec9ebd..a16159882 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/BaseOrchestrator.cs @@ -12,6 +12,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -220,22 +221,45 @@ internal virtual async Task CloseConnectionAsync(SyncContext context, DbConnecti } - [DebuggerStepThrough] - internal SyncException GetSyncError(SyncContext context, Exception exception) + //[DebuggerStepThrough] + internal SyncException GetSyncError(SyncContext context, Exception innerException, string message = default, [CallerMemberName] string methodName = null) { - if (exception is SyncException) - return exception as SyncException; + // First we log the error before adding a new layer + if (this.Logger != null) + this.Logger.LogError(SyncEventsId.Exception, innerException, innerException.Message); + + var strSyncStage = context == null ? SyncStage.None : context.SyncStage; + var strScopeName = context == null ? null : $"[{context.ScopeName}]."; + var strMethodName = string.IsNullOrEmpty(methodName) ? "" : $"[{methodName}]."; + var strMessage = string.IsNullOrEmpty(message) ? "" : message; + + var strDataSource = innerException is SyncException se ? se.DataSource : ""; + strDataSource = string.IsNullOrEmpty(strDataSource) ? "" : $"[{strDataSource}]."; + + var strInitialCatalog = innerException is SyncException se2 ? se2.InitialCatalog : ""; + strInitialCatalog = string.IsNullOrEmpty(strInitialCatalog) ? "" : $"[{strInitialCatalog}]."; + + message = $"{strDataSource}{strInitialCatalog}{strScopeName}{strMethodName}{strMessage}"; + + var baseMessage = innerException.Message; - var syncStage = context == null ? SyncStage.None : context.SyncStage; - var syncException = new SyncException(exception, syncStage); + if (innerException is SyncException se3) + { + if (!string.IsNullOrEmpty(se3.BaseMessage)) + baseMessage = se3.BaseMessage; + } + + message += $":{baseMessage}"; + + var syncException = new SyncException(innerException, message, strSyncStage) + { + BaseMessage = baseMessage + }; // try to let the provider enrich the exception if (this.Provider != null) this.Provider.EnsureSyncException(syncException); - if (this.Logger != null) - this.Logger.LogError(SyncEventsId.Exception, syncException, syncException.Message); - return syncException; } @@ -475,7 +499,7 @@ public override string ToString() if (this.Provider == null) return base.ToString(); - return $"{Provider.GetDatabaseName()}, {Provider.GetShortProviderTypeName()}"; + return $"{Provider.GetDatabaseName()}, {Provider.GetShortProviderTypeName()}"; } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.StoredProcedures.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.StoredProcedures.cs index 5e4d3601b..c2d9495db 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.StoredProcedures.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.StoredProcedures.cs @@ -35,7 +35,6 @@ public abstract partial class BaseOrchestrator /// If specified the stored procedure is generated again, even if already exists. public virtual async Task CreateStoredProcedureAsync(ScopeInfo scopeInfo, string tableName, string schemaName = null, DbStoredProcedureType storedProcedureType = default, bool overwrite = false) { - var context = new SyncContext(Guid.NewGuid(), scopeInfo.Name); try { @@ -55,7 +54,7 @@ public virtual async Task CreateStoredProcedureAsync(ScopeInfo scopeInfo, var tableBuilder = this.GetTableBuilder(syncTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, + (context, exists) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // should create only if not exists OR if overwrite has been set @@ -76,7 +75,15 @@ public virtual async Task CreateStoredProcedureAsync(ScopeInfo scopeInfo, } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + message += $"StoredProcedure:{storedProcedureType}."; + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } } @@ -123,7 +130,13 @@ public virtual async Task CreateStoredProceduresAsync(ScopeInfo scopeInfo, } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } } @@ -160,14 +173,21 @@ public virtual async Task ExistStoredProcedureAsync(ScopeInfo scopeInfo, s var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, + (context, exists) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); return exists; } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + message += $"StoredProcedure:{storedProcedureType}."; + + throw GetSyncError(context, ex, message); } } @@ -204,11 +224,11 @@ public virtual async Task DropStoredProcedureAsync(ScopeInfo scopeInfo, st var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool existsAndCanBeDeleted; - (context, existsAndCanBeDeleted) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, + (context, existsAndCanBeDeleted) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (existsAndCanBeDeleted) - (context, hasBeenDropped) = await InternalDropStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, + (context, hasBeenDropped) = await InternalDropStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // Removing cached commands @@ -220,7 +240,14 @@ public virtual async Task DropStoredProcedureAsync(ScopeInfo scopeInfo, st } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + message += $"StoredProcedure:{storedProcedureType}."; + + throw GetSyncError(context, ex, message); } } @@ -258,7 +285,7 @@ public virtual async Task DropStoredProceduresAsync(ScopeInfo scopeInfo, s var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); // check bulk before - (context, hasDroppedAtLeastOneStoredProcedure) = await InternalDropStoredProceduresAsync(scopeInfo, context, tableBuilder, + (context, hasDroppedAtLeastOneStoredProcedure) = await InternalDropStoredProceduresAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // Removing cached commands @@ -270,7 +297,12 @@ public virtual async Task DropStoredProceduresAsync(ScopeInfo scopeInfo, s } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + throw GetSyncError(context, ex, message); } } @@ -278,90 +310,135 @@ public virtual async Task DropStoredProceduresAsync(ScopeInfo scopeInfo, s /// /// Internal create Stored Procedure routine /// - internal async Task<(SyncContext context, bool created)> InternalCreateStoredProcedureAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbStoredProcedureType storedProcedureType, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) + internal async Task<(SyncContext context, bool created)> InternalCreateStoredProcedureAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbStoredProcedureType storedProcedureType, + DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (tableBuilder.TableDescription.Columns.Count <= 0) - throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); + try + { + if (tableBuilder.TableDescription.Columns.Count <= 0) + throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); - if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) - throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); + if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) + throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); - var filter = tableBuilder.TableDescription.GetFilter(); + var filter = tableBuilder.TableDescription.GetFilter(); - var command = await tableBuilder.GetCreateStoredProcedureCommandAsync(storedProcedureType, filter, connection, transaction).ConfigureAwait(false); + var command = await tableBuilder.GetCreateStoredProcedureCommandAsync(storedProcedureType, filter, connection, transaction).ConfigureAwait(false); - if (command == null) - return (context, false); + if (command == null) + return (context, false); - var action = new StoredProcedureCreatingArgs(context, tableBuilder.TableDescription, storedProcedureType, command, connection, transaction); - await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); + var action = new StoredProcedureCreatingArgs(context, tableBuilder.TableDescription, storedProcedureType, command, connection, transaction); + await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await this.InterceptAsync(new StoredProcedureCreatedArgs(context, tableBuilder.TableDescription, storedProcedureType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + + return (context, true); + } + catch (Exception ex) + { + string message = null; - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - await this.InterceptAsync(new StoredProcedureCreatedArgs(context, tableBuilder.TableDescription, storedProcedureType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; - return (context, true); + message += $"StoredProcedure:{storedProcedureType}."; + + throw GetSyncError(context, ex, message); + } } /// /// Internal drop storedProcedure routine /// - internal async Task<(SyncContext context, bool dropped)> InternalDropStoredProcedureAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbStoredProcedureType storedProcedureType, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) + internal async Task<(SyncContext context, bool dropped)> InternalDropStoredProcedureAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbStoredProcedureType storedProcedureType, + DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var filter = tableBuilder.TableDescription.GetFilter(); + try + { + var filter = tableBuilder.TableDescription.GetFilter(); + + var command = await tableBuilder.GetDropStoredProcedureCommandAsync(storedProcedureType, filter, connection, transaction).ConfigureAwait(false); - var command = await tableBuilder.GetDropStoredProcedureCommandAsync(storedProcedureType, filter, connection, transaction).ConfigureAwait(false); + if (command == null) + return (context, false); - if (command == null) - return (context, false); + var action = await this.InterceptAsync(new StoredProcedureDroppingArgs(context, tableBuilder.TableDescription, storedProcedureType, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - var action = await this.InterceptAsync(new StoredProcedureDroppingArgs(context, tableBuilder.TableDescription, storedProcedureType, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + if (action.Cancel || action.Command == null) + return (context, false); - if (action.Cancel || action.Command == null) - return (context, false); + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + + await this.InterceptAsync(new StoredProcedureDroppedArgs(context, tableBuilder.TableDescription, storedProcedureType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + + return (context, true); + } + catch (Exception ex) + { + string message = null; - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; - await this.InterceptAsync(new StoredProcedureDroppedArgs(context, tableBuilder.TableDescription, storedProcedureType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + message += $"StoredProcedure:{storedProcedureType}."; - return (context, true); + throw GetSyncError(context, ex, message); + } } /// /// Internal exists storedProcedure procedure routine /// - internal async Task<(SyncContext context, bool exists)> InternalExistsStoredProcedureAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbStoredProcedureType storedProcedureType, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) + internal async Task<(SyncContext context, bool exists)> InternalExistsStoredProcedureAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbStoredProcedureType storedProcedureType, + DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var filter = tableBuilder.TableDescription.GetFilter(); + try + { + var filter = tableBuilder.TableDescription.GetFilter(); + + var existsCommand = await tableBuilder.GetExistsStoredProcedureCommandAsync(storedProcedureType, filter, connection, transaction).ConfigureAwait(false); + if (existsCommand == null) + return (context, false); + + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + existsCommand.CommandTimeout = this.Options.DbCommandTimeout.Value; - var existsCommand = await tableBuilder.GetExistsStoredProcedureCommandAsync(storedProcedureType, filter, connection, transaction).ConfigureAwait(false); - if (existsCommand == null) - return (context, false); + await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - existsCommand.CommandTimeout = this.Options.DbCommandTimeout.Value; + var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); + var exists = Convert.ToInt32(existsResultObject) > 0; - await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + return (context, exists); + } + catch (Exception ex) + { + string message = null; - var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); - var exists = Convert.ToInt32(existsResultObject) > 0; + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; - return (context, exists); + message += $"StoredProcedure:{storedProcedureType}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -370,27 +447,39 @@ public virtual async Task DropStoredProceduresAsync(ScopeInfo scopeInfo, s internal async Task<(SyncContext context, bool dropped)> InternalDropStoredProceduresAsync( ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - // check bulk before - var hasDroppedAtLeastOneStoredProcedure = false; + try + { + // check bulk before + var hasDroppedAtLeastOneStoredProcedure = false; - var listStoredProcedureType = Enum.GetValues(typeof(DbStoredProcedureType)).Cast().OrderBy(sp => (int)sp); + var listStoredProcedureType = Enum.GetValues(typeof(DbStoredProcedureType)).Cast().OrderBy(sp => (int)sp); - foreach (DbStoredProcedureType storedProcedureType in Enum.GetValues(typeof(DbStoredProcedureType))) - { - bool exists; - (context, exists) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (exists) + foreach (DbStoredProcedureType storedProcedureType in Enum.GetValues(typeof(DbStoredProcedureType))) { - bool dropped; - (context, dropped) = await InternalDropStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - - // If at least one stored proc has been dropped, we're good to return true; - if (dropped) - hasDroppedAtLeastOneStoredProcedure = true; + bool exists; + (context, exists) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + if (exists) + { + bool dropped; + (context, dropped) = await InternalDropStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + + // If at least one stored proc has been dropped, we're good to return true; + if (dropped) + hasDroppedAtLeastOneStoredProcedure = true; + } } + + return (context, hasDroppedAtLeastOneStoredProcedure); } + catch (Exception ex) + { + string message = null; - return (context, hasDroppedAtLeastOneStoredProcedure); + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -399,57 +488,70 @@ public virtual async Task DropStoredProceduresAsync(ScopeInfo scopeInfo, s internal async Task<(SyncContext context, bool created)> InternalCreateStoredProceduresAsync( ScopeInfo scopeInfo, SyncContext context, bool overwrite, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var hasCreatedAtLeastOneStoredProcedure = false; + try + { + var hasCreatedAtLeastOneStoredProcedure = false; - // Order Asc is the correct order to Delete - var listStoredProcedureType = Enum.GetValues(typeof(DbStoredProcedureType)).Cast().OrderBy(sp => (int)sp); + // Order Asc is the correct order to Delete + var listStoredProcedureType = Enum.GetValues(typeof(DbStoredProcedureType)).Cast().OrderBy(sp => (int)sp); + + // we need to drop bulk in order to be sure bulk type is delete after all + if (overwrite) + { + foreach (DbStoredProcedureType storedProcedureType in listStoredProcedureType) + { + bool exists; + (context, exists) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + + if (exists) + (context, _) = await InternalDropStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + } + + } + + // Order Desc is the correct order to Create + listStoredProcedureType = Enum.GetValues(typeof(DbStoredProcedureType)).Cast().OrderByDescending(sp => (int)sp); - // we need to drop bulk in order to be sure bulk type is delete after all - if (overwrite) - { foreach (DbStoredProcedureType storedProcedureType in listStoredProcedureType) { + // check with filter + if ((storedProcedureType is DbStoredProcedureType.SelectChangesWithFilters || storedProcedureType is DbStoredProcedureType.SelectInitializedChangesWithFilters) + && tableBuilder.TableDescription.GetFilter() == null) + continue; + bool exists; (context, exists) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (exists) + // Drop storedProcedure if already exists + if (exists && overwrite) (context, _) = await InternalDropStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - } - - } - // Order Desc is the correct order to Create - listStoredProcedureType = Enum.GetValues(typeof(DbStoredProcedureType)).Cast().OrderByDescending(sp => (int)sp); + var shouldCreate = !exists || overwrite; - foreach (DbStoredProcedureType storedProcedureType in listStoredProcedureType) - { - // check with filter - if ((storedProcedureType is DbStoredProcedureType.SelectChangesWithFilters || storedProcedureType is DbStoredProcedureType.SelectInitializedChangesWithFilters) - && tableBuilder.TableDescription.GetFilter() == null) - continue; + if (!shouldCreate) + continue; - bool exists; - (context, exists) = await InternalExistsStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + bool created; + (context, created) = await InternalCreateStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - // Drop storedProcedure if already exists - if (exists && overwrite) - (context, _) = await InternalDropStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + // If at least one stored proc has been created, we're good to return true; + if (created) + hasCreatedAtLeastOneStoredProcedure = true; + } - var shouldCreate = !exists || overwrite; + return (context, hasCreatedAtLeastOneStoredProcedure); + } + catch (Exception ex) + { + string message = null; - if (!shouldCreate) - continue; + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; - bool created; - (context, created) = await InternalCreateStoredProcedureAsync(scopeInfo, context, tableBuilder, storedProcedureType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + message += $"Overwrite:{overwrite}."; - // If at least one stored proc has been created, we're good to return true; - if (created) - hasCreatedAtLeastOneStoredProcedure = true; + throw GetSyncError(context, ex, message); } - - return (context, hasCreatedAtLeastOneStoredProcedure); } - } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.Table.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.Table.cs index 23bec5c8a..509661b79 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.Table.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.Table.cs @@ -56,15 +56,15 @@ public async Task CreateTableAsync(ScopeInfo scopeInfo, string tableName, var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool schemaExists; - (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, + (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (!schemaExists) - (context, _) = await InternalCreateSchemaAsync(scopeInfo, context, tableBuilder, + (context, _) = await InternalCreateSchemaAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); bool exists; - (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, + (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // should create only if not exists OR if overwrite has been set @@ -74,7 +74,7 @@ public async Task CreateTableAsync(ScopeInfo scopeInfo, string tableName, { // Drop if already exists and we need to overwrite if (exists && overwrite) - (context, _) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, + (context, _) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); (context, hasBeenCreated) = await InternalCreateTableAsync(scopeInfo, context, tableBuilder, @@ -127,11 +127,11 @@ public async Task CreateTablesAsync(ScopeInfo scopeInfo, bool overwrite = { var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, + (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (exists) - (context, _) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, + (context, _) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); } } @@ -142,15 +142,15 @@ public async Task CreateTablesAsync(ScopeInfo scopeInfo, bool overwrite = var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool schemaExists; - (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, + (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (!schemaExists) - (context, _) = await InternalCreateSchemaAsync(scopeInfo, context, tableBuilder, + (context, _) = await InternalCreateSchemaAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); bool exists; - (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, + (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // should create only if not exists OR if overwrite has been set @@ -160,11 +160,11 @@ public async Task CreateTablesAsync(ScopeInfo scopeInfo, bool overwrite = { // Drop if already exists and we need to overwrite if (exists && overwrite) - (context, _) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, + (context, _) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); bool hasBeenCreated; - (context, hasBeenCreated) = await InternalCreateTableAsync(scopeInfo, context, tableBuilder, + (context, hasBeenCreated) = await InternalCreateTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (hasBeenCreated) @@ -212,7 +212,7 @@ public async Task ExistTableAsync(ScopeInfo scopeInfo, string tableName, s var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, + (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); @@ -220,7 +220,12 @@ public async Task ExistTableAsync(ScopeInfo scopeInfo, string tableName, s } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + throw GetSyncError(context, ex, message); } } @@ -258,11 +263,11 @@ public async Task DropTableAsync(ScopeInfo scopeInfo, string tableName, st var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, + (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (exists) - (context, hasBeenDropped) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, + (context, hasBeenDropped) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); @@ -271,7 +276,12 @@ public async Task DropTableAsync(ScopeInfo scopeInfo, string tableName, st } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + throw GetSyncError(context, ex, message); } } @@ -310,7 +320,7 @@ public async Task DropTablesAsync(ScopeInfo scopeInfo) runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (exists) - (context, atLeastOneTableHasBeenDropped) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, + (context, atLeastOneTableHasBeenDropped) = await InternalDropTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); } @@ -329,42 +339,57 @@ public async Task DropTablesAsync(ScopeInfo scopeInfo) /// internal async Task<(SyncContext contet, bool added)> InternalAddColumnAsync(ScopeInfo scopeInfo, SyncContext context, string addedColumnName, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (Provider == null) - throw new MissingProviderException(nameof(InternalAddColumnAsync)); + try + { + if (Provider == null) + throw new MissingProviderException(nameof(InternalAddColumnAsync)); - if (tableBuilder.TableDescription.Columns.Count <= 0) - throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); + if (tableBuilder.TableDescription.Columns.Count <= 0) + throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); - if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) - throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); + if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) + throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); - using var command = await tableBuilder.GetAddColumnCommandAsync(addedColumnName, connection, transaction).ConfigureAwait(false); + using var command = await tableBuilder.GetAddColumnCommandAsync(addedColumnName, connection, transaction).ConfigureAwait(false); - if (command == null) - return (context, false); + if (command == null) + return (context, false); - var (tableName, _) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); + var (tableName, _) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); - var action = new ColumnCreatingArgs(context, addedColumnName, tableBuilder.TableDescription, tableName, command, connection, transaction); + var action = new ColumnCreatingArgs(context, addedColumnName, tableBuilder.TableDescription, tableName, command, connection, transaction); - await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - await this.InterceptAsync(new ColumnCreatedArgs(context, addedColumnName, tableBuilder.TableDescription, tableName, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - action.Command.Dispose(); + await this.InterceptAsync(new ColumnCreatedArgs(context, addedColumnName, tableBuilder.TableDescription, tableName, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - return (context, true); + action.Command.Dispose(); + + return (context, true); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + if (!string.IsNullOrEmpty(addedColumnName)) + message += $"Column:{addedColumnName}."; + + throw GetSyncError(context, ex, message); + } } @@ -373,37 +398,53 @@ public async Task DropTablesAsync(ScopeInfo scopeInfo) /// internal async Task<(SyncContext context, bool dropped)> InternalDropColumnAsync(ScopeInfo scopeInfo, SyncContext context, string droppedColumnName, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (tableBuilder.TableDescription.Columns.Count <= 0) - throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); + try + { + + if (tableBuilder.TableDescription.Columns.Count <= 0) + throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); - if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) - throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); + if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) + throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); - using var command = await tableBuilder.GetDropColumnCommandAsync(droppedColumnName, connection, transaction).ConfigureAwait(false); + using var command = await tableBuilder.GetDropColumnCommandAsync(droppedColumnName, connection, transaction).ConfigureAwait(false); - if (command == null) - return (context, false); + if (command == null) + return (context, false); - var (tableName, _) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); + var (tableName, _) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); - var action = await this.InterceptAsync(new ColumnDroppingArgs(context, droppedColumnName, tableBuilder.TableDescription, tableName, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + var action = await this.InterceptAsync(new ColumnDroppingArgs(context, droppedColumnName, tableBuilder.TableDescription, tableName, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - await this.InterceptAsync(new ColumnDroppedArgs(context, droppedColumnName, tableBuilder.TableDescription, tableName, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ColumnDroppedArgs(context, droppedColumnName, tableBuilder.TableDescription, tableName, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - action.Command.Dispose(); + action.Command.Dispose(); - return (context, true); + return (context, true); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + if (!string.IsNullOrEmpty(droppedColumnName)) + message += $"Column:{droppedColumnName}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -411,39 +452,52 @@ public async Task DropTablesAsync(ScopeInfo scopeInfo) /// internal async Task<(SyncContext context, bool created)> InternalCreateTableAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (tableBuilder.TableDescription.Columns.Count <= 0) - throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); + try + { + + if (tableBuilder.TableDescription.Columns.Count <= 0) + throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); + + if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) + throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); + + using var command = await tableBuilder.GetCreateTableCommandAsync(connection, transaction).ConfigureAwait(false); - if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) - throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); + if (command == null) + return (context, false); - using var command = await tableBuilder.GetCreateTableCommandAsync(connection, transaction).ConfigureAwait(false); + var (tableName, _) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); - if (command == null) - return (context, false); + var action = new TableCreatingArgs(context, tableBuilder.TableDescription, tableName, command, connection, transaction); - var (tableName, _) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); + await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); - var action = new TableCreatingArgs(context, tableBuilder.TableDescription, tableName, command, connection, transaction); + if (action.Cancel || action.Command == null) + return (context, false); - await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; - if (action.Cancel || action.Command == null) - return (context, false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new TableCreatedArgs(context, tableBuilder.TableDescription, tableName, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + action.Command.Dispose(); - await this.InterceptAsync(new TableCreatedArgs(context, tableBuilder.TableDescription, tableName, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + return (context, true); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; - action.Command.Dispose(); - - return (context, true); + throw GetSyncError(context, ex, message); + } } /// @@ -452,29 +506,42 @@ public async Task DropTablesAsync(ScopeInfo scopeInfo) internal async Task<(SyncContext context, bool created)> InternalCreateSchemaAsync( ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - using var command = await tableBuilder.GetCreateSchemaCommandAsync(connection, transaction).ConfigureAwait(false); + try + { + + using var command = await tableBuilder.GetCreateSchemaCommandAsync(connection, transaction).ConfigureAwait(false); + + if (command == null) + return (context, false); + + var action = await this.InterceptAsync(new SchemaNameCreatingArgs(context, tableBuilder.TableDescription, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - if (command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - var action = await this.InterceptAsync(new SchemaNameCreatingArgs(context, tableBuilder.TableDescription, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; - if (action.Cancel || action.Command == null) - return (context, false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new SchemaNameCreatedArgs(context, tableBuilder.TableDescription, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + action.Command.Dispose(); - await this.InterceptAsync(new SchemaNameCreatedArgs(context, tableBuilder.TableDescription, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + return (context, true); + } + catch (Exception ex) + { + string message = null; - action.Command.Dispose(); + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Schema:{tableBuilder.TableDescription.SchemaName}."; - return (context, true); + throw GetSyncError(context, ex, message); + } } /// @@ -482,32 +549,44 @@ public async Task DropTablesAsync(ScopeInfo scopeInfo) /// internal async Task<(SyncContext context, bool dropped)> InternalDropTableAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - using var command = await tableBuilder.GetDropTableCommandAsync(connection, transaction).ConfigureAwait(false); + try + { + + using var command = await tableBuilder.GetDropTableCommandAsync(connection, transaction).ConfigureAwait(false); - if (command == null) - return (context, false); + if (command == null) + return (context, false); - var (tableName, _) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); + var (tableName, _) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); - var action = await this.InterceptAsync(new TableDroppingArgs(context, tableBuilder.TableDescription, tableName, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + var action = await this.InterceptAsync(new TableDroppingArgs(context, tableBuilder.TableDescription, tableName, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - await this.InterceptAsync(new TableDroppedArgs(context, tableBuilder.TableDescription, tableName, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new TableDroppedArgs(context, tableBuilder.TableDescription, tableName, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - action.Command.Dispose(); + action.Command.Dispose(); - return (context, true); + return (context, true); + } + catch (Exception ex) + { + string message = null; + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -515,22 +594,34 @@ public async Task DropTablesAsync(ScopeInfo scopeInfo) /// internal async Task<(SyncContext context, bool exists)> InternalExistsTableAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - // Get exists command - using var existsCommand = await tableBuilder.GetExistsTableCommandAsync(connection, transaction).ConfigureAwait(false); + try + { + + // Get exists command + using var existsCommand = await tableBuilder.GetExistsTableCommandAsync(connection, transaction).ConfigureAwait(false); - if (existsCommand == null) - return (context, false); + if (existsCommand == null) + return (context, false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - existsCommand.CommandTimeout = this.Options.DbCommandTimeout.Value; - - await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + existsCommand.CommandTimeout = this.Options.DbCommandTimeout.Value; - var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); - var exists = Convert.ToInt32(existsResultObject) > 0; - return (context, exists); + await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); + var exists = Convert.ToInt32(existsResultObject) > 0; + return (context, exists); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -538,25 +629,38 @@ public async Task DropTablesAsync(ScopeInfo scopeInfo) /// internal async Task<(SyncContext context, bool exists)> InternalExistsSchemaAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (string.IsNullOrEmpty(tableBuilder.TableDescription.SchemaName) || tableBuilder.TableDescription.SchemaName == "dbo") - return (context, true); + try + { - // Get exists command - using var existsCommand = await tableBuilder.GetExistsSchemaCommandAsync(connection, transaction).ConfigureAwait(false); + if (string.IsNullOrEmpty(tableBuilder.TableDescription.SchemaName) || tableBuilder.TableDescription.SchemaName == "dbo") + return (context, true); - if (existsCommand == null) - return (context, false); + // Get exists command + using var existsCommand = await tableBuilder.GetExistsSchemaCommandAsync(connection, transaction).ConfigureAwait(false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - existsCommand.CommandTimeout = this.Options.DbCommandTimeout.Value; + if (existsCommand == null) + return (context, false); - await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + existsCommand.CommandTimeout = this.Options.DbCommandTimeout.Value; - var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); - var exists = Convert.ToInt32(existsResultObject) > 0; - return (context, exists); + await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); + var exists = Convert.ToInt32(existsResultObject) > 0; + return (context, exists); + + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -564,22 +668,39 @@ public async Task DropTablesAsync(ScopeInfo scopeInfo) /// internal async Task<(SyncContext context, bool exists)> InternalExistsColumnAsync(ScopeInfo scopeInfo, SyncContext context, string columnName, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - // Get exists command - using var existsCommand = await tableBuilder.GetExistsColumnCommandAsync(columnName, connection, transaction).ConfigureAwait(false); + try + { + + + // Get exists command + using var existsCommand = await tableBuilder.GetExistsColumnCommandAsync(columnName, connection, transaction).ConfigureAwait(false); - if (existsCommand == null) - return (context, false); + if (existsCommand == null) + return (context, false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - existsCommand.CommandTimeout = this.Options.DbCommandTimeout.Value; - - await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + existsCommand.CommandTimeout = this.Options.DbCommandTimeout.Value; - var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); - var exists = Convert.ToInt32(existsResultObject) > 0; + await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - return (context, exists); + var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); + var exists = Convert.ToInt32(existsResultObject) > 0; + + return (context, exists); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + if (!string.IsNullOrEmpty(columnName)) + message += $"Column:{columnName}."; + + throw GetSyncError(context, ex, message); + } } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.TrackingTable.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.TrackingTable.cs index a58e71526..90eb2bc1a 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.TrackingTable.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.TrackingTable.cs @@ -34,7 +34,7 @@ public abstract partial class BaseOrchestrator /// If specified the tracking table is dropped, if exists, then created. /// Optional Connection /// Optional Transaction - public async Task CreateTrackingTableAsync(ScopeInfo scopeInfo, string tableName, string schemaName = default, bool overwrite = false, + public async Task CreateTrackingTableAsync(ScopeInfo scopeInfo, string tableName, string schemaName = default, bool overwrite = false, DbConnection connection = null, DbTransaction transaction = null) { var context = new SyncContext(Guid.NewGuid(), scopeInfo.Name); @@ -55,15 +55,15 @@ public async Task CreateTrackingTableAsync(ScopeInfo scopeInfo, string tab var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool schemaExists; - (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, + (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (!schemaExists) - (context, _) = await InternalCreateSchemaAsync(scopeInfo,context, tableBuilder, + (context, _) = await InternalCreateSchemaAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); bool exists; - (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, + (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // should create only if not exists OR if overwrite has been set @@ -73,10 +73,10 @@ public async Task CreateTrackingTableAsync(ScopeInfo scopeInfo, string tab { // Drop if already exists and we need to overwrite if (exists && overwrite) - (context, _) = await InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, + (context, _) = await InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - (context, hasBeenCreated) = await InternalCreateTrackingTableAsync(scopeInfo, context, tableBuilder, + (context, hasBeenCreated) = await InternalCreateTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); } @@ -86,7 +86,14 @@ public async Task CreateTrackingTableAsync(ScopeInfo scopeInfo, string tab } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } } @@ -133,7 +140,12 @@ public async Task ExistTrackingTableAsync(ScopeInfo scopeInfo, string tabl } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + throw GetSyncError(context, ex, message); } } @@ -169,15 +181,15 @@ public async Task CreateTrackingTablesAsync(ScopeInfo scopeInfo, bool over var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool schemaExists; - (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, + (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (!schemaExists) - (context, _) = await InternalCreateSchemaAsync(scopeInfo, context, tableBuilder, + (context, _) = await InternalCreateSchemaAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); bool exists; - (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, + (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // should create only if not exists OR if overwrite has been set @@ -187,11 +199,11 @@ public async Task CreateTrackingTablesAsync(ScopeInfo scopeInfo, bool over { // Drop if already exists and we need to overwrite if (exists && overwrite) - (context, _) = await InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, - runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + (context, _) = await InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, + runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); bool hasBeenCreated; - (context, hasBeenCreated) = await InternalCreateTrackingTableAsync(scopeInfo, context, tableBuilder, + (context, hasBeenCreated) = await InternalCreateTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (hasBeenCreated) @@ -204,7 +216,11 @@ public async Task CreateTrackingTablesAsync(ScopeInfo scopeInfo, bool over } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } } @@ -233,18 +249,23 @@ public async Task DropTrackingTableAsync(ScopeInfo scopeInfo, string table var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, + (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (exists) - (context, hasBeenDropped) = await InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, + (context, hasBeenDropped) = await InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); return hasBeenDropped; } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + throw GetSyncError(context, ex, message); } } @@ -279,11 +300,11 @@ public async Task DropTrackingTablesAsync(ScopeInfo scopeInfo, DbConnectio var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, + (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (exists) - (context, atLeastOneTrackingTableHasBeenDropped) = await InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, + (context, atLeastOneTrackingTableHasBeenDropped) = await InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); } @@ -303,43 +324,57 @@ public async Task DropTrackingTablesAsync(ScopeInfo scopeInfo, DbConnectio internal virtual async Task<(SyncContext context, bool crated)> InternalCreateTrackingTableAsync( ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (Provider == null) - throw new MissingProviderException(nameof(InternalCreateTrackingTableAsync)); + try + { + + if (Provider == null) + throw new MissingProviderException(nameof(InternalCreateTrackingTableAsync)); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (tableBuilder.TableDescription.Columns.Count <= 0) - throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); + if (tableBuilder.TableDescription.Columns.Count <= 0) + throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); - if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) - throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); + if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) + throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); - using var command = await tableBuilder.GetCreateTrackingTableCommandAsync(runner.Connection, runner.Transaction).ConfigureAwait(false); + using var command = await tableBuilder.GetCreateTrackingTableCommandAsync(runner.Connection, runner.Transaction).ConfigureAwait(false); - if (command == null) - return (context, false); + if (command == null) + return (context, false); - var (_, trackingTableName) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); + var (_, trackingTableName) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); - var action = await this.InterceptAsync(new TrackingTableCreatingArgs(context, tableBuilder.TableDescription, trackingTableName, command, runner.Connection, runner.Transaction), - runner.Progress, runner.CancellationToken).ConfigureAwait(false); + var action = await this.InterceptAsync(new TrackingTableCreatingArgs(context, tableBuilder.TableDescription, trackingTableName, command, runner.Connection, runner.Transaction), + runner.Progress, runner.CancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = this.Options.DbCommandTimeout.Value; - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - await this.InterceptAsync(new TrackingTableCreatedArgs(context, tableBuilder.TableDescription, trackingTableName, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new TrackingTableCreatedArgs(context, tableBuilder.TableDescription, trackingTableName, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - action.Command.Dispose(); + action.Command.Dispose(); - return (context, true); + return (context, true); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + + throw GetSyncError(context, ex, message); + } } /// @@ -348,41 +383,54 @@ public async Task DropTrackingTablesAsync(ScopeInfo scopeInfo, DbConnectio internal virtual async Task<(SyncContext context, bool renamed)> InternalRenameTrackingTableAsync( ScopeInfo scopeInfo, SyncContext context, ParserName oldTrackingTableName, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Provisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (tableBuilder.TableDescription.Columns.Count <= 0) - throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); + if (tableBuilder.TableDescription.Columns.Count <= 0) + throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); - if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) - throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); + if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) + throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); - using var command = await tableBuilder.GetRenameTrackingTableCommandAsync(oldTrackingTableName, runner.Connection, runner.Transaction).ConfigureAwait(false); + using var command = await tableBuilder.GetRenameTrackingTableCommandAsync(oldTrackingTableName, runner.Connection, runner.Transaction).ConfigureAwait(false); - if (command == null) - return (context, false); + if (command == null) + return (context, false); - var (_, trackingTableName) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); + var (_, trackingTableName) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); - var action = await this.InterceptAsync(new TrackingTableRenamingArgs(context, tableBuilder.TableDescription, trackingTableName, oldTrackingTableName, command, runner.Connection, runner.Transaction), - runner.Progress, runner.CancellationToken).ConfigureAwait(false); + var action = await this.InterceptAsync(new TrackingTableRenamingArgs(context, tableBuilder.TableDescription, trackingTableName, oldTrackingTableName, command, runner.Connection, runner.Transaction), + runner.Progress, runner.CancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - // Parametrized command timeout established if exist - if (Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = Options.DbCommandTimeout.Value; - - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + // Parametrized command timeout established if exist + if (Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = Options.DbCommandTimeout.Value; - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - await this.InterceptAsync(new TrackingTableRenamedArgs(context, tableBuilder.TableDescription, trackingTableName, oldTrackingTableName, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - action.Command.Dispose(); + await this.InterceptAsync(new TrackingTableRenamedArgs(context, tableBuilder.TableDescription, trackingTableName, oldTrackingTableName, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + + action.Command.Dispose(); + + return (context, true); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + throw GetSyncError(context, ex, message); + } - return (context, true); } /// @@ -391,32 +439,44 @@ public async Task DropTrackingTablesAsync(ScopeInfo scopeInfo, DbConnectio internal virtual async Task<(SyncContext context, bool dropped)> InternalDropTrackingTableAsync( ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Deprovisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.Deprovisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - using var command = await tableBuilder.GetDropTrackingTableCommandAsync(connection, transaction).ConfigureAwait(false); + using var command = await tableBuilder.GetDropTrackingTableCommandAsync(connection, transaction).ConfigureAwait(false); - if (command == null) - return (context, false); + if (command == null) + return (context, false); - var (_, trackingTableName) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); + var (_, trackingTableName) = this.Provider.GetParsers(tableBuilder.TableDescription, scopeInfo.Setup); - var action = await this.InterceptAsync(new TrackingTableDroppingArgs(context, tableBuilder.TableDescription, trackingTableName, command, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); + var action = await this.InterceptAsync(new TrackingTableDroppingArgs(context, tableBuilder.TableDescription, trackingTableName, command, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - // Parametrized command timeout established if exist - if (Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = Options.DbCommandTimeout.Value; + // Parametrized command timeout established if exist + if (Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = Options.DbCommandTimeout.Value; - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - await this.InterceptAsync(new TrackingTableDroppedArgs(context, tableBuilder.TableDescription, trackingTableName, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await this.InterceptAsync(new TrackingTableDroppedArgs(context, tableBuilder.TableDescription, trackingTableName, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); + + action.Command.Dispose(); + + return (context, true); + } + catch (Exception ex) + { + string message = null; - action.Command.Dispose(); + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; - return (context, true); + throw GetSyncError(context, ex, message); + } } /// @@ -424,26 +484,37 @@ public async Task DropTrackingTablesAsync(ScopeInfo scopeInfo, DbConnectio /// internal virtual async Task<(SyncContext context, bool exists)> InternalExistsTrackingTableAsync(ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - // Get exists command - using var existsCommand = await tableBuilder.GetExistsTrackingTableCommandAsync(runner.Connection, runner.Transaction).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + // Get exists command + using var existsCommand = await tableBuilder.GetExistsTrackingTableCommandAsync(runner.Connection, runner.Transaction).ConfigureAwait(false); - if (existsCommand == null) - return (context, false); + if (existsCommand == null) + return (context, false); - await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); - // Parametrized command timeout established if exist - if (Options.DbCommandTimeout.HasValue) - { - existsCommand.CommandTimeout = Options.DbCommandTimeout.Value; + // Parametrized command timeout established if exist + if (Options.DbCommandTimeout.HasValue) + { + existsCommand.CommandTimeout = Options.DbCommandTimeout.Value; + } + + var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); + var exists = Convert.ToInt32(existsResultObject) > 0; + await runner.CommitAsync().ConfigureAwait(false); + return (context, exists); } + catch (Exception ex) + { + string message = null; - var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); - var exists = Convert.ToInt32(existsResultObject) > 0; - await runner.CommitAsync().ConfigureAwait(false); - return (context, exists); + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + throw GetSyncError(context, ex, message); + } } } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.Triggers.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.Triggers.cs index 079176310..197a5db80 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.Triggers.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Builders/BaseOrchestrator.Triggers.cs @@ -57,7 +57,7 @@ public async Task CreateTriggerAsync(ScopeInfo scopeInfo, string tableName var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, + (context, exists) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); // should create only if not exists OR if overwrite has been set @@ -67,10 +67,10 @@ public async Task CreateTriggerAsync(ScopeInfo scopeInfo, string tableName { // Drop trigger if already exists if (exists && overwrite) - (context, _) = await InternalDropTriggerAsync(scopeInfo, context, tableBuilder, triggerType, + (context, _) = await InternalDropTriggerAsync(scopeInfo, context, tableBuilder, triggerType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - (context, hasBeenCreated) = await InternalCreateTriggerAsync(scopeInfo, context, tableBuilder, triggerType, + (context, hasBeenCreated) = await InternalCreateTriggerAsync(scopeInfo, context, tableBuilder, triggerType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); } @@ -80,7 +80,14 @@ public async Task CreateTriggerAsync(ScopeInfo scopeInfo, string tableName } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + message += $"Trigger:{triggerType}."; + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } } @@ -130,7 +137,13 @@ public async Task CreateTriggersAsync(ScopeInfo scopeInfo, string tableNam } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } } @@ -170,14 +183,20 @@ public async Task ExistTriggerAsync(ScopeInfo scopeInfo, string tableName, var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, + (context, exists) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); return exists; } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + message += $"Trigger:{triggerType}."; + + throw GetSyncError(context, ex, message); } } @@ -218,20 +237,25 @@ public async Task DropTriggerAsync(ScopeInfo scopeInfo, string tableName, var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool existsAndCanBeDeleted; - (context, existsAndCanBeDeleted) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, + (context, existsAndCanBeDeleted) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (existsAndCanBeDeleted) - (context, hasBeenDropped) = await InternalDropTriggerAsync(scopeInfo, context, tableBuilder, triggerType, + (context, hasBeenDropped) = await InternalDropTriggerAsync(scopeInfo, context, tableBuilder, triggerType, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); return hasBeenDropped; } catch (Exception ex) { - throw GetSyncError(context, ex); - } + string message = null; + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + message += $"Trigger:{triggerType}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -270,7 +294,7 @@ public async Task DropTriggersAsync(ScopeInfo scopeInfo, string tableName, var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool dropped; - (context, dropped) = await this.InternalDropTriggersAsync(scopeInfo, context, tableBuilder, + (context, dropped) = await this.InternalDropTriggersAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress); await runner.CommitAsync().ConfigureAwait(false); @@ -279,7 +303,12 @@ public async Task DropTriggersAsync(ScopeInfo scopeInfo, string tableName, } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + var tableFullName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + message += $"Table:{tableFullName}."; + + throw GetSyncError(context, ex, message); } } @@ -289,33 +318,48 @@ public async Task DropTriggersAsync(ScopeInfo scopeInfo, string tableName, internal virtual async Task<(SyncContext context, bool created)> InternalCreateTriggerAsync( ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbTriggerType triggerType, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (tableBuilder.TableDescription.Columns.Count <= 0) - throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); + try + { - if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) - throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); + if (tableBuilder.TableDescription.Columns.Count <= 0) + throw new MissingsColumnException(tableBuilder.TableDescription.GetFullName()); - using var command = await tableBuilder.GetCreateTriggerCommandAsync(triggerType, connection, transaction).ConfigureAwait(false); + if (tableBuilder.TableDescription.PrimaryKeys.Count <= 0) + throw new MissingPrimaryKeyException(tableBuilder.TableDescription.GetFullName()); - if (command == null) - return (context, false); + using var command = await tableBuilder.GetCreateTriggerCommandAsync(triggerType, connection, transaction).ConfigureAwait(false); - var action = await this.InterceptAsync(new TriggerCreatingArgs(context, tableBuilder.TableDescription, triggerType, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + if (command == null) + return (context, false); - if (action.Cancel || action.Command == null) - return (context, false); + var action = await this.InterceptAsync(new TriggerCreatingArgs(context, tableBuilder.TableDescription, triggerType, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - // Parametrized command timeout established if exist - if (Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = Options.DbCommandTimeout.Value; - - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + if (action.Cancel || action.Command == null) + return (context, false); - await action.Command.ExecuteNonQueryAsync(); - await this.InterceptAsync(new TriggerCreatedArgs(context, tableBuilder.TableDescription, triggerType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - action.Command.Dispose(); + // Parametrized command timeout established if exist + if (Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = Options.DbCommandTimeout.Value; - return (context, true); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + + await action.Command.ExecuteNonQueryAsync(); + await this.InterceptAsync(new TriggerCreatedArgs(context, tableBuilder.TableDescription, triggerType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + action.Command.Dispose(); + + return (context, true); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + message += $"Trigger:{triggerType}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -328,28 +372,44 @@ public async Task DropTriggersAsync(ScopeInfo scopeInfo, string tableName, var listTriggerType = Enum.GetValues(typeof(DbTriggerType)); - foreach (DbTriggerType triggerType in listTriggerType) + try { - bool exists; - (context, exists) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - // Drop trigger if already exists - if (exists && overwrite) - (context, _) = await InternalDropTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + foreach (DbTriggerType triggerType in listTriggerType) + { + bool exists; + (context, exists) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + + // Drop trigger if already exists + if (exists && overwrite) + (context, _) = await InternalDropTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - var shouldCreate = !exists || overwrite; + var shouldCreate = !exists || overwrite; - if (!shouldCreate) - continue; + if (!shouldCreate) + continue; - bool hasBeenCreated; - (context, hasBeenCreated) = await InternalCreateTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + bool hasBeenCreated; + (context, hasBeenCreated) = await InternalCreateTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (hasBeenCreated) - hasCreatedAtLeastOneTrigger = true; + if (hasBeenCreated) + hasCreatedAtLeastOneTrigger = true; + } + + return (context, hasCreatedAtLeastOneTrigger); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } - return (context, hasCreatedAtLeastOneTrigger); } /// @@ -358,27 +418,41 @@ public async Task DropTriggersAsync(ScopeInfo scopeInfo, string tableName, internal virtual async Task<(SyncContext context, bool dropped)> InternalDropTriggerAsync( ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbTriggerType triggerType, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - using var command = await tableBuilder.GetDropTriggerCommandAsync(triggerType, connection, transaction).ConfigureAwait(false); + try + { + using var command = await tableBuilder.GetDropTriggerCommandAsync(triggerType, connection, transaction).ConfigureAwait(false); + + if (command == null) + return (context, false); + + var action = await this.InterceptAsync(new TriggerDroppingArgs(context, tableBuilder.TableDescription, triggerType, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - if (command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - var action = await this.InterceptAsync(new TriggerDroppingArgs(context, tableBuilder.TableDescription, triggerType, command, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + // Parametrized command timeout established if exist + if (Options.DbCommandTimeout.HasValue) + action.Command.CommandTimeout = Options.DbCommandTimeout.Value; - if (action.Cancel || action.Command == null) - return (context, false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - // Parametrized command timeout established if exist - if (Options.DbCommandTimeout.HasValue) - action.Command.CommandTimeout = Options.DbCommandTimeout.Value; + await action.Command.ExecuteNonQueryAsync(); + await this.InterceptAsync(new TriggerDroppedArgs(context, tableBuilder.TableDescription, triggerType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + action.Command.Dispose(); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + return (context, true); + } + catch (Exception ex) + { + string message = null; + + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; - await action.Command.ExecuteNonQueryAsync(); - await this.InterceptAsync(new TriggerDroppedArgs(context, tableBuilder.TableDescription, triggerType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - action.Command.Dispose(); + message += $"Trigger:{triggerType}."; - return (context, true); + throw GetSyncError(context, ex, message); + } } /// @@ -391,22 +465,33 @@ public async Task DropTriggersAsync(ScopeInfo scopeInfo, string tableName, var listTriggerType = Enum.GetValues(typeof(DbTriggerType)); - foreach (DbTriggerType triggerType in listTriggerType) + try { - bool exists; - (context, exists) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + foreach (DbTriggerType triggerType in listTriggerType) + { + bool exists; + (context, exists) = await InternalExistsTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (!exists) - continue; - bool dropped; - (context, dropped) = await InternalDropTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + if (!exists) + continue; + bool dropped; + (context, dropped) = await InternalDropTriggerAsync(scopeInfo, context, tableBuilder, triggerType, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + + if (dropped) + hasDroppeAtLeastOneTrigger = true; + } - if (dropped) - hasDroppeAtLeastOneTrigger = true; + return (context, hasDroppeAtLeastOneTrigger); } + catch (Exception ex) + { + string message = null; - return (context, hasDroppeAtLeastOneTrigger); + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + throw GetSyncError(context, ex, message); + } } /// @@ -415,23 +500,35 @@ public async Task DropTriggersAsync(ScopeInfo scopeInfo, string tableName, internal virtual async Task<(SyncContext context, bool exists)> InternalExistsTriggerAsync( ScopeInfo scopeInfo, SyncContext context, DbTableBuilder tableBuilder, DbTriggerType triggerType, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - // Get exists command - using var existsCommand = await tableBuilder.GetExistsTriggerCommandAsync(triggerType, connection, transaction).ConfigureAwait(false); + try + { + // Get exists command + using var existsCommand = await tableBuilder.GetExistsTriggerCommandAsync(triggerType, connection, transaction).ConfigureAwait(false); - if (existsCommand == null) - return (context, false); + if (existsCommand == null) + return (context, false); - // Parametrized command timeout established if exist - if (Options.DbCommandTimeout.HasValue) - existsCommand.CommandTimeout = Options.DbCommandTimeout.Value; - - await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + // Parametrized command timeout established if exist + if (Options.DbCommandTimeout.HasValue) + existsCommand.CommandTimeout = Options.DbCommandTimeout.Value; - var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); - var exists = Convert.ToInt32(existsResultObject) > 0; - return (context, exists); + await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - } + var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); + var exists = Convert.ToInt32(existsResultObject) > 0; + return (context, exists); + } + catch (Exception ex) + { + string message = null; + if (tableBuilder != null && tableBuilder.TableDescription != null) + message += $"Table:{tableBuilder.TableDescription.GetFullName()}."; + + message += $"Trigger:{triggerType}."; + + throw GetSyncError(context, ex, message); + } + } } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/DbConnectionRunner.cs b/Projects/Dotmim.Sync.Core/Orchestrators/DbConnectionRunner.cs index 272a56373..f97c1159c 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/DbConnectionRunner.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/DbConnectionRunner.cs @@ -23,30 +23,39 @@ public static async Task GetConnectionAsync(this BaseOrchest CancellationToken cancellationToken = default, IProgress progress = default) { - // Get context or create a new one - context.SyncStage = syncStage; + try + { + + // Get context or create a new one + context.SyncStage = syncStage; + + if (orchestrator.Provider == null) + return new DbConnectionRunner(null, context, null, null, true, true, cancellationToken, progress); - if (orchestrator.Provider == null) - return new DbConnectionRunner(null, context, null, null, true, true, cancellationToken, progress); + if (connection == null) + connection = orchestrator.Provider.CreateConnection(); - if (connection == null) - connection = orchestrator.Provider.CreateConnection(); + var alreadyOpened = connection.State == ConnectionState.Open; + var alreadyInTransaction = transaction != null && transaction.Connection == connection; - var alreadyOpened = connection.State == ConnectionState.Open; - var alreadyInTransaction = transaction != null && transaction.Connection == connection; + // Open connection + if (!alreadyOpened) + await orchestrator.OpenConnectionAsync(context, connection, cancellationToken, progress).ConfigureAwait(false); - // Open connection - if (!alreadyOpened) - await orchestrator.OpenConnectionAsync(context, connection, cancellationToken, progress).ConfigureAwait(false); + // Create a transaction + if (!alreadyInTransaction && syncMode == SyncMode.WithTransaction) + { + transaction = connection.BeginTransaction(orchestrator.Provider.IsolationLevel); + await orchestrator.InterceptAsync(new TransactionOpenedArgs(context, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + } - // Create a transaction - if (!alreadyInTransaction && syncMode == SyncMode.WithTransaction) + return new DbConnectionRunner(orchestrator, context, connection, transaction, alreadyOpened, alreadyInTransaction, cancellationToken, progress); + } + catch (Exception ex) { - transaction = connection.BeginTransaction(orchestrator.Provider.IsolationLevel); - await orchestrator.InterceptAsync(new TransactionOpenedArgs(context, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + throw orchestrator.GetSyncError(context, ex); } - return new DbConnectionRunner(orchestrator, context, connection, transaction, alreadyOpened, alreadyInTransaction, cancellationToken, progress); } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/BaseOrchestrator.GetChanges.cs b/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/BaseOrchestrator.GetChanges.cs index 89b2e0d64..466afe314 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/BaseOrchestrator.GetChanges.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/BaseOrchestrator.GetChanges.cs @@ -30,106 +30,120 @@ internal virtual async Task InternalGetChangesAsync( DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { + try + { + // Statistics about changes that are selected + DatabaseChangesSelected changesSelected; - // Statistics about changes that are selected - DatabaseChangesSelected changesSelected; - - context.SyncStage = SyncStage.ChangesSelecting; + context.SyncStage = SyncStage.ChangesSelecting; - // Create a new empty in-memory batch info - if (context.SyncWay == SyncWay.Upload && context.SyncType == SyncType.Reinitialize) - return new DatabaseChangesSelected(); + // Create a new empty in-memory batch info + if (context.SyncWay == SyncWay.Upload && context.SyncType == SyncType.Reinitialize) + return new DatabaseChangesSelected(); - // create local directory - if (!string.IsNullOrEmpty(batchInfo.DirectoryRoot) && !Directory.Exists(batchInfo.DirectoryRoot)) - Directory.CreateDirectory(batchInfo.DirectoryRoot); + // create local directory + if (!string.IsNullOrEmpty(batchInfo.DirectoryRoot) && !Directory.Exists(batchInfo.DirectoryRoot)) + Directory.CreateDirectory(batchInfo.DirectoryRoot); - changesSelected = new DatabaseChangesSelected(); + changesSelected = new DatabaseChangesSelected(); - var cptSyncTable = 0; - var currentProgress = context.ProgressPercentage; + var cptSyncTable = 0; + var currentProgress = context.ProgressPercentage; - var schemaTables = scopeInfo.Schema.Tables.SortByDependencies(tab => tab.GetRelations().Select(r => r.GetParentTable())); + var schemaTables = scopeInfo.Schema.Tables.SortByDependencies(tab => tab.GetRelations().Select(r => r.GetParentTable())); - var lstAllBatchPartInfos = new ConcurrentBag(); - var lstTableChangesSelected = new ConcurrentBag(); + var lstAllBatchPartInfos = new ConcurrentBag(); + var lstTableChangesSelected = new ConcurrentBag(); - var threadNumberLimits = supportsMultiActiveResultSets ? 16 : 1; + var threadNumberLimits = supportsMultiActiveResultSets ? 16 : 1; - if (supportsMultiActiveResultSets) - { - await schemaTables.ForEachAsync(async syncTable => + if (supportsMultiActiveResultSets) { - if (cancellationToken.IsCancellationRequested) - return; + await schemaTables.ForEachAsync(async syncTable => + { + if (cancellationToken.IsCancellationRequested) + return; - // tmp count of table for report progress pct - cptSyncTable++; + // tmp count of table for report progress pct + cptSyncTable++; - List syncTableBatchPartInfos; - TableChangesSelected tableChangesSelected; - (context, syncTableBatchPartInfos, tableChangesSelected) = await InternalReadSyncTableChangesAsync( - scopeInfo, context, excludingScopeId, syncTable, batchInfo, isNew, fromLastTimestamp, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + List syncTableBatchPartInfos; + TableChangesSelected tableChangesSelected; + (context, syncTableBatchPartInfos, tableChangesSelected) = await InternalReadSyncTableChangesAsync( + scopeInfo, context, excludingScopeId, syncTable, batchInfo, isNew, fromLastTimestamp, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (syncTableBatchPartInfos == null) - return; + if (syncTableBatchPartInfos == null) + return; - // We don't report progress if no table changes is empty, to limit verbosity - if (tableChangesSelected != null && (tableChangesSelected.Deletes > 0 || tableChangesSelected.Upserts > 0)) - lstTableChangesSelected.Add(tableChangesSelected); + // We don't report progress if no table changes is empty, to limit verbosity + if (tableChangesSelected != null && (tableChangesSelected.Deletes > 0 || tableChangesSelected.Upserts > 0)) + lstTableChangesSelected.Add(tableChangesSelected); - // Add sync table bpi to all bpi - syncTableBatchPartInfos.ForEach(bpi => lstAllBatchPartInfos.Add(bpi)); + // Add sync table bpi to all bpi + syncTableBatchPartInfos.ForEach(bpi => lstAllBatchPartInfos.Add(bpi)); - context.ProgressPercentage = currentProgress + (cptSyncTable * 0.2d / scopeInfo.Schema.Tables.Count); + context.ProgressPercentage = currentProgress + (cptSyncTable * 0.2d / scopeInfo.Schema.Tables.Count); - }, threadNumberLimits); - } - else - { - foreach (var syncTable in schemaTables) + }, threadNumberLimits); + } + else { - if (cancellationToken.IsCancellationRequested) - continue; + foreach (var syncTable in schemaTables) + { + if (cancellationToken.IsCancellationRequested) + continue; - // tmp count of table for report progress pct - cptSyncTable++; + // tmp count of table for report progress pct + cptSyncTable++; - List syncTableBatchPartInfos; - TableChangesSelected tableChangesSelected; - (context, syncTableBatchPartInfos, tableChangesSelected) = await InternalReadSyncTableChangesAsync( - scopeInfo, context, excludingScopeId, syncTable, batchInfo, isNew, fromLastTimestamp, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + List syncTableBatchPartInfos; + TableChangesSelected tableChangesSelected; + (context, syncTableBatchPartInfos, tableChangesSelected) = await InternalReadSyncTableChangesAsync( + scopeInfo, context, excludingScopeId, syncTable, batchInfo, isNew, fromLastTimestamp, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (syncTableBatchPartInfos == null) - continue; + if (syncTableBatchPartInfos == null) + continue; - // We don't report progress if no table changes is empty, to limit verbosity - if (tableChangesSelected != null && (tableChangesSelected.Deletes > 0 || tableChangesSelected.Upserts > 0)) - lstTableChangesSelected.Add(tableChangesSelected); + // We don't report progress if no table changes is empty, to limit verbosity + if (tableChangesSelected != null && (tableChangesSelected.Deletes > 0 || tableChangesSelected.Upserts > 0)) + lstTableChangesSelected.Add(tableChangesSelected); - // Add sync table bpi to all bpi - syncTableBatchPartInfos.ForEach(bpi => lstAllBatchPartInfos.Add(bpi)); + // Add sync table bpi to all bpi + syncTableBatchPartInfos.ForEach(bpi => lstAllBatchPartInfos.Add(bpi)); - context.ProgressPercentage = currentProgress + (cptSyncTable * 0.2d / scopeInfo.Schema.Tables.Count); + context.ProgressPercentage = currentProgress + (cptSyncTable * 0.2d / scopeInfo.Schema.Tables.Count); + } } - } - while (!lstTableChangesSelected.IsEmpty) - if (lstTableChangesSelected.TryTake(out var tableChangesSelected)) - changesSelected.TableChangesSelected.Add(tableChangesSelected); + while (!lstTableChangesSelected.IsEmpty) + if (lstTableChangesSelected.TryTake(out var tableChangesSelected)) + changesSelected.TableChangesSelected.Add(tableChangesSelected); - // Ensure correct order - this.EnsureLastBatchInfo(scopeInfo, context, batchInfo, lstAllBatchPartInfos, schemaTables); + // Ensure correct order + this.EnsureLastBatchInfo(scopeInfo, context, batchInfo, lstAllBatchPartInfos, schemaTables); - if (batchInfo.RowsCount <= 0) - { - var cleanFolder = await this.InternalCanCleanFolderAsync(scopeInfo.Name, context.Parameters, batchInfo).ConfigureAwait(false); - if (cleanFolder) - batchInfo.TryRemoveDirectory(); + if (batchInfo.RowsCount <= 0) + { + var cleanFolder = await this.InternalCanCleanFolderAsync(scopeInfo.Name, context.Parameters, batchInfo).ConfigureAwait(false); + if (cleanFolder) + batchInfo.TryRemoveDirectory(); + } + return changesSelected; } - return changesSelected; + catch (Exception ex) + { + string message = null; + if (batchInfo != null && batchInfo.DirectoryRoot != null) + message += $"Directory:{batchInfo.DirectoryRoot}."; + + message += $"Supports MultiActiveResultSets:{supportsMultiActiveResultSets}."; + message += $"Is New:{isNew}."; + message += $"Interval:{fromLastTimestamp}/{toNewTimestamp}."; + + throw GetSyncError(context, ex, message); + } } @@ -143,200 +157,222 @@ await schemaTables.ForEachAsync(async syncTable => if (cancellationToken.IsCancellationRequested) return default; - var setupTable = scopeInfo.Setup.Tables[syncTable.TableName, syncTable.SchemaName]; + try + { + var setupTable = scopeInfo.Setup.Tables[syncTable.TableName, syncTable.SchemaName]; - if (setupTable == null) - return (context, default, default); + if (setupTable == null) + return (context, default, default); - // Only table schema is replicated, no datas are applied - if (setupTable.SyncDirection == SyncDirection.None) - return (context, default, default); + // Only table schema is replicated, no datas are applied + if (setupTable.SyncDirection == SyncDirection.None) + return (context, default, default); - // if we are in upload stage, so check if table is not download only - if (context.SyncWay == SyncWay.Upload && setupTable.SyncDirection == SyncDirection.DownloadOnly) - return (context, default, default); + // if we are in upload stage, so check if table is not download only + if (context.SyncWay == SyncWay.Upload && setupTable.SyncDirection == SyncDirection.DownloadOnly) + return (context, default, default); - // if we are in download stage, so check if table is not download only - if (context.SyncWay == SyncWay.Download && setupTable.SyncDirection == SyncDirection.UploadOnly) - return (context, default, default); + // if we are in download stage, so check if table is not download only + if (context.SyncWay == SyncWay.Download && setupTable.SyncDirection == SyncDirection.UploadOnly) + return (context, default, default); - var (selectIncrementalChangesCommand, dbCommandType) = await this.InternalGetSelectChangesCommandAsync(scopeInfo, context, syncTable, isNew, - connection, transaction); + var (selectIncrementalChangesCommand, dbCommandType) = await this.InternalGetSelectChangesCommandAsync(scopeInfo, context, syncTable, isNew, + connection, transaction); - if (selectIncrementalChangesCommand == null) - return (context, default, default); + if (selectIncrementalChangesCommand == null) + return (context, default, default); - this.InternalSetSelectChangesCommonParameters(context, syncTable, excludintScopeId, isNew, lastTimestamp, selectIncrementalChangesCommand); + this.InternalSetSelectChangesCommonParameters(context, syncTable, excludintScopeId, isNew, lastTimestamp, selectIncrementalChangesCommand); - var schemaChangesTable = CreateChangesTable(syncTable); + var schemaChangesTable = CreateChangesTable(syncTable); - // numbers of batch files generated - var batchIndex = -1; + // numbers of batch files generated + var batchIndex = -1; - var localSerializerModified = new LocalJsonSerializer(); - var localSerializerDeleted = new LocalJsonSerializer(); + var localSerializerModified = new LocalJsonSerializer(); + var localSerializerDeleted = new LocalJsonSerializer(); - var interceptorsWriting = this.interceptors.GetInterceptors(); - if (interceptorsWriting.Count > 0) - { - localSerializerModified.OnWritingRow(async (syncTable, rowArray) => + var interceptorsWriting = this.interceptors.GetInterceptors(); + if (interceptorsWriting.Count > 0) { - var copyArray = new object[rowArray.Length]; - Array.Copy(rowArray, copyArray, rowArray.Length); - - var args = new SerializingRowArgs(context, syncTable, copyArray); - await this.InterceptAsync(args, progress, cancellationToken).ConfigureAwait(false); - return args.Result; - }); - } - - string batchPartInfoFullPathModified = null, batchPartFileNameModified = null; - string batchPartInfoFullPathDeleted = null, batchPartFileNameDeleted = null; - - // Statistics - var tableChangesSelected = new TableChangesSelected(schemaChangesTable.TableName, schemaChangesTable.SchemaName); + localSerializerModified.OnWritingRow(async (syncTable, rowArray) => + { + var copyArray = new object[rowArray.Length]; + Array.Copy(rowArray, copyArray, rowArray.Length); - var rowsCountInBatchModified = 0; - var rowsCountInBatchDeleted = 0; + var args = new SerializingRowArgs(context, syncTable, copyArray); + await this.InterceptAsync(args, progress, cancellationToken).ConfigureAwait(false); + return args.Result; + }); + } - var syncTableBatchPartInfos = new List(); + string batchPartInfoFullPathModified = null, batchPartFileNameModified = null; + string batchPartInfoFullPathDeleted = null, batchPartFileNameDeleted = null; - // launch interceptor if any - var args = await this.InterceptAsync(new TableChangesSelectingArgs(context, schemaChangesTable, selectIncrementalChangesCommand, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + // Statistics + var tableChangesSelected = new TableChangesSelected(schemaChangesTable.TableName, schemaChangesTable.SchemaName); - if (!args.Cancel && args.Command != null) - { - // Parametrized command timeout established if exist - if (Options.DbCommandTimeout.HasValue) - args.Command.CommandTimeout = Options.DbCommandTimeout.Value; + var rowsCountInBatchModified = 0; + var rowsCountInBatchDeleted = 0; - await this.InterceptAsync(new ExecuteCommandArgs(context, args.Command, dbCommandType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + var syncTableBatchPartInfos = new List(); - // Get the reader - using var dataReader = await args.Command.ExecuteReaderAsync().ConfigureAwait(false); + // launch interceptor if any + var args = await this.InterceptAsync(new TableChangesSelectingArgs(context, schemaChangesTable, selectIncrementalChangesCommand, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - while (dataReader.Read()) + if (!args.Cancel && args.Command != null) { - // Create a row from dataReader - var syncRow = CreateSyncRowFromReader2(dataReader, schemaChangesTable); + // Parametrized command timeout established if exist + if (Options.DbCommandTimeout.HasValue) + args.Command.CommandTimeout = Options.DbCommandTimeout.Value; - var tableChangesSelectedSyncRowArgs = await this.InterceptAsync(new RowsChangesSelectedArgs(context, syncRow, schemaChangesTable, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - syncRow = tableChangesSelectedSyncRowArgs.SyncRow; + await this.InterceptAsync(new ExecuteCommandArgs(context, args.Command, dbCommandType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - if (syncRow == null) - continue; + // Get the reader + using var dataReader = await args.Command.ExecuteReaderAsync().ConfigureAwait(false); - // Set the correct state to be applied - if (syncRow.RowState == SyncRowState.Deleted) + while (dataReader.Read()) { - // open the file and write table header for all deleted rows - if (!localSerializerDeleted.IsOpen) - { - batchIndex++; - (batchPartInfoFullPathDeleted, batchPartFileNameDeleted) = batchInfo.GetNewBatchPartInfoPath(schemaChangesTable, batchIndex, localSerializerDeleted.Extension, "DELETED"); - await localSerializerDeleted.OpenFileAsync(batchPartInfoFullPathDeleted, schemaChangesTable).ConfigureAwait(false); - } - - tableChangesSelected.Deletes++; - rowsCountInBatchDeleted++; - await localSerializerDeleted.WriteRowToFileAsync(syncRow, schemaChangesTable).ConfigureAwait(false); - var currentBatchSizeDeleted = await localSerializerDeleted.GetCurrentFileSizeAsync().ConfigureAwait(false); + // Create a row from dataReader + var syncRow = CreateSyncRowFromReader2(context, dataReader, schemaChangesTable); - if (currentBatchSizeDeleted > this.Options.BatchSize) - { - //Add a new batch part info with deleted rows - batchIndex++; - var bpiDeleted = new BatchPartInfo(batchPartFileNameDeleted, tableChangesSelected.TableName, tableChangesSelected.SchemaName, rowsCountInBatchDeleted, batchIndex); - syncTableBatchPartInfos.Add(bpiDeleted); + var tableChangesSelectedSyncRowArgs = await this.InterceptAsync(new RowsChangesSelectedArgs(context, syncRow, schemaChangesTable, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + syncRow = tableChangesSelectedSyncRowArgs.SyncRow; - // Close file - if (localSerializerDeleted.IsOpen) - await localSerializerDeleted.CloseFileAsync().ConfigureAwait(false); + if (syncRow == null) + continue; - rowsCountInBatchDeleted = 0; + // Set the correct state to be applied + if (syncRow.RowState == SyncRowState.Deleted) + { + // open the file and write table header for all deleted rows + if (!localSerializerDeleted.IsOpen) + { + batchIndex++; + (batchPartInfoFullPathDeleted, batchPartFileNameDeleted) = batchInfo.GetNewBatchPartInfoPath(schemaChangesTable, batchIndex, localSerializerDeleted.Extension, "DELETED"); + await localSerializerDeleted.OpenFileAsync(batchPartInfoFullPathDeleted, schemaChangesTable).ConfigureAwait(false); + } + + tableChangesSelected.Deletes++; + rowsCountInBatchDeleted++; + await localSerializerDeleted.WriteRowToFileAsync(syncRow, schemaChangesTable).ConfigureAwait(false); + var currentBatchSizeDeleted = await localSerializerDeleted.GetCurrentFileSizeAsync().ConfigureAwait(false); + + if (currentBatchSizeDeleted > this.Options.BatchSize) + { + //Add a new batch part info with deleted rows + batchIndex++; + var bpiDeleted = new BatchPartInfo(batchPartFileNameDeleted, tableChangesSelected.TableName, tableChangesSelected.SchemaName, rowsCountInBatchDeleted, batchIndex); + syncTableBatchPartInfos.Add(bpiDeleted); + + // Close file + if (localSerializerDeleted.IsOpen) + await localSerializerDeleted.CloseFileAsync().ConfigureAwait(false); + + rowsCountInBatchDeleted = 0; + + (batchPartInfoFullPathDeleted, batchPartFileNameDeleted) = batchInfo.GetNewBatchPartInfoPath(schemaChangesTable, batchIndex, localSerializerDeleted.Extension, "DELETED"); + } - (batchPartInfoFullPathDeleted, batchPartFileNameDeleted) = batchInfo.GetNewBatchPartInfoPath(schemaChangesTable, batchIndex, localSerializerDeleted.Extension, "DELETED"); } - - } - else if (syncRow.RowState == SyncRowState.Modified) - { - // open the file and write table header for all Inserted / Modified rows - if (!localSerializerModified.IsOpen) + else if (syncRow.RowState == SyncRowState.Modified) { - batchIndex++; - (batchPartInfoFullPathModified, batchPartFileNameModified) = batchInfo.GetNewBatchPartInfoPath(schemaChangesTable, batchIndex, localSerializerModified.Extension, "UPSERTS"); - await localSerializerModified.OpenFileAsync(batchPartInfoFullPathModified, schemaChangesTable).ConfigureAwait(false); + // open the file and write table header for all Inserted / Modified rows + if (!localSerializerModified.IsOpen) + { + batchIndex++; + (batchPartInfoFullPathModified, batchPartFileNameModified) = batchInfo.GetNewBatchPartInfoPath(schemaChangesTable, batchIndex, localSerializerModified.Extension, "UPSERTS"); + await localSerializerModified.OpenFileAsync(batchPartInfoFullPathModified, schemaChangesTable).ConfigureAwait(false); + } + + rowsCountInBatchModified++; + tableChangesSelected.Upserts++; + await localSerializerModified.WriteRowToFileAsync(syncRow, schemaChangesTable).ConfigureAwait(false); + var currentBatchSizeModified = await localSerializerModified.GetCurrentFileSizeAsync().ConfigureAwait(false); + + if (currentBatchSizeModified > this.Options.BatchSize) + { + //Add a new batch part info with modified rows + batchIndex++; + var bpiModified = new BatchPartInfo(batchPartFileNameModified, tableChangesSelected.TableName, tableChangesSelected.SchemaName, rowsCountInBatchModified, batchIndex); + syncTableBatchPartInfos.Add(bpiModified); + + // Close file + if (localSerializerModified.IsOpen) + await localSerializerModified.CloseFileAsync().ConfigureAwait(false); + + rowsCountInBatchModified = 0; + + (batchPartInfoFullPathModified, batchPartFileNameModified) = batchInfo.GetNewBatchPartInfoPath(schemaChangesTable, batchIndex, localSerializerModified.Extension, "UPSERTS"); + } } + } - rowsCountInBatchModified++; - tableChangesSelected.Upserts++; - await localSerializerModified.WriteRowToFileAsync(syncRow, schemaChangesTable).ConfigureAwait(false); - var currentBatchSizeModified = await localSerializerModified.GetCurrentFileSizeAsync().ConfigureAwait(false); - - if (currentBatchSizeModified > this.Options.BatchSize) - { - //Add a new batch part info with modified rows - batchIndex++; - var bpiModified = new BatchPartInfo(batchPartFileNameModified, tableChangesSelected.TableName, tableChangesSelected.SchemaName, rowsCountInBatchModified, batchIndex); - syncTableBatchPartInfos.Add(bpiModified); + dataReader.Close(); - // Close file - if (localSerializerModified.IsOpen) - await localSerializerModified.CloseFileAsync().ConfigureAwait(false); + // Close file + if (localSerializerModified.IsOpen) + await localSerializerModified.CloseFileAsync().ConfigureAwait(false); - rowsCountInBatchModified = 0; + if (localSerializerDeleted.IsOpen) + await localSerializerDeleted.CloseFileAsync().ConfigureAwait(false); + } - (batchPartInfoFullPathModified, batchPartFileNameModified) = batchInfo.GetNewBatchPartInfoPath(schemaChangesTable, batchIndex, localSerializerModified.Extension, "UPSERTS"); - } - } + // Check if we have ..something. + // Delete folder if nothing + // Add the BPI to BI if something + if (rowsCountInBatchModified == 0) + { + if (!string.IsNullOrEmpty(batchPartInfoFullPathModified) && File.Exists(batchPartInfoFullPathModified)) + File.Delete(batchPartInfoFullPathModified); + } + else + { + var bpi2 = new BatchPartInfo(batchPartFileNameModified, tableChangesSelected.TableName, tableChangesSelected.SchemaName, rowsCountInBatchModified, batchIndex); + bpi2.IsLastBatch = true; + syncTableBatchPartInfos.Add(bpi2); } - dataReader.Close(); + if (rowsCountInBatchDeleted == 0) + { + if (!string.IsNullOrEmpty(batchPartInfoFullPathDeleted) && File.Exists(batchPartInfoFullPathDeleted)) + File.Delete(batchPartInfoFullPathDeleted); + } + else + { + var bpi2 = new BatchPartInfo(batchPartFileNameDeleted, tableChangesSelected.TableName, tableChangesSelected.SchemaName, rowsCountInBatchDeleted, batchIndex) + { + IsLastBatch = true + }; - // Close file - if (localSerializerModified.IsOpen) - await localSerializerModified.CloseFileAsync().ConfigureAwait(false); + syncTableBatchPartInfos.Add(bpi2); + } - if (localSerializerDeleted.IsOpen) - await localSerializerDeleted.CloseFileAsync().ConfigureAwait(false); - } + // even if no rows raise the interceptor + var tableChangesSelectedArgs = new TableChangesSelectedArgs(context, batchInfo, syncTableBatchPartInfos, syncTable, tableChangesSelected, connection, transaction); + await this.InterceptAsync(tableChangesSelectedArgs, progress, cancellationToken).ConfigureAwait(false); - // Check if we have ..something. - // Delete folder if nothing - // Add the BPI to BI if something - if (rowsCountInBatchModified == 0) - { - if (!string.IsNullOrEmpty(batchPartInfoFullPathModified) && File.Exists(batchPartInfoFullPathModified)) - File.Delete(batchPartInfoFullPathModified); + return (context, syncTableBatchPartInfos, tableChangesSelected); } - else + catch (Exception ex) { - var bpi2 = new BatchPartInfo(batchPartFileNameModified, tableChangesSelected.TableName, tableChangesSelected.SchemaName, rowsCountInBatchModified, batchIndex); - bpi2.IsLastBatch = true; - syncTableBatchPartInfos.Add(bpi2); - } + string message = null; - if (rowsCountInBatchDeleted == 0) - { - if (!string.IsNullOrEmpty(batchPartInfoFullPathDeleted) && File.Exists(batchPartInfoFullPathDeleted)) - File.Delete(batchPartInfoFullPathDeleted); - } - else - { - var bpi2 = new BatchPartInfo(batchPartFileNameDeleted, tableChangesSelected.TableName, tableChangesSelected.SchemaName, rowsCountInBatchDeleted, batchIndex) - { - IsLastBatch = true - }; + if (batchInfo != null && batchInfo.DirectoryRoot != null) + message += $"Directory:{batchInfo.DirectoryRoot}."; - syncTableBatchPartInfos.Add(bpi2); - } + if (batchInfo != null && batchInfo.DirectoryName!= null) + message += $"Folder:{batchInfo.DirectoryName}."; + + if (syncTable != null) + message += $"Table:{syncTable.GetFullName()}."; - // even if no rows raise the interceptor - var tableChangesSelectedArgs = new TableChangesSelectedArgs(context, batchInfo, syncTableBatchPartInfos, syncTable, tableChangesSelected, connection, transaction); - await this.InterceptAsync(tableChangesSelectedArgs, progress, cancellationToken).ConfigureAwait(false); + message += $"Is New:{isNew}."; - return (context, syncTableBatchPartInfos, tableChangesSelected); + message += $"LastTimestamp:{lastTimestamp}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -348,107 +384,119 @@ await schemaTables.ForEachAsync(async syncTable => DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { + try + { + context.SyncStage = SyncStage.ChangesSelecting; - context.SyncStage = SyncStage.ChangesSelecting; - - // Create stats object to store changes count - var changes = new DatabaseChangesSelected(); + // Create stats object to store changes count + var changes = new DatabaseChangesSelected(); - // Call interceptor - var databaseChangesSelectingArgs = new DatabaseChangesSelectingArgs(context, default, this.Options.BatchSize, true, - fromLastTimestamp, toLastTimestamp, connection, transaction); + // Call interceptor + var databaseChangesSelectingArgs = new DatabaseChangesSelectingArgs(context, default, this.Options.BatchSize, true, + fromLastTimestamp, toLastTimestamp, connection, transaction); - await this.InterceptAsync(databaseChangesSelectingArgs, progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(databaseChangesSelectingArgs, progress, cancellationToken).ConfigureAwait(false); - if (context.SyncWay == SyncWay.Upload && context.SyncType == SyncType.Reinitialize) - return (context, changes); + if (context.SyncWay == SyncWay.Upload && context.SyncType == SyncType.Reinitialize) + return (context, changes); - var threadNumberLimits = supportsMultiActiveResultSets ? 8 : 1; + var threadNumberLimits = supportsMultiActiveResultSets ? 8 : 1; - await scopeInfo.Schema.Tables.ForEachAsync(async syncTable => - { - if (cancellationToken.IsCancellationRequested) - return; + await scopeInfo.Schema.Tables.ForEachAsync(async syncTable => + { + if (cancellationToken.IsCancellationRequested) + return; - var setupTable = scopeInfo.Setup.Tables[syncTable.TableName, syncTable.SchemaName]; + var setupTable = scopeInfo.Setup.Tables[syncTable.TableName, syncTable.SchemaName]; - if (setupTable == null) - return; + if (setupTable == null) + return; - // Only table schema is replicated, no datas are applied - if (setupTable.SyncDirection == SyncDirection.None) - return; + // Only table schema is replicated, no datas are applied + if (setupTable.SyncDirection == SyncDirection.None) + return; - // if we are in upload stage, so check if table is not download only - if (context.SyncWay == SyncWay.Upload && setupTable.SyncDirection == SyncDirection.DownloadOnly) - return; + // if we are in upload stage, so check if table is not download only + if (context.SyncWay == SyncWay.Upload && setupTable.SyncDirection == SyncDirection.DownloadOnly) + return; - // if we are in download stage, so check if table is not download only - if (context.SyncWay == SyncWay.Download && setupTable.SyncDirection == SyncDirection.UploadOnly) - return; + // if we are in download stage, so check if table is not download only + if (context.SyncWay == SyncWay.Download && setupTable.SyncDirection == SyncDirection.UploadOnly) + return; - // Get Command - var (command, dbCommandType) = await this.InternalGetSelectChangesCommandAsync(scopeInfo, context, syncTable, isNew, connection, transaction); + // Get Command + var (command, dbCommandType) = await this.InternalGetSelectChangesCommandAsync(scopeInfo, context, syncTable, isNew, connection, transaction); - if (command == null) return; + if (command == null) return; - this.InternalSetSelectChangesCommonParameters(context, syncTable, excludingScopeId, isNew, fromLastTimestamp, command); + this.InternalSetSelectChangesCommonParameters(context, syncTable, excludingScopeId, isNew, fromLastTimestamp, command); - // launch interceptor if any - var args = new TableChangesSelectingArgs(context, syncTable, command, connection, transaction); - await this.InterceptAsync(args, progress, cancellationToken).ConfigureAwait(false); + // launch interceptor if any + var args = new TableChangesSelectingArgs(context, syncTable, command, connection, transaction); + await this.InterceptAsync(args, progress, cancellationToken).ConfigureAwait(false); - if (args.Cancel || args.Command == null) - return; + if (args.Cancel || args.Command == null) + return; - // Statistics - var tableChangesSelected = new TableChangesSelected(syncTable.TableName, syncTable.SchemaName); + // Statistics + var tableChangesSelected = new TableChangesSelected(syncTable.TableName, syncTable.SchemaName); - // Parametrized command timeout established if exist - if (this.Options.DbCommandTimeout.HasValue) - command.CommandTimeout = this.Options.DbCommandTimeout.Value; + // Parametrized command timeout established if exist + if (this.Options.DbCommandTimeout.HasValue) + command.CommandTimeout = this.Options.DbCommandTimeout.Value; - await this.InterceptAsync(new ExecuteCommandArgs(context, args.Command, dbCommandType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - // Get the reader - using var dataReader = await args.Command.ExecuteReaderAsync().ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, args.Command, dbCommandType, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + // Get the reader + using var dataReader = await args.Command.ExecuteReaderAsync().ConfigureAwait(false); - while (dataReader.Read()) - { - bool isTombstone = false; - for (var i = 0; i < dataReader.FieldCount; i++) + while (dataReader.Read()) { - if (dataReader.GetName(i) == "sync_row_is_tombstone") + bool isTombstone = false; + for (var i = 0; i < dataReader.FieldCount; i++) { - isTombstone = Convert.ToInt64(dataReader.GetValue(i)) > 0; - break; + if (dataReader.GetName(i) == "sync_row_is_tombstone") + { + isTombstone = Convert.ToInt64(dataReader.GetValue(i)) > 0; + break; + } } + + // Set the correct state to be applied + if (isTombstone) + tableChangesSelected.Deletes++; + else + tableChangesSelected.Upserts++; } - // Set the correct state to be applied - if (isTombstone) - tableChangesSelected.Deletes++; - else - tableChangesSelected.Upserts++; - } + dataReader.Close(); - dataReader.Close(); + // Check interceptor + var changesArgs = new TableChangesSelectedArgs(context, null, null, syncTable, tableChangesSelected, connection, transaction); + await this.InterceptAsync(changesArgs, progress, cancellationToken).ConfigureAwait(false); - // Check interceptor - var changesArgs = new TableChangesSelectedArgs(context, null, null, syncTable, tableChangesSelected, connection, transaction); - await this.InterceptAsync(changesArgs, progress, cancellationToken).ConfigureAwait(false); + if (tableChangesSelected.Deletes > 0 || tableChangesSelected.Upserts > 0) + changes.TableChangesSelected.Add(tableChangesSelected); + + }, threadNumberLimits); - if (tableChangesSelected.Deletes > 0 || tableChangesSelected.Upserts > 0) - changes.TableChangesSelected.Add(tableChangesSelected); + var databaseChangesSelectedArgs = new DatabaseChangesSelectedArgs(context, fromLastTimestamp, toLastTimestamp, + default, changes, connection, transaction); - }, threadNumberLimits); + await this.InterceptAsync(databaseChangesSelectedArgs, progress, cancellationToken).ConfigureAwait(false); - var databaseChangesSelectedArgs = new DatabaseChangesSelectedArgs(context, fromLastTimestamp, toLastTimestamp, - default, changes, connection, transaction); + return (context, changes); + } + catch (Exception ex) + { + string message = null; - await this.InterceptAsync(databaseChangesSelectedArgs, progress, cancellationToken).ConfigureAwait(false); + message += $"Supports MultiActiveResultSets:{supportsMultiActiveResultSets}."; + message += $"Is New:{isNew}."; + message += $"Interval:{fromLastTimestamp}/{toLastTimestamp}."; - return (context, changes); + throw GetSyncError(context, ex, message); + } } @@ -463,36 +511,49 @@ await scopeInfo.Schema.Tables.ForEachAsync(async syncTable => internal async Task<(DbCommand, DbCommandType)> InternalGetSelectChangesCommandAsync(ScopeInfo scopeInfo, SyncContext context, SyncTable syncTable, bool isNew, DbConnection connection, DbTransaction transaction) { - DbCommandType dbCommandType; - + DbCommandType dbCommandType = DbCommandType.None; SyncFilter tableFilter = null; + try + { + // Sqlite does not have any filter, since he can't be server side + if (this.Provider != null && this.Provider.CanBeServerProvider) + tableFilter = syncTable.GetFilter(); + + var hasFilters = tableFilter != null; + + // Determing the correct DbCommandType + if (isNew && hasFilters) + dbCommandType = DbCommandType.SelectInitializedChangesWithFilters; + else if (isNew && !hasFilters) + dbCommandType = DbCommandType.SelectInitializedChanges; + else if (!isNew && hasFilters) + dbCommandType = DbCommandType.SelectChangesWithFilters; + else + dbCommandType = DbCommandType.SelectChanges; - // Check if we have parameters specified + // Get correct Select incremental changes command + var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, syncTable, scopeInfo.Setup); - // Sqlite does not have any filter, since he can't be server side - if (this.Provider != null && this.Provider.CanBeServerProvider) - tableFilter = syncTable.GetFilter(); + var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, dbCommandType, tableFilter, + connection, transaction, default, default).ConfigureAwait(false); - var hasFilters = tableFilter != null; + return (command, dbCommandType); + } + catch (Exception ex) + { + string message = null; - // Determing the correct DbCommandType - if (isNew && hasFilters) - dbCommandType = DbCommandType.SelectInitializedChangesWithFilters; - else if (isNew && !hasFilters) - dbCommandType = DbCommandType.SelectInitializedChanges; - else if (!isNew && hasFilters) - dbCommandType = DbCommandType.SelectChangesWithFilters; - else - dbCommandType = DbCommandType.SelectChanges; + if (syncTable != null) + message += $"Table:{syncTable.GetFullName()}."; - // Get correct Select incremental changes command - var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, syncTable, scopeInfo.Setup); + message += $"Is New:{isNew}."; - var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, dbCommandType, tableFilter, - connection, transaction, default, default).ConfigureAwait(false); + if (dbCommandType != DbCommandType.None) + message += $"dbCommandType:{dbCommandType}."; - return (command, dbCommandType); + throw GetSyncError(context, ex, message); + } } /// @@ -500,72 +561,98 @@ await scopeInfo.Schema.Tables.ForEachAsync(async syncTable => /// internal void InternalSetSelectChangesCommonParameters(SyncContext context, SyncTable syncTable, Guid? excludingScopeId, bool isNew, long? lastTimestamp, DbCommand selectIncrementalChangesCommand) { - // Set the parameters - SetParameterValue(selectIncrementalChangesCommand, "sync_min_timestamp", lastTimestamp); - SetParameterValue(selectIncrementalChangesCommand, "sync_scope_id", excludingScopeId.HasValue ? excludingScopeId.Value : DBNull.Value); + try + { + // Set the parameters + SetParameterValue(selectIncrementalChangesCommand, "sync_min_timestamp", lastTimestamp); + SetParameterValue(selectIncrementalChangesCommand, "sync_scope_id", excludingScopeId.HasValue ? excludingScopeId.Value : DBNull.Value); - // Check filters - SyncFilter tableFilter = null; + // Check filters + SyncFilter tableFilter = null; - // Sqlite does not have any filter, since he can't be server side - if (this.Provider != null && this.Provider.CanBeServerProvider) - tableFilter = syncTable.GetFilter(); + // Sqlite does not have any filter, since he can't be server side + if (this.Provider != null && this.Provider.CanBeServerProvider) + tableFilter = syncTable.GetFilter(); - var hasFilters = tableFilter != null; + var hasFilters = tableFilter != null; - if (!hasFilters) - return; + if (!hasFilters) + return; - // context parameters can be null at some point. - var contexParameters = context.Parameters ?? new SyncParameters(); + // context parameters can be null at some point. + var contexParameters = context.Parameters ?? new SyncParameters(); - foreach (var filterParam in tableFilter.Parameters) - { - var parameter = contexParameters.FirstOrDefault(p => - p.Name.Equals(filterParam.Name, SyncGlobalization.DataSourceStringComparison)); + foreach (var filterParam in tableFilter.Parameters) + { + var parameter = contexParameters.FirstOrDefault(p => + p.Name.Equals(filterParam.Name, SyncGlobalization.DataSourceStringComparison)); - object val = parameter?.Value; + object val = parameter?.Value; - SetParameterValue(selectIncrementalChangesCommand, filterParam.Name, val); + SetParameterValue(selectIncrementalChangesCommand, filterParam.Name, val); + } } + catch (Exception ex) + { + string message = null; - } - + if (syncTable != null) + message += $"Table:{syncTable.GetFullName()}."; + message += $"Is New:{isNew}."; + message += $"lastTimestamp:{lastTimestamp}."; + throw GetSyncError(context, ex, message); + } + } /// /// Create a new SyncRow from a dataReader. /// - internal SyncRow CreateSyncRowFromReader2(IDataReader dataReader, SyncTable schemaTable) + internal SyncRow CreateSyncRowFromReader2(SyncContext context, IDataReader dataReader, SyncTable schemaTable) { // Create a new row, based on table structure + SyncRow syncRow = null; + try + { + syncRow = new SyncRow(schemaTable); - var syncRow = new SyncRow(schemaTable); + bool isTombstone = false; - bool isTombstone = false; + for (var i = 0; i < dataReader.FieldCount; i++) + { + var columnName = dataReader.GetName(i); - for (var i = 0; i < dataReader.FieldCount; i++) - { - var columnName = dataReader.GetName(i); + // if we have the tombstone value, do not add it to the table + if (columnName == "sync_row_is_tombstone") + { + isTombstone = Convert.ToInt64(dataReader.GetValue(i)) > 0; + continue; + } + if (columnName == "sync_update_scope_id") + continue; - // if we have the tombstone value, do not add it to the table - if (columnName == "sync_row_is_tombstone") - { - isTombstone = Convert.ToInt64(dataReader.GetValue(i)) > 0; - continue; - } - if (columnName == "sync_update_scope_id") - continue; + var columnValueObject = dataReader.GetValue(i); + var columnValue = columnValueObject == DBNull.Value ? null : columnValueObject; - var columnValueObject = dataReader.GetValue(i); - var columnValue = columnValueObject == DBNull.Value ? null : columnValueObject; + syncRow[i] = columnValue; + } - syncRow[i] = columnValue; + syncRow.RowState = isTombstone ? SyncRowState.Deleted : SyncRowState.Modified; + return syncRow; } + catch (Exception ex) + { + string message = null; + + if (schemaTable != null) + message += $"Table:{schemaTable.GetFullName()}."; + + if (syncRow != null) + message += $"Row:{syncRow}."; - syncRow.RowState = isTombstone ? SyncRowState.Deleted : SyncRowState.Modified; - return syncRow; + throw GetSyncError(context, ex, message); + } } /// @@ -574,37 +661,49 @@ internal SyncRow CreateSyncRowFromReader2(IDataReader dataReader, SyncTable sche /// internal void EnsureLastBatchInfo(ScopeInfo scopeInfo, SyncContext context, BatchInfo batchInfo, IEnumerable lstAllBatchPartInfos, IEnumerable schemaTables) { - - // delete all empty batchparts (empty tables) - foreach (var bpi in lstAllBatchPartInfos.Where(bpi => bpi.RowsCount <= 0)) - File.Delete(Path.Combine(batchInfo.GetDirectoryFullPath(), bpi.FileName)); - - // Generate a good index order to be compliant with previous versions - var tmpLstBatchPartInfos = new List(); - foreach (var table in schemaTables) + try { - // get all bpi where count > 0 and ordered by index - foreach (var bpi in lstAllBatchPartInfos.Where(bpi => bpi.RowsCount > 0 && bpi.EqualsByName(new BatchPartInfo { TableName = table.TableName, SchemaName = table.SchemaName })).OrderBy(bpi => bpi.Index).ToArray()) + // delete all empty batchparts (empty tables) + foreach (var bpi in lstAllBatchPartInfos.Where(bpi => bpi.RowsCount <= 0)) + File.Delete(Path.Combine(batchInfo.GetDirectoryFullPath(), bpi.FileName)); + + // Generate a good index order to be compliant with previous versions + var tmpLstBatchPartInfos = new List(); + foreach (var table in schemaTables) { - batchInfo.BatchPartsInfo.Add(bpi); - batchInfo.RowsCount += bpi.RowsCount; + // get all bpi where count > 0 and ordered by index + foreach (var bpi in lstAllBatchPartInfos.Where(bpi => bpi.RowsCount > 0 && bpi.EqualsByName(new BatchPartInfo { TableName = table.TableName, SchemaName = table.SchemaName })).OrderBy(bpi => bpi.Index).ToArray()) + { + batchInfo.BatchPartsInfo.Add(bpi); + batchInfo.RowsCount += bpi.RowsCount; - tmpLstBatchPartInfos.Add(bpi); + tmpLstBatchPartInfos.Add(bpi); + } } - } - var newBatchIndex = 0; - foreach (var bpi in tmpLstBatchPartInfos) - { - bpi.Index = newBatchIndex; - newBatchIndex++; - bpi.IsLastBatch = newBatchIndex == tmpLstBatchPartInfos.Count; + var newBatchIndex = 0; + foreach (var bpi in tmpLstBatchPartInfos) + { + bpi.Index = newBatchIndex; + newBatchIndex++; + bpi.IsLastBatch = newBatchIndex == tmpLstBatchPartInfos.Count; + } + + //Set the total rows count contained in the batch info + batchInfo.EnsureLastBatch(); } + catch (Exception ex) + { + string message = null; - //Set the total rows count contained in the batch info - batchInfo.EnsureLastBatch(); + if (batchInfo != null && batchInfo.DirectoryRoot != null) + message += $"Directory:{batchInfo.DirectoryRoot}."; - } + if (batchInfo != null && batchInfo.DirectoryName != null) + message += $"Folder:{batchInfo.DirectoryName}."; + throw GetSyncError(context, ex, message); + } + } } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/LocalOrchestrator.GetChanges.cs b/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/LocalOrchestrator.GetChanges.cs index 75fdd571e..5502ce9a5 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/LocalOrchestrator.GetChanges.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/LocalOrchestrator.GetChanges.cs @@ -77,13 +77,11 @@ public virtual async Task GetChangesAsync(ScopeInfoClient cSc await runner.CommitAsync().ConfigureAwait(false); return clientChanges; - } catch (Exception ex) { throw GetSyncError(context, ex); } - } /// diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/RemoteOrchestrator.GetChanges.cs b/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/RemoteOrchestrator.GetChanges.cs index 975bfc9c4..3cafbb619 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/RemoteOrchestrator.GetChanges.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/GetChanges/RemoteOrchestrator.GetChanges.cs @@ -59,7 +59,6 @@ public partial class RemoteOrchestrator : BaseOrchestrator /// public virtual async Task GetChangesAsync(ScopeInfoClient cScopeInfoClient, DbConnection connection = null, DbTransaction transaction = null) { - var context = new SyncContext(Guid.NewGuid(), cScopeInfoClient); try diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/LocalOrchestrator.Untracked.cs b/Projects/Dotmim.Sync.Core/Orchestrators/LocalOrchestrator.Untracked.cs index 8061840af..3c40c1a28 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/LocalOrchestrator.Untracked.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/LocalOrchestrator.Untracked.cs @@ -36,7 +36,7 @@ public virtual async Task UpdateUntrackedRowsAsync(string scopeName, DbCon runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (cScopeInfo.Schema == null || cScopeInfo.Schema.Tables == null || cScopeInfo.Schema.Tables.Count <= 0 || !cScopeInfo.Schema.HasColumns) - throw new MissingTablesException(scopeName); + throw new MissingTablesException(); long totalUpdates = 0L; // Update untracked rows diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/BaseOrchestrator.Metadatas.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/BaseOrchestrator.Metadatas.cs index 3a6c4bac5..a2cd2f3b5 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/BaseOrchestrator.Metadatas.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/BaseOrchestrator.Metadatas.cs @@ -12,6 +12,8 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -32,89 +34,91 @@ public abstract partial class BaseOrchestrator var databaseMetadatasCleaned = new DatabaseMetadatasCleaned { TimestampLimit = timestampLimit }; - await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.MetadataCleaning, connection, transaction).ConfigureAwait(false); - - await this.InterceptAsync(new MetadataCleaningArgs(context, scopeInfos, timestampLimit, - runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.MetadataCleaning, connection, transaction).ConfigureAwait(false); - // contains all tables already processed - var doneList = new List(); + await this.InterceptAsync(new MetadataCleaningArgs(context, scopeInfos, timestampLimit, + runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - foreach (var scopeInfo in scopeInfos) - { - if (scopeInfo.Setup?.Tables == null || scopeInfo.Setup.Tables.Count <= 0) - continue; + // contains all tables already processed + var doneList = new List(); - foreach (var setupTable in scopeInfo.Setup.Tables) + foreach (var scopeInfo in scopeInfos) { - var isDone = doneList.Any(t => t.EqualsByName(setupTable)); - - if (isDone) + if (scopeInfo.Setup?.Tables == null || scopeInfo.Setup.Tables.Count <= 0) continue; - // create a fake syncTable - // Don't need anything else than table name to make a delete metadata clean up - var syncTable = new SyncTable(setupTable.TableName, setupTable.SchemaName); - var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, syncTable, scopeInfo.Setup); + foreach (var setupTable in scopeInfo.Setup.Tables) + { + var isDone = doneList.Any(t => t.EqualsByName(setupTable)); - var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.DeleteMetadata, null, - runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (isDone) + continue; - if (command != null) - { - // Set the special parameters for delete metadata - SetParameterValue(command, "sync_row_timestamp", timestampLimit); + // create a fake syncTable + // Don't need anything else than table name to make a delete metadata clean up + var syncTable = new SyncTable(setupTable.TableName, setupTable.SchemaName); + var syncAdapter = this.GetSyncAdapter(scopeInfo.Name, syncTable, scopeInfo.Setup); - await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.DeleteMetadata, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + var (command, _) = await this.InternalGetCommandAsync(scopeInfo, context, syncAdapter, DbCommandType.DeleteMetadata, null, + runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - // Parametrized command timeout established if exist - if (Options.DbCommandTimeout.HasValue) + if (command != null) { - command.CommandTimeout = Options.DbCommandTimeout.Value; - } + // Set the special parameters for delete metadata + SetParameterValue(command, "sync_row_timestamp", timestampLimit); - int rowsCleanedCount = 0; - try - { - rowsCleanedCount = await command.ExecuteNonQueryAsync().ConfigureAwait(false); - } - catch (Exception) - { + await this.InterceptAsync(new ExecuteCommandArgs(context, command, DbCommandType.DeleteMetadata, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - // we can try a stored proc that is not existing anymore - // so just pass through it - } + // Parametrized command timeout established if exist + if (Options.DbCommandTimeout.HasValue) + { + command.CommandTimeout = Options.DbCommandTimeout.Value; + } + + int rowsCleanedCount = 0; + rowsCleanedCount = await command.ExecuteNonQueryAsync().ConfigureAwait(false); - // Check if we have a return value instead - var syncRowCountParam = InternalGetParameter(command, "sync_row_count"); + // Check if we have a return value instead + var syncRowCountParam = InternalGetParameter(command, "sync_row_count"); - if (syncRowCountParam != null) - rowsCleanedCount = (int)syncRowCountParam.Value; + if (syncRowCountParam != null) + rowsCleanedCount = (int)syncRowCountParam.Value; - // Only add a new table metadata stats object, if we have, at least, purged 1 or more rows - if (rowsCleanedCount > 0) - { - var tableMetadatasCleaned = new TableMetadatasCleaned(syncTable.TableName, syncTable.SchemaName) + // Only add a new table metadata stats object, if we have, at least, purged 1 or more rows + if (rowsCleanedCount > 0) { - RowsCleanedCount = rowsCleanedCount, - TimestampLimit = timestampLimit - }; + var tableMetadatasCleaned = new TableMetadatasCleaned(syncTable.TableName, syncTable.SchemaName) + { + RowsCleanedCount = rowsCleanedCount, + TimestampLimit = timestampLimit + }; - databaseMetadatasCleaned.Tables.Add(tableMetadatasCleaned); + databaseMetadatasCleaned.Tables.Add(tableMetadatasCleaned); + } + + command.Dispose(); } - command.Dispose(); + doneList.Add(setupTable); } - - doneList.Add(setupTable); } - } - await this.InterceptAsync(new MetadataCleanedArgs(context, databaseMetadatasCleaned, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new MetadataCleanedArgs(context, databaseMetadatasCleaned, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - await runner.CommitAsync().ConfigureAwait(false); + await runner.CommitAsync().ConfigureAwait(false); - return (context, databaseMetadatasCleaned); + return (context, databaseMetadatasCleaned); + } + catch (Exception ex) + { + string message = null; + + message += $"TimestampLimit:{timestampLimit}."; + + throw GetSyncError(context, ex, message); + } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/LocalOrchestrator.Metadatas.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/LocalOrchestrator.Metadatas.cs index 05a0d21fc..7bc327e53 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/LocalOrchestrator.Metadatas.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/LocalOrchestrator.Metadatas.cs @@ -32,51 +32,57 @@ public partial class LocalOrchestrator : BaseOrchestrator public async Task DeleteMetadatasAsync(DbConnection connection = null, DbTransaction transaction = null) { var context = new SyncContext(Guid.NewGuid(), SyncOptions.DefaultScopeName); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.MetadataCleaning, connection, transaction).ConfigureAwait(false); - await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.MetadataCleaning, connection, transaction).ConfigureAwait(false); - - bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, - runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - - if (!exists) - (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, + bool exists; + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - List clientScopeInfos; - (context, clientScopeInfos) = await this.InternalLoadAllScopeInfosAsync(context, - runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (!exists) + (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, + runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (clientScopeInfos == null || clientScopeInfos.Count == 0) - return new DatabaseMetadatasCleaned(); + List clientScopeInfos; + (context, clientScopeInfos) = await this.InternalLoadAllScopeInfosAsync(context, + runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - bool existsCScopeInfoClient; - (context, existsCScopeInfoClient) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, - runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (clientScopeInfos == null || clientScopeInfos.Count == 0) + return new DatabaseMetadatasCleaned(); - if (!existsCScopeInfoClient) - (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, + bool existsCScopeInfoClient; + (context, existsCScopeInfoClient) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - var clientHistoriesScopeInfos = await this.InternalLoadAllScopeInfoClientsAsync(context, - runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (!existsCScopeInfoClient) + (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, + runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (clientHistoriesScopeInfos == null || clientHistoriesScopeInfos.Count == 0) - return new DatabaseMetadatasCleaned(); + var clientHistoriesScopeInfos = await this.InternalLoadAllScopeInfoClientsAsync(context, + runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - var minTimestamp = clientHistoriesScopeInfos.Min(scope => scope.LastSyncTimestamp); + if (clientHistoriesScopeInfos == null || clientHistoriesScopeInfos.Count == 0) + return new DatabaseMetadatasCleaned(); - if (!minTimestamp.HasValue || minTimestamp.Value == 0) - return new DatabaseMetadatasCleaned(); + var minTimestamp = clientHistoriesScopeInfos.Min(scope => scope.LastSyncTimestamp); - DatabaseMetadatasCleaned databaseMetadatasCleaned; - (context, databaseMetadatasCleaned) = await this.InternalDeleteMetadatasAsync(context, minTimestamp, - runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (!minTimestamp.HasValue || minTimestamp.Value == 0) + return new DatabaseMetadatasCleaned(); - await runner.CommitAsync().ConfigureAwait(false); + DatabaseMetadatasCleaned databaseMetadatasCleaned; + (context, databaseMetadatasCleaned) = await this.InternalDeleteMetadatasAsync(context, minTimestamp, + runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + + await runner.CommitAsync().ConfigureAwait(false); - return databaseMetadatasCleaned; + return databaseMetadatasCleaned; + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } @@ -92,9 +98,16 @@ public async Task DeleteMetadatasAsync(DbConnection co public async Task DeleteMetadatasAsync(long timeStampStart, DbConnection connection = null, DbTransaction transaction = null) { var context = new SyncContext(Guid.NewGuid(), SyncOptions.DefaultScopeName); - DatabaseMetadatasCleaned databaseMetadatasCleaned; - (_, databaseMetadatasCleaned) = await InternalDeleteMetadatasAsync(context, timeStampStart, connection, transaction).ConfigureAwait(false); - return databaseMetadatasCleaned; + try + { + DatabaseMetadatasCleaned databaseMetadatasCleaned; + (_, databaseMetadatasCleaned) = await InternalDeleteMetadatasAsync(context, timeStampStart, connection, transaction).ConfigureAwait(false); + return databaseMetadatasCleaned; + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } @@ -106,7 +119,6 @@ public async Task DeleteMetadatasAsync(long timeStampS { if (!timeStampStart.HasValue) return (context, null); - try { await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.MetadataCleaning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); @@ -132,12 +144,12 @@ public async Task DeleteMetadatasAsync(long timeStampS } catch (Exception ex) { - throw GetSyncError(context, ex); - } - - } - + string message = null; + message += $"TimestampStart:{timeStampStart}."; + throw GetSyncError(context, ex, message); + } + } } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/RemoteOrchestrator.Metadatas.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/RemoteOrchestrator.Metadatas.cs index 116818780..ee354a842 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/RemoteOrchestrator.Metadatas.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Metadatas/RemoteOrchestrator.Metadatas.cs @@ -33,14 +33,14 @@ public virtual async Task DeleteMetadatasAsync(DbConne await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.MetadataCleaning, connection, transaction).ConfigureAwait(false); bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (!exists) - await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, + await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - var sScopeInfoClients = await this.InternalLoadAllScopeInfoClientsAsync(context, + var sScopeInfoClients = await this.InternalLoadAllScopeInfoClientsAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); if (sScopeInfoClients == null || sScopeInfoClients.Count == 0) @@ -74,7 +74,6 @@ await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClien /// Optional Transaction public virtual async Task DeleteMetadatasAsync(long timeStampStart, DbConnection connection = null, DbTransaction transaction = null) { - var context = new SyncContext(Guid.NewGuid(), SyncOptions.DefaultScopeName); try @@ -82,7 +81,7 @@ public virtual async Task DeleteMetadatasAsync(long ti await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.MetadataCleaning, connection, transaction).ConfigureAwait(false); DatabaseMetadatasCleaned databaseMetadatasCleaned; - (context, databaseMetadatasCleaned) = await this.InternalDeleteMetadatasAsync(timeStampStart, context, + (context, databaseMetadatasCleaned) = await this.InternalDeleteMetadatasAsync(timeStampStart, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); @@ -92,9 +91,12 @@ public virtual async Task DeleteMetadatasAsync(long ti } catch (Exception ex) { - throw GetSyncError(context, ex); - } + string message = null; + message += $"TimestampStart:{timeStampStart}."; + + throw GetSyncError(context, ex, message); + } } /// @@ -106,36 +108,46 @@ public virtual async Task DeleteMetadatasAsync(long ti if (!timeStampStart.HasValue) return (context, new DatabaseMetadatasCleaned()); + try + { + + await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.MetadataCleaning, connection, transaction).ConfigureAwait(false); + + bool exists; + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.MetadataCleaning, connection, transaction).ConfigureAwait(false); + if (!exists) + await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + List sScopeInfos; + (context, sScopeInfos) = await this.InternalLoadAllScopeInfosAsync(context, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (!exists) - await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + if (sScopeInfos == null || sScopeInfos.Count == 0) + return (context, new DatabaseMetadatasCleaned()); - List sScopeInfos; - (context, sScopeInfos) = await this.InternalLoadAllScopeInfosAsync(context, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + DatabaseMetadatasCleaned databaseMetadatasCleaned; + (context, databaseMetadatasCleaned) = await this.InternalDeleteMetadatasAsync(sScopeInfos, context, timeStampStart.Value, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (sScopeInfos == null || sScopeInfos.Count == 0) - return (context, new DatabaseMetadatasCleaned()); + foreach (var serverScopeInfo in sScopeInfos) + { + serverScopeInfo.LastCleanupTimestamp = databaseMetadatasCleaned.TimestampLimit; + var tmpContext = new SyncContext(Guid.NewGuid(), serverScopeInfo.Name); - DatabaseMetadatasCleaned databaseMetadatasCleaned; - (context, databaseMetadatasCleaned) = await this.InternalDeleteMetadatasAsync(sScopeInfos, context, timeStampStart.Value, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + await this.InternalSaveScopeInfoAsync(serverScopeInfo, tmpContext, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + } - foreach (var serverScopeInfo in sScopeInfos) - { - serverScopeInfo.LastCleanupTimestamp = databaseMetadatasCleaned.TimestampLimit; - var tmpContext = new SyncContext(Guid.NewGuid(), serverScopeInfo.Name); + await runner.CommitAsync().ConfigureAwait(false); - await this.InternalSaveScopeInfoAsync(serverScopeInfo, tmpContext, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + return (context, databaseMetadatasCleaned); } + catch (Exception ex) + { + string message = null; - await runner.CommitAsync().ConfigureAwait(false); + message += $"TimestampStart:{timeStampStart}."; - return (context, databaseMetadatasCleaned); + throw GetSyncError(context, ex, message); + } } - } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Provision/BaseOrchestrator.Provision.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Provision/BaseOrchestrator.Provision.cs index 49fb85b84..b861c667f 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Provision/BaseOrchestrator.Provision.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Provision/BaseOrchestrator.Provision.cs @@ -16,215 +16,237 @@ namespace Dotmim.Sync { public abstract partial class BaseOrchestrator { - internal virtual async Task<(SyncContext context, bool provisioned)> InternalProvisionAsync(ScopeInfo scopeInfo, SyncContext context, bool overwrite, SyncProvision provision, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) + internal virtual async Task<(SyncContext context, bool provisioned)> InternalProvisionAsync(ScopeInfo scopeInfo, SyncContext context, bool overwrite, SyncProvision provision, + DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (Provider == null) - throw new MissingProviderException(nameof(InternalProvisionAsync)); + try + { - context.SyncStage = SyncStage.Provisioning; + if (Provider == null) + throw new MissingProviderException(nameof(InternalProvisionAsync)); - // If schema does not have any table, raise an exception - if (scopeInfo.Schema == null || scopeInfo.Schema.Tables == null || !scopeInfo.Schema.HasTables) - throw new MissingTablesException(scopeInfo.Name); + context.SyncStage = SyncStage.Provisioning; - await this.InterceptAsync(new ProvisioningArgs(context, provision, scopeInfo.Schema, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + // If schema does not have any table, raise an exception + if (scopeInfo.Schema == null || scopeInfo.Schema.Tables == null || !scopeInfo.Schema.HasTables) + throw new MissingTablesException(); - // get Database builder - var builder = this.Provider.GetDatabaseBuilder(); + await this.InterceptAsync(new ProvisioningArgs(context, provision, scopeInfo.Schema, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - // Initialize database if needed - await builder.EnsureDatabaseAsync(connection, transaction).ConfigureAwait(false); + // get Database builder + var builder = this.Provider.GetDatabaseBuilder(); - // Check if we have tables AND columns - // If we don't have any columns it's most probably because user called method with the Setup only - // So far we have only tables names, it's enough to get the schema - if (scopeInfo.Schema.HasTables && !scopeInfo.Schema.HasColumns) - (context, scopeInfo.Schema) = await this.InternalGetSchemaAsync(context, scopeInfo.Setup, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + // Initialize database if needed + await builder.EnsureDatabaseAsync(connection, transaction).ConfigureAwait(false); - // Shoudl we create scope - if (provision.HasFlag(SyncProvision.ScopeInfo)) - { - bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + // Check if we have tables AND columns + // If we don't have any columns it's most probably because user called method with the Setup only + // So far we have only tables names, it's enough to get the schema + if (scopeInfo.Schema.HasTables && !scopeInfo.Schema.HasColumns) + (context, scopeInfo.Schema) = await this.InternalGetSchemaAsync(context, scopeInfo.Setup, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (!exists) - (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - } + // Shoudl we create scope + if (provision.HasFlag(SyncProvision.ScopeInfo)) + { + bool exists; + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (provision.HasFlag(SyncProvision.ScopeInfoClient)) - { - bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + if (!exists) + (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + } - if (!exists) - (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - } + if (provision.HasFlag(SyncProvision.ScopeInfoClient)) + { + bool exists; + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - // Sorting tables based on dependencies between them - var schemaTables = scopeInfo.Schema.Tables - .SortByDependencies(tab => tab.GetRelations() - .Select(r => r.GetParentTable())); + if (!exists) + (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + } - foreach (var schemaTable in schemaTables) - { - var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); + // Sorting tables based on dependencies between them + var schemaTables = scopeInfo.Schema.Tables + .SortByDependencies(tab => tab.GetRelations() + .Select(r => r.GetParentTable())); - // Check if we need to create a schema there - bool schemaExists; - (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + foreach (var schemaTable in schemaTables) + { + var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); - if (!schemaExists) - (context, _) = await InternalCreateSchemaAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + // Check if we need to create a schema there + bool schemaExists; + (context, schemaExists) = await InternalExistsSchemaAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (provision.HasFlag(SyncProvision.Table)) - { - bool tableExists; - (context, tableExists) = await this.InternalExistsTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + if (!schemaExists) + (context, _) = await InternalCreateSchemaAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (!tableExists) - (context, _) = await this.InternalCreateTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - } + if (provision.HasFlag(SyncProvision.Table)) + { + bool tableExists; + (context, tableExists) = await this.InternalExistsTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (provision.HasFlag(SyncProvision.TrackingTable)) - { - bool trackingTableExist; - (context, trackingTableExist) = await this.InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + if (!tableExists) + (context, _) = await this.InternalCreateTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + } - if (!trackingTableExist) - (context, _) = await this.InternalCreateTrackingTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - } + if (provision.HasFlag(SyncProvision.TrackingTable)) + { + bool trackingTableExist; + (context, trackingTableExist) = await this.InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - if (provision.HasFlag(SyncProvision.Triggers)) - await this.InternalCreateTriggersAsync(scopeInfo, context, overwrite, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + if (!trackingTableExist) + (context, _) = await this.InternalCreateTrackingTableAsync(scopeInfo, context, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + } - if (provision.HasFlag(SyncProvision.StoredProcedures)) - (context, _) = await this.InternalCreateStoredProceduresAsync(scopeInfo, context, overwrite, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + if (provision.HasFlag(SyncProvision.Triggers)) + await this.InternalCreateTriggersAsync(scopeInfo, context, overwrite, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - } + if (provision.HasFlag(SyncProvision.StoredProcedures)) + (context, _) = await this.InternalCreateStoredProceduresAsync(scopeInfo, context, overwrite, tableBuilder, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + } + await this.InterceptAsync(new ProvisionedArgs(context, provision, scopeInfo.Schema, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - await this.InterceptAsync(new ProvisionedArgs(context, provision, scopeInfo.Schema, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + return (context, true); + } + catch (Exception ex) + { + string message = null; - return (context, true); + message += $"Provision:{provision}."; + message += $"Overwrite:{overwrite}."; + throw GetSyncError(context, ex, message); + } } internal virtual async Task<(SyncContext context, bool deprovisioned)> InternalDeprovisionAsync(ScopeInfo scopeInfo, SyncContext context, SyncProvision provision, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - if (Provider == null) - throw new MissingProviderException(nameof(InternalDeprovisionAsync)); + try + { - context.SyncStage = SyncStage.Deprovisioning; + if (Provider == null) + throw new MissingProviderException(nameof(InternalDeprovisionAsync)); - await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.Deprovisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + context.SyncStage = SyncStage.Deprovisioning; - await this.InterceptAsync(new DeprovisioningArgs(context, provision, scopeInfo?.Setup, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.Deprovisioning, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - // get Database builder - var builder = this.Provider.GetDatabaseBuilder(); + await this.InterceptAsync(new DeprovisioningArgs(context, provision, scopeInfo?.Setup, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); - // Sorting tables based on dependencies between them - IEnumerable schemaTables; - if (scopeInfo == null) - { - schemaTables = new List(); - } - else - { - if (scopeInfo.Schema != null) + // get Database builder + var builder = this.Provider.GetDatabaseBuilder(); + + // Sorting tables based on dependencies between them + IEnumerable schemaTables; + if (scopeInfo == null) { - schemaTables = scopeInfo.Schema.Tables.SortByDependencies(tab => tab.GetRelations().Select(r => r.GetParentTable())).ToList(); + schemaTables = new List(); } else { - schemaTables = new List(); - foreach (var setupTable in scopeInfo.Setup.Tables) - ((List)schemaTables).Add(new SyncTable(setupTable.TableName, setupTable.SchemaName)); + if (scopeInfo.Schema != null) + { + schemaTables = scopeInfo.Schema.Tables.SortByDependencies(tab => tab.GetRelations().Select(r => r.GetParentTable())).ToList(); + } + else + { + schemaTables = new List(); + foreach (var setupTable in scopeInfo.Setup.Tables) + ((List)schemaTables).Add(new SyncTable(setupTable.TableName, setupTable.SchemaName)); + } } - } - - // Disable check constraints - if (this.Options.DisableConstraintsOnApplyChanges) - foreach (var table in schemaTables.Reverse()) - await this.InternalDisableConstraintsAsync(scopeInfo, context, table, runner.Connection, runner.Transaction).ConfigureAwait(false); + // Disable check constraints + if (this.Options.DisableConstraintsOnApplyChanges) + foreach (var table in schemaTables.Reverse()) + await this.InternalDisableConstraintsAsync(scopeInfo, context, table, runner.Connection, runner.Transaction).ConfigureAwait(false); - // Checking if we have to deprovision tables - bool hasDeprovisionTableFlag = provision.HasFlag(SyncProvision.Table); - // Firstly, removing the flag from the provision, because we need to drop everything in correct order, then drop tables in reverse side - if (hasDeprovisionTableFlag) - provision ^= SyncProvision.Table; + // Checking if we have to deprovision tables + bool hasDeprovisionTableFlag = provision.HasFlag(SyncProvision.Table); - foreach (var schemaTable in schemaTables) - { - var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); + // Firstly, removing the flag from the provision, because we need to drop everything in correct order, then drop tables in reverse side + if (hasDeprovisionTableFlag) + provision ^= SyncProvision.Table; - if (provision.HasFlag(SyncProvision.StoredProcedures)) + foreach (var schemaTable in schemaTables) { - (context, _) = await InternalDropStoredProceduresAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); + + if (provision.HasFlag(SyncProvision.StoredProcedures)) + { + (context, _) = await InternalDropStoredProceduresAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + + // Removing cached commands + this.RemoveCommands(); + } - // Removing cached commands - this.RemoveCommands(); + if (provision.HasFlag(SyncProvision.Triggers)) + { + (context, _) = await InternalDropTriggersAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + } + + if (provision.HasFlag(SyncProvision.TrackingTable)) + { + bool exists; + (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + + if (exists) + (context, _) = await this.InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + } } - if (provision.HasFlag(SyncProvision.Triggers)) + // Eventually if we have the "Table" flag, then drop the table + if (hasDeprovisionTableFlag) { - (context, _) = await InternalDropTriggersAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + foreach (var schemaTable in schemaTables.Reverse()) + { + var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); + bool exists; + (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + + if (exists) + (context, _) = await this.InternalDropTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + } } - if (provision.HasFlag(SyncProvision.TrackingTable)) + if (provision.HasFlag(SyncProvision.ScopeInfo)) { bool exists; - (context, exists) = await InternalExistsTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); if (exists) - (context, _) = await this.InternalDropTrackingTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + { + (context, _) = await this.InternalDropScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + } } - } - // Eventually if we have the "Table" flag, then drop the table - if (hasDeprovisionTableFlag) - { - foreach (var schemaTable in schemaTables.Reverse()) + if (provision.HasFlag(SyncProvision.ScopeInfoClient)) { - var tableBuilder = this.GetTableBuilder(schemaTable, scopeInfo); bool exists; - (context, exists) = await InternalExistsTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); if (exists) - (context, _) = await this.InternalDropTableAsync(scopeInfo, context, tableBuilder, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + (context, _) = await this.InternalDropScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); } - } - if (provision.HasFlag(SyncProvision.ScopeInfo)) - { - bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + var args = new DeprovisionedArgs(context, provision, scopeInfo?.Setup, runner.Connection); + await this.InterceptAsync(args, progress, cancellationToken).ConfigureAwait(false); - if (exists) - { - (context, _) = await this.InternalDropScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); - } - } + await runner.CommitAsync().ConfigureAwait(false); - if (provision.HasFlag(SyncProvision.ScopeInfoClient)) - { - bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); - if (exists) - (context, _) = await this.InternalDropScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, cancellationToken, progress).ConfigureAwait(false); + return (context, true); } + catch (Exception ex) + { + string message = null; - var args = new DeprovisionedArgs(context, provision, scopeInfo?.Setup, runner.Connection); - await this.InterceptAsync(args, progress, cancellationToken).ConfigureAwait(false); - - await runner.CommitAsync().ConfigureAwait(false); - + message += $"Provision:{provision}."; - return (context, true); + throw GetSyncError(context, ex, message); + } } - } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Provision/LocalOrchestrator.Provision.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Provision/LocalOrchestrator.Provision.cs index 8949bcb03..47f817429 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Provision/LocalOrchestrator.Provision.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Provision/LocalOrchestrator.Provision.cs @@ -68,7 +68,12 @@ public async Task ProvisionAsync(ScopeInfo sScopeInfo, SyncProvision } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + message += $"Provision:{provision}."; + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } } @@ -124,7 +129,11 @@ public virtual async Task DeprovisionAsync(string scopeName, SyncProvision } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + message += $"Provision:{provision}."; + + throw GetSyncError(context, ex, message); } } @@ -177,9 +186,12 @@ public virtual async Task DeprovisionAsync(string scopeName, SyncSetup set } catch (Exception ex) { - throw GetSyncError(context, ex); - } + string message = null; + + message += $"Provision:{provision}."; + throw GetSyncError(context, ex, message); + } } /// @@ -297,7 +309,12 @@ public virtual async Task DropAllAsync(DbConnection connection = null, DbTransac } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + message += $"Provision:{provision}."; + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Provision/RemoteOrchestrator.Provision.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Provision/RemoteOrchestrator.Provision.cs index 3db3a2ef3..ea24f4107 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Provision/RemoteOrchestrator.Provision.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Provision/RemoteOrchestrator.Provision.cs @@ -21,8 +21,6 @@ namespace Dotmim.Sync { public partial class RemoteOrchestrator : BaseOrchestrator { - - /// /// Provision a server datasource (triggers, stored procedures (if supported) and tracking tables if needed. Create also scope_info and scope_info_client tables. /// @@ -77,7 +75,12 @@ public virtual async Task ProvisionAsync(string scopeName, SyncSetup } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + message += $"Provision:{provision}."; + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); } } @@ -118,9 +121,13 @@ public virtual async Task ProvisionAsync(ScopeInfo serverScopeInfo, S } catch (Exception ex) { - throw GetSyncError(context, ex); - } + string message = null; + + message += $"Provision:{provision}."; + message += $"Overwrite:{overwrite}."; + throw GetSyncError(context, ex, message); + } } /// @@ -157,7 +164,6 @@ public virtual Task ProvisionAsync(SyncSetup setup, SyncProvision pro /// are not deprovisioned by default to preserve existing configurations /// /// If you do not specify provision, a default value SyncProvision.StoredProcedures | SyncProvision.Triggers is used. - /// /// Optional Connection /// Optional Transaction /// @@ -194,12 +200,16 @@ public virtual async Task DeprovisionAsync(string scopeName, SyncProvision } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + message += $"Provision:{provision}."; + + throw GetSyncError(context, ex, message); } } - /// + /// public virtual Task DeprovisionAsync(SyncSetup setup, SyncProvision provision = default, DbConnection connection = null, DbTransaction transaction = null) => DeprovisionAsync(SyncOptions.DefaultScopeName, setup, provision, connection, transaction); @@ -249,7 +259,11 @@ public virtual async Task DeprovisionAsync(string scopeName, SyncSetup set } catch (Exception ex) { - throw GetSyncError(context, ex); + string message = null; + + message += $"Provision:{provision}."; + + throw GetSyncError(context, ex, message); } } @@ -359,9 +373,13 @@ public virtual async Task DropAllAsync(DbConnection connection = null, DbTransac } catch (Exception ex) { - throw GetSyncError(context, ex); - } + string message = null; + message += $"Provision:{provision}."; + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); + } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopeInfoClients.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopeInfoClients.cs index 38257ce68..3e4644add 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopeInfoClients.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopeInfoClients.cs @@ -106,28 +106,35 @@ await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClien internal virtual async Task<(SyncContext context, bool exists)> InternalExistsScopeInfoClientAsync(SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { + try + { - if (string.IsNullOrEmpty(context.ScopeName) || !context.ClientId.HasValue) - return (context, false); + if (string.IsNullOrEmpty(context.ScopeName) || !context.ClientId.HasValue) + return (context, false); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction).ConfigureAwait(false); - // Get exists command - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + // Get exists command + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - using var existsCommand = scopeBuilder.GetCommandAsync(DbScopeCommandType.ExistScopeInfoClient, runner.Connection, runner.Transaction); + using var existsCommand = scopeBuilder.GetCommandAsync(DbScopeCommandType.ExistScopeInfoClient, runner.Connection, runner.Transaction); - if (existsCommand == null) return (context, false); + if (existsCommand == null) return (context, false); - SetParameterValue(existsCommand, "sync_scope_name", context.ScopeName); - SetParameterValue(existsCommand, "sync_scope_id", context.ClientId); - SetParameterValue(existsCommand, "sync_scope_hash", context.Hash); + SetParameterValue(existsCommand, "sync_scope_name", context.ScopeName); + SetParameterValue(existsCommand, "sync_scope_id", context.ClientId); + SetParameterValue(existsCommand, "sync_scope_hash", context.Hash); - await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); - var exists = Convert.ToInt32(existsResultObject) > 0; - return (context, exists); + var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); + var exists = Convert.ToInt32(existsResultObject) > 0; + return (context, exists); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } /// @@ -136,32 +143,39 @@ await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClien internal virtual async Task<(SyncContext context, ScopeInfoClient scopeInfoClient)> InternalLoadScopeInfoClientAsync(SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + try + { + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction).ConfigureAwait(false); - using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetScopeInfoClient, runner.Connection, runner.Transaction); + using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetScopeInfoClient, runner.Connection, runner.Transaction); - if (command == null) return (context, null); + if (command == null) return (context, null); - SetParameterValue(command, "sync_scope_name", context.ScopeName); - SetParameterValue(command, "sync_scope_id", context.ClientId); - SetParameterValue(command, "sync_scope_hash", context.Hash); + SetParameterValue(command, "sync_scope_name", context.ScopeName); + SetParameterValue(command, "sync_scope_id", context.ClientId); + SetParameterValue(command, "sync_scope_hash", context.Hash); - await this.InterceptAsync(new ExecuteCommandArgs(context, command, default, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, command, default, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); - using DbDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(false); + using DbDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(false); - ScopeInfoClient scopeInfoClient = null; + ScopeInfoClient scopeInfoClient = null; - if (reader.Read()) - scopeInfoClient = InternalReadScopeInfoClient(reader); + if (reader.Read()) + scopeInfoClient = InternalReadScopeInfoClient(reader); - reader.Close(); + reader.Close(); - command.Dispose(); + command.Dispose(); - return (context, scopeInfoClient); + return (context, scopeInfoClient); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } /// @@ -169,32 +183,37 @@ await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClien /// internal virtual async Task> InternalLoadAllScopeInfoClientsAsync(SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction).ConfigureAwait(false); - - using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetAllScopeInfoClients, runner.Connection, runner.Transaction); + try + { + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - if (command == null) return default; + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction).ConfigureAwait(false); - var scopeInfoClients = new List(); + using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetAllScopeInfoClients, runner.Connection, runner.Transaction); - await this.InterceptAsync(new ExecuteCommandArgs(context, command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + if (command == null) return default; - using DbDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(false); + var scopeInfoClients = new List(); - while (reader.Read()) - { - var scopeInfoClient = InternalReadScopeInfoClient(reader); + await this.InterceptAsync(new ExecuteCommandArgs(context, command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - scopeInfoClients.Add(scopeInfoClient); - } + using DbDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(false); - reader.Close(); + while (reader.Read()) + { + var scopeInfoClient = InternalReadScopeInfoClient(reader); - command.Dispose(); + scopeInfoClients.Add(scopeInfoClient); + } - return scopeInfoClients; + reader.Close(); + command.Dispose(); + return scopeInfoClients; + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } /// @@ -203,44 +222,51 @@ internal virtual async Task> InternalLoadAllScopeInfoClien internal async Task<(SyncContext context, ScopeInfoClient scopeInfoClient)> InternalSaveScopeInfoClientAsync(ScopeInfoClient scopeInfoClient, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + try + { + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction).ConfigureAwait(false); - bool scopeExists; - (context, scopeExists) = await InternalExistsScopeInfoClientAsync(context, - runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + bool scopeExists; + (context, scopeExists) = await InternalExistsScopeInfoClientAsync(context, + runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - DbCommand command; - if (scopeExists) - command = scopeBuilder.GetCommandAsync(DbScopeCommandType.UpdateScopeInfoClient, runner.Connection, runner.Transaction); - else - command = scopeBuilder.GetCommandAsync(DbScopeCommandType.InsertScopeInfoClient, runner.Connection, runner.Transaction); + DbCommand command; + if (scopeExists) + command = scopeBuilder.GetCommandAsync(DbScopeCommandType.UpdateScopeInfoClient, runner.Connection, runner.Transaction); + else + command = scopeBuilder.GetCommandAsync(DbScopeCommandType.InsertScopeInfoClient, runner.Connection, runner.Transaction); - if (command == null) return (context, null); + if (command == null) return (context, null); - InternalSetSaveScopeInfoClientParameters(scopeInfoClient, command); + InternalSetSaveScopeInfoClientParameters(scopeInfoClient, command); - //var action = new ScopeSavingArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeInfoClient, command, runner.Connection, runner.Transaction); - //await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); + //var action = new ScopeSavingArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeInfoClient, command, runner.Connection, runner.Transaction); + //await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); - //if (action.Cancel || action.Command == null) - // return default; + //if (action.Cancel || action.Command == null) + // return default; - await this.InterceptAsync(new ExecuteCommandArgs(context, command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - using DbDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(false); + using DbDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(false); - reader.Read(); + reader.Read(); - var newScopeInfoClient = InternalReadScopeInfoClient(reader); + var newScopeInfoClient = InternalReadScopeInfoClient(reader); - reader.Close(); + reader.Close(); - //await this.InterceptAsync(new ScopeSavedArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), newScopeInfoClient, connection, transaction), progress, cancellationToken).ConfigureAwait(false); - //command.Dispose(); + //await this.InterceptAsync(new ScopeSavedArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), newScopeInfoClient, connection, transaction), progress, cancellationToken).ConfigureAwait(false); + //command.Dispose(); - return (context, newScopeInfoClient); + return (context, newScopeInfoClient); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } /// diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopeInfos.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopeInfos.cs index efbf4c48d..b5c895454 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopeInfos.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopeInfos.cs @@ -150,216 +150,258 @@ await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, /// /// Internal load a ScopeInfo by scope name /// - internal async Task<(SyncContext context, ScopeInfo scopeInfo)> InternalGetScopeInfoAsync(SyncContext context, + internal async Task<(SyncContext context, ScopeInfo scopeInfo)> InternalGetScopeInfoAsync(SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + bool exists; + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (!exists) - (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (!exists) + (context, _) = await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - ScopeInfo localScopeInfo; - (context, localScopeInfo) = await this.InternalLoadScopeInfoAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + ScopeInfo localScopeInfo; + (context, localScopeInfo) = await this.InternalLoadScopeInfoAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - await runner.CommitAsync().ConfigureAwait(false); + await runner.CommitAsync().ConfigureAwait(false); - return (context, localScopeInfo); + return (context, localScopeInfo); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } /// /// Internal load a scope by scope name /// - internal virtual async Task<(SyncContext context, ScopeInfo scopeInfo)> InternalLoadScopeInfoAsync(SyncContext context, + internal virtual async Task<(SyncContext context, ScopeInfo scopeInfo)> InternalLoadScopeInfoAsync(SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + try + { + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetScopeInfo, runner.Connection, runner.Transaction); + using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetScopeInfo, runner.Connection, runner.Transaction); - if (command == null) return (context, null); + if (command == null) return (context, null); - SetParameterValue(command, "sync_scope_name", context.ScopeName); + SetParameterValue(command, "sync_scope_name", context.ScopeName); - var action = new ScopeInfoLoadingArgs(context, context.ScopeName, command, runner.Connection, runner.Transaction); - await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); + var action = new ScopeInfoLoadingArgs(context, context.ScopeName, command, runner.Connection, runner.Transaction); + await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, null); + if (action.Cancel || action.Command == null) + return (context, null); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - using DbDataReader reader = await action.Command.ExecuteReaderAsync().ConfigureAwait(false); + using DbDataReader reader = await action.Command.ExecuteReaderAsync().ConfigureAwait(false); - ScopeInfo scopeInfo = null; + ScopeInfo scopeInfo = null; - if (reader.Read()) - scopeInfo = InternalReadScopeInfo(reader); + if (reader.Read()) + scopeInfo = InternalReadScopeInfo(reader); - reader.Close(); + reader.Close(); - if (scopeInfo?.Schema != null) - scopeInfo.Schema.EnsureSchema(); + if (scopeInfo?.Schema != null) + scopeInfo.Schema.EnsureSchema(); - var scopeLoadedArgs = new ScopeInfoLoadedArgs(context, scopeInfo, runner.Connection, runner.Transaction); - await this.InterceptAsync(scopeLoadedArgs, progress, cancellationToken).ConfigureAwait(false); - scopeInfo = scopeLoadedArgs.ScopeInfo; + var scopeLoadedArgs = new ScopeInfoLoadedArgs(context, scopeInfo, runner.Connection, runner.Transaction); + await this.InterceptAsync(scopeLoadedArgs, progress, cancellationToken).ConfigureAwait(false); + scopeInfo = scopeLoadedArgs.ScopeInfo; - action.Command.Dispose(); + action.Command.Dispose(); - return (context, scopeInfo); + return (context, scopeInfo); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } /// /// Internal exists scope /// - internal async Task<(SyncContext context, bool exists)> InternalExistsScopeInfoAsync(string scopeName, SyncContext context, + internal async Task<(SyncContext context, bool exists)> InternalExistsScopeInfoAsync(string scopeName, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - // Get exists command - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + try + { + // Get exists command + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - using var existsCommand = scopeBuilder.GetCommandAsync(DbScopeCommandType.ExistScopeInfo, runner.Connection, runner.Transaction); + using var existsCommand = scopeBuilder.GetCommandAsync(DbScopeCommandType.ExistScopeInfo, runner.Connection, runner.Transaction); - if (existsCommand == null) return (context, false); + if (existsCommand == null) return (context, false); - SetParameterValue(existsCommand, "sync_scope_name", scopeName); + SetParameterValue(existsCommand, "sync_scope_name", scopeName); - if (existsCommand == null) - return (context, false); + if (existsCommand == null) + return (context, false); - await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); - var exists = Convert.ToInt32(existsResultObject) > 0; - return (context, exists); + var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); + var exists = Convert.ToInt32(existsResultObject) > 0; + return (context, exists); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } /// /// Internal load all scopes. scopeName arg is just here for getting context /// - internal async Task<(SyncContext context, List scopeInfos)> InternalLoadAllScopeInfosAsync(SyncContext context, + internal async Task<(SyncContext context, List scopeInfos)> InternalLoadAllScopeInfosAsync(SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + try + { + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetAllScopeInfos, runner.Connection, runner.Transaction); + using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.GetAllScopeInfos, runner.Connection, runner.Transaction); - if (command == null) return (context, null); + if (command == null) return (context, null); - var clientScopes = new List(); + var clientScopes = new List(); - await this.InterceptAsync(new ExecuteCommandArgs(context, command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - using DbDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(false); + using DbDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(false); - while (reader.Read()) - { - var scopeInfo = InternalReadScopeInfo(reader); + while (reader.Read()) + { + var scopeInfo = InternalReadScopeInfo(reader); - if (scopeInfo.Schema != null) - scopeInfo.Schema.EnsureSchema(); + if (scopeInfo.Schema != null) + scopeInfo.Schema.EnsureSchema(); - clientScopes.Add(scopeInfo); - } + clientScopes.Add(scopeInfo); + } - reader.Close(); + reader.Close(); - command.Dispose(); + command.Dispose(); - return (context, clientScopes); + return (context, clientScopes); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } /// /// Internal upsert scope info in a scope table /// - internal async Task<(SyncContext context, ScopeInfo clientScopeInfo)> InternalSaveScopeInfoAsync(ScopeInfo scopeInfo, SyncContext context, + internal async Task<(SyncContext context, ScopeInfo clientScopeInfo)> InternalSaveScopeInfoAsync(ScopeInfo scopeInfo, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + try + { + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeWriting, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeWriting, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - bool scopeExists; - (context, scopeExists) = await InternalExistsScopeInfoAsync(scopeInfo.Name, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + bool scopeExists; + (context, scopeExists) = await InternalExistsScopeInfoAsync(scopeInfo.Name, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - DbCommand command; - if (scopeExists) - command = scopeBuilder.GetCommandAsync(DbScopeCommandType.UpdateScopeInfo, runner.Connection, runner.Transaction); - else - command = scopeBuilder.GetCommandAsync(DbScopeCommandType.InsertScopeInfo, runner.Connection, runner.Transaction); + DbCommand command; + if (scopeExists) + command = scopeBuilder.GetCommandAsync(DbScopeCommandType.UpdateScopeInfo, runner.Connection, runner.Transaction); + else + command = scopeBuilder.GetCommandAsync(DbScopeCommandType.InsertScopeInfo, runner.Connection, runner.Transaction); - if (command == null) return (context, null); + if (command == null) return (context, null); - command = InternalSetSaveScopeInfoParameters(scopeInfo, command); + command = InternalSetSaveScopeInfoParameters(scopeInfo, command); - var action = new ScopeInfoSavingArgs(context, scopeInfo, command, runner.Connection, runner.Transaction); - await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); + var action = new ScopeInfoSavingArgs(context, scopeInfo, command, runner.Connection, runner.Transaction); + await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, null); + if (action.Cancel || action.Command == null) + return (context, null); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - using DbDataReader reader = await action.Command.ExecuteReaderAsync().ConfigureAwait(false); + using DbDataReader reader = await action.Command.ExecuteReaderAsync().ConfigureAwait(false); - reader.Read(); + reader.Read(); - scopeInfo = InternalReadScopeInfo(reader); + scopeInfo = InternalReadScopeInfo(reader); - reader.Close(); + reader.Close(); - await this.InterceptAsync(new ScopeInfoSavedArgs(context, scopeInfo, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - action.Command.Dispose(); + await this.InterceptAsync(new ScopeInfoSavedArgs(context, scopeInfo, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + action.Command.Dispose(); - return (context, scopeInfo); + return (context, scopeInfo); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } /// /// Internal delete scope info in a scope table /// - internal async Task<(SyncContext context, bool deleted)> InternalDeleteScopeInfoAsync(ScopeInfo scopeInfo, SyncContext context, + internal async Task<(SyncContext context, bool deleted)> InternalDeleteScopeInfoAsync(ScopeInfo scopeInfo, SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + try + { + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeWriting, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.ScopeWriting, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - bool scopeExists; - (context, scopeExists) = await InternalExistsScopeInfoAsync(scopeInfo.Name, context, - runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + bool scopeExists; + (context, scopeExists) = await InternalExistsScopeInfoAsync(scopeInfo.Name, context, + runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (!scopeExists) - return (context, true); + if (!scopeExists) + return (context, true); - using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.DeleteScopeInfo, runner.Connection, runner.Transaction); + using var command = scopeBuilder.GetCommandAsync(DbScopeCommandType.DeleteScopeInfo, runner.Connection, runner.Transaction); - InternalSetDeleteScopeInfoParameters(scopeInfo, command); + InternalSetDeleteScopeInfoParameters(scopeInfo, command); - var action = new ScopeInfoSavingArgs(context, scopeInfo, command, runner.Connection, runner.Transaction); - await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); + var action = new ScopeInfoSavingArgs(context, scopeInfo, command, runner.Connection, runner.Transaction); + await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - await this.InterceptAsync(new ScopeInfoSavedArgs(context, scopeInfo, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - action.Command.Dispose(); + await this.InterceptAsync(new ScopeInfoSavedArgs(context, scopeInfo, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + action.Command.Dispose(); - return (context, true); + return (context, true); + } + catch (Exception ex) + { + throw GetSyncError(context, ex); + } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopesTables.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopesTables.cs index 25310825c..a9e0d88af 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopesTables.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/BaseOrchestrator.ScopesTables.cs @@ -97,7 +97,7 @@ public async Task ExistScopeInfoTableAsync(DbConnection connection = null, await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); bool exists; - (context, exists) = await InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, + (context, exists) = await InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); await runner.CommitAsync().ConfigureAwait(false); @@ -126,7 +126,7 @@ public async Task ExistScopeInfoClientTableAsync(DbConnection connection = await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); bool exists; - (context, exists) = await InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, + (context, exists) = await InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); return exists; @@ -142,28 +142,37 @@ public async Task ExistScopeInfoClientTableAsync(DbConnection connection = /// internal virtual async Task<(SyncContext context, bool exists)> InternalExistsScopeInfoTableAsync(SyncContext context, DbScopeType scopeType, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - - var scopeCommandType = scopeType switch + try { - DbScopeType.ScopeInfo => DbScopeCommandType.ExistsScopeInfoTable, - DbScopeType.ScopeInfoClient => DbScopeCommandType.ExistsScopeInfoClientTable, - _ => throw new NotImplementedException() - }; + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); + var scopeCommandType = scopeType switch + { + DbScopeType.ScopeInfo => DbScopeCommandType.ExistsScopeInfoTable, + DbScopeType.ScopeInfoClient => DbScopeCommandType.ExistsScopeInfoClientTable, + _ => throw new NotImplementedException() + }; - // Get exists command - using var existsCommand = scopeBuilder.GetCommandAsync(scopeCommandType, runner.Connection, runner.Transaction); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); + + // Get exists command + using var existsCommand = scopeBuilder.GetCommandAsync(scopeCommandType, runner.Connection, runner.Transaction); - if (existsCommand == null) - return (context, false); + if (existsCommand == null) + return (context, false); - await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, existsCommand, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); - var exists = Convert.ToInt32(existsResultObject) > 0; - return (context, exists); + var existsResultObject = await existsCommand.ExecuteScalarAsync().ConfigureAwait(false); + var exists = Convert.ToInt32(existsResultObject) > 0; + return (context, exists); + } + catch (Exception ex) + { + string message = null; + message += $"ScopeType:{scopeType}."; + throw GetSyncError(context, ex, message); + } } /// @@ -171,36 +180,46 @@ public async Task ExistScopeInfoClientTableAsync(DbConnection connection = /// internal virtual async Task<(SyncContext context, bool dropped)> InternalDropScopeInfoTableAsync(SyncContext context, DbScopeType scopeType, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - - var scopeCommandType = scopeType switch + try { - DbScopeType.ScopeInfo => DbScopeCommandType.DropScopeInfoTable, - DbScopeType.ScopeInfoClient => DbScopeCommandType.DropScopeInfoClientTable, - _ => throw new NotImplementedException() - }; - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); + + var scopeCommandType = scopeType switch + { + DbScopeType.ScopeInfo => DbScopeCommandType.DropScopeInfoTable, + DbScopeType.ScopeInfoClient => DbScopeCommandType.DropScopeInfoClientTable, + _ => throw new NotImplementedException() + }; + + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); - using var command = scopeBuilder.GetCommandAsync(scopeCommandType, runner.Connection, runner.Transaction); + using var command = scopeBuilder.GetCommandAsync(scopeCommandType, runner.Connection, runner.Transaction); - if (command == null) return (context, false); + if (command == null) return (context, false); - var action = new ScopeInfoTableDroppingArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeType, command, runner.Connection, runner.Transaction); - await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); + var action = new ScopeInfoTableDroppingArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeType, command, runner.Connection, runner.Transaction); + await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); - if (action.Cancel || action.Command == null) - return (context, false); + if (action.Cancel || action.Command == null) + return (context, false); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), progress, cancellationToken).ConfigureAwait(false); - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - await this.InterceptAsync(new ScopeInfoTableDroppedArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeType, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await this.InterceptAsync(new ScopeInfoTableDroppedArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeType, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - action.Command.Dispose(); + action.Command.Dispose(); - return (context, true); + return (context, true); + } + catch (Exception ex) + { + string message = null; + message += $"ScopeType:{scopeType}."; + throw GetSyncError(context, ex, message); + } } /// @@ -208,38 +227,46 @@ public async Task ExistScopeInfoClientTableAsync(DbConnection connection = /// internal virtual async Task<(SyncContext context, bool created)> InternalCreateScopeInfoTableAsync(SyncContext context, DbScopeType scopeType, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - - var scopeCommandType = scopeType switch + try { - DbScopeType.ScopeInfo => DbScopeCommandType.CreateScopeInfoTable, - DbScopeType.ScopeInfoClient => DbScopeCommandType.CreateScopeInfoClientTable, - _ => throw new NotImplementedException() - }; + var scopeBuilder = this.GetScopeBuilder(this.Options.ScopeInfoTableName); - await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); + var scopeCommandType = scopeType switch + { + DbScopeType.ScopeInfo => DbScopeCommandType.CreateScopeInfoTable, + DbScopeType.ScopeInfoClient => DbScopeCommandType.CreateScopeInfoClientTable, + _ => throw new NotImplementedException() + }; - using var command = scopeBuilder.GetCommandAsync(scopeCommandType, runner.Connection, runner.Transaction); + await using var runner = await this.GetConnectionAsync(context, SyncMode.NoTransaction, SyncStage.None, connection, transaction).ConfigureAwait(false); - if (command == null) - return (context, false); + using var command = scopeBuilder.GetCommandAsync(scopeCommandType, runner.Connection, runner.Transaction); - var action = new ScopeInfoTableCreatingArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeType, command, runner.Connection, runner.Transaction); - await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); + if (command == null) + return (context, false); - if (action.Cancel || action.Command == null) - return (context, false); + var action = new ScopeInfoTableCreatingArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeType, command, runner.Connection, runner.Transaction); + await this.InterceptAsync(action, progress, cancellationToken).ConfigureAwait(false); - await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + if (action.Cancel || action.Command == null) + return (context, false); - await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); + await this.InterceptAsync(new ExecuteCommandArgs(context, action.Command, default, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - await this.InterceptAsync(new ScopeInfoTableCreatedArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeType, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); + await action.Command.ExecuteNonQueryAsync().ConfigureAwait(false); - action.Command.Dispose(); + await this.InterceptAsync(new ScopeInfoTableCreatedArgs(context, scopeBuilder.ScopeInfoTableName.ToString(), scopeType, runner.Connection, runner.Transaction), runner.Progress, runner.CancellationToken).ConfigureAwait(false); - return (context, true); - } + action.Command.Dispose(); + return (context, true); + } + catch (Exception ex) + { + string message = null; + message += $"ScopeType:{scopeType}."; + throw GetSyncError(context, ex, message); + } + } } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/LocalOrchestrator.ScopeInfos.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/LocalOrchestrator.ScopeInfos.cs index 3301dd731..a942cde4b 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/LocalOrchestrator.ScopeInfos.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/LocalOrchestrator.ScopeInfos.cs @@ -69,35 +69,41 @@ public virtual Task GetScopeInfoAsync(DbConnection connection = null, internal virtual async Task<(SyncContext context, ScopeInfo serverScopeInfo)> InternalEnsureScopeInfoAsync(SyncContext context, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - - bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (!exists) - await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + try + { + await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (!exists) - await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + bool exists; + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (!exists) + await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - ScopeInfo cScopeInfo; - bool cScopeInfoExists; - (context, cScopeInfoExists) = await this.InternalExistsScopeInfoAsync(context.ScopeName, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (!exists) + await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (!cScopeInfoExists) - { - cScopeInfo = this.InternalCreateScopeInfo(context.ScopeName); - (context, cScopeInfo) = await this.InternalSaveScopeInfoAsync(cScopeInfo, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + ScopeInfo cScopeInfo; + bool cScopeInfoExists; + (context, cScopeInfoExists) = await this.InternalExistsScopeInfoAsync(context.ScopeName, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + + if (!cScopeInfoExists) + { + cScopeInfo = this.InternalCreateScopeInfo(context.ScopeName); + (context, cScopeInfo) = await this.InternalSaveScopeInfoAsync(cScopeInfo, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + } + else + { + (context, cScopeInfo) = await this.InternalLoadScopeInfoAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + } + + await runner.CommitAsync().ConfigureAwait(false); + + return (context, cScopeInfo); } - else + catch (Exception ex) { - (context, cScopeInfo) = await this.InternalLoadScopeInfoAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + throw GetSyncError(context, ex); } - - await runner.CommitAsync().ConfigureAwait(false); - - return (context, cScopeInfo); - } /// @@ -109,47 +115,68 @@ public virtual Task GetScopeInfoAsync(DbConnection connection = null, if (clientScopeInfo.Schema == null) return (context, false, clientScopeInfo, serverScopeInfo); - if (inputSetup != null && clientScopeInfo.Setup != null && !clientScopeInfo.Setup.EqualsByProperties(inputSetup)) + try { - var conflictingSetupArgs = new ConflictingSetupArgs(context, inputSetup, clientScopeInfo, serverScopeInfo); - await this.InterceptAsync(conflictingSetupArgs, progress, cancellationToken).ConfigureAwait(false); + 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.Rollback) + throw new SetupConflictOnClientException(inputSetup, clientScopeInfo.Setup); - if (conflictingSetupArgs.Action == ConflictingSetupAction.Abort) - return (context, true, clientScopeInfo, serverScopeInfo); + if (conflictingSetupArgs.Action == ConflictingSetupAction.Abort) + return (context, true, clientScopeInfo, serverScopeInfo); - // re affect scope infos - clientScopeInfo = conflictingSetupArgs.ClientScopeInfo; - serverScopeInfo = conflictingSetupArgs.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 (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.Rollback) + throw new SetupConflictOnClientException(clientScopeInfo.Setup, clientScopeInfo.Setup); - if (conflictingSetupArgs.Action == ConflictingSetupAction.Abort) - return (context, true, clientScopeInfo, serverScopeInfo); + if (conflictingSetupArgs.Action == ConflictingSetupAction.Abort) + return (context, true, clientScopeInfo, serverScopeInfo); - // re affect scope infos - clientScopeInfo = conflictingSetupArgs.ClientScopeInfo; - serverScopeInfo = conflictingSetupArgs.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."); + // 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 SetupConflictOnClientException(clientScopeInfo.Setup, clientScopeInfo.Setup); - return (context, false, clientScopeInfo, serverScopeInfo); + return (context, false, clientScopeInfo, serverScopeInfo); - } + } + catch (SetupConflictOnClientException) + { + // direct throw because message is already really long and we don't want to duplicate it + throw; + } + catch (Exception ex) + { + string message = null; + + if (inputSetup != null) + message += $"Input Setup:{JsonConvert.SerializeObject(inputSetup)}."; + if (clientScopeInfo != null && clientScopeInfo.Setup != null) + message += $"Client Scope Setup:{JsonConvert.SerializeObject(clientScopeInfo.Setup)}."; + if (serverScopeInfo != null && serverScopeInfo.Setup != null) + message += $"Server Scope Setup:{JsonConvert.SerializeObject(serverScopeInfo.Setup)}."; + + throw GetSyncError(context, ex, message); + } + } } } diff --git a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/RemoteOrchestrator.ScopeInfos.cs b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/RemoteOrchestrator.ScopeInfos.cs index 4591b428b..0ad9a6b60 100644 --- a/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/RemoteOrchestrator.ScopeInfos.cs +++ b/Projects/Dotmim.Sync.Core/Orchestrators/Scopes/RemoteOrchestrator.ScopeInfos.cs @@ -63,7 +63,7 @@ public virtual async Task GetScopeInfoAsync(string scopeName, DbConne } /// - public virtual Task GetScopeInfoAsync(DbConnection connection = default, DbTransaction transaction = default) + public virtual Task GetScopeInfoAsync(DbConnection connection = default, DbTransaction transaction = default) => GetScopeInfoAsync(SyncOptions.DefaultScopeName, connection, transaction); @@ -91,7 +91,7 @@ public virtual Task GetScopeInfoAsync(DbConnection connection = defau /// Optional Connection /// Optional Transaction /// - public virtual Task GetScopeInfoAsync(SyncSetup setup, DbConnection connection = default, DbTransaction transaction = default) + public virtual Task GetScopeInfoAsync(SyncSetup setup, DbConnection connection = default, DbTransaction transaction = default) => GetScopeInfoAsync(SyncOptions.DefaultScopeName, setup, connection, transaction); @@ -117,116 +117,145 @@ public virtual async Task GetScopeInfoAsync(string scopeName, SyncSet internal virtual async Task<(SyncContext context, ScopeInfo serverScopeInfo, bool shouldProvision)> InternalEnsureScopeInfoAsync(SyncContext context, SyncSetup setup, bool overwrite, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress progress) { - bool shouldProvision = false; - await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); + try + { + bool shouldProvision = false; - bool exists; - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (!exists) - await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + await using var runner = await this.GetConnectionAsync(context, SyncMode.WithTransaction, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); - (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (!exists) - await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + bool exists; + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (!exists) + await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfo, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - ScopeInfo sScopeInfo; - bool sScopeInfoExists; - (context, sScopeInfoExists) = await this.InternalExistsScopeInfoAsync(context.ScopeName, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + (context, exists) = await this.InternalExistsScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (!exists) + await this.InternalCreateScopeInfoTableAsync(context, DbScopeType.ScopeInfoClient, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - bool shouldSave = false; + ScopeInfo sScopeInfo; + bool sScopeInfoExists; + (context, sScopeInfoExists) = await this.InternalExistsScopeInfoAsync(context.ScopeName, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (!sScopeInfoExists) - { - sScopeInfo = this.InternalCreateScopeInfo(context.ScopeName); - shouldSave = true; - } - else - { - (context, sScopeInfo) = await this.InternalLoadScopeInfoAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - } + bool shouldSave = false; - // if serverscopeinfo is a new, because we never run any sync before, grab schema and affect setup - if (setup != null && setup.Tables.Count > 0) - { - if ((sScopeInfo.Setup == null && sScopeInfo.Schema == null) || overwrite) + if (!sScopeInfoExists) { - SyncSet schema; - (context, schema) = await this.InternalGetSchemaAsync(context, setup, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - sScopeInfo.Setup = setup; - sScopeInfo.Schema = schema; - - // Checking if we have already some scopes - // Then gets the first scope to get the tracking tables & sp prefixes - List allScopes; - (context, allScopes) = await this.InternalLoadAllScopeInfosAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + sScopeInfo = this.InternalCreateScopeInfo(context.ScopeName); + shouldSave = true; + } + else + { + (context, sScopeInfo) = await this.InternalLoadScopeInfoAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + } - if (allScopes.Count > 0) + // if serverscopeinfo is a new, because we never run any sync before, grab schema and affect setup + if (setup != null && setup.Tables.Count > 0) + { + if ((sScopeInfo.Setup == null && sScopeInfo.Schema == null) || overwrite) { - // Get the first scope with an existing setup - var firstScope = allScopes.FirstOrDefault(sc => sc.Setup != null); + SyncSet schema; + (context, schema) = await this.InternalGetSchemaAsync(context, setup, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + sScopeInfo.Setup = setup; + sScopeInfo.Schema = schema; + + // Checking if we have already some scopes + // Then gets the first scope to get the tracking tables & sp prefixes + List allScopes; + (context, allScopes) = await this.InternalLoadAllScopeInfosAsync(context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - if (firstScope != null) + if (allScopes.Count > 0) { - if (sScopeInfo.Setup.TrackingTablesPrefix != firstScope.Setup.TrackingTablesPrefix) - throw new Exception($"Can't add a new setup with different tracking table prefix. Please use same tracking table prefix as your first setup ([\"{firstScope.Setup.TrackingTablesPrefix}\"])"); + // Get the first scope with an existing setup + var firstScope = allScopes.FirstOrDefault(sc => sc.Setup != null); - if (sScopeInfo.Setup.TrackingTablesSuffix != firstScope.Setup.TrackingTablesSuffix) - throw new Exception($"Can't add a new setup with different tracking table suffix. Please use same tracking table suffix as your first setup ([\"{firstScope.Setup.TrackingTablesSuffix}\"])"); + if (firstScope != null) + { + if (sScopeInfo.Setup.TrackingTablesPrefix != firstScope.Setup.TrackingTablesPrefix) + throw new Exception($"Can't add a new setup with different tracking table prefix. Please use same tracking table prefix as your first setup ([\"{firstScope.Setup.TrackingTablesPrefix}\"])"); - if (sScopeInfo.Setup.TriggersPrefix != firstScope.Setup.TriggersPrefix) - throw new Exception($"Can't add a new setup with different trigger prefix. Please use same trigger prefix as your first setup ([\"{firstScope.Setup.TriggersPrefix}\"])"); + if (sScopeInfo.Setup.TrackingTablesSuffix != firstScope.Setup.TrackingTablesSuffix) + throw new Exception($"Can't add a new setup with different tracking table suffix. Please use same tracking table suffix as your first setup ([\"{firstScope.Setup.TrackingTablesSuffix}\"])"); - if (sScopeInfo.Setup.TriggersSuffix != firstScope.Setup.TriggersSuffix) - throw new Exception($"Can't add a new setup with different trigger suffix. Please use same trigger suffix as your first setup ([\"{firstScope.Setup.TriggersSuffix}\"])"); + if (sScopeInfo.Setup.TriggersPrefix != firstScope.Setup.TriggersPrefix) + throw new Exception($"Can't add a new setup with different trigger prefix. Please use same trigger prefix as your first setup ([\"{firstScope.Setup.TriggersPrefix}\"])"); + + if (sScopeInfo.Setup.TriggersSuffix != firstScope.Setup.TriggersSuffix) + throw new Exception($"Can't add a new setup with different trigger suffix. Please use same trigger suffix as your first setup ([\"{firstScope.Setup.TriggersSuffix}\"])"); + } } - } - shouldSave = true; - shouldProvision = true; + shouldSave = true; + shouldProvision = true; + } } - } - if (shouldSave) - (context, sScopeInfo) = await this.InternalSaveScopeInfoAsync(sScopeInfo, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); + if (shouldSave) + (context, sScopeInfo) = await this.InternalSaveScopeInfoAsync(sScopeInfo, context, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); - await runner.CommitAsync().ConfigureAwait(false); + await runner.CommitAsync().ConfigureAwait(false); - return (context, sScopeInfo, shouldProvision); + return (context, sScopeInfo, shouldProvision); + } + catch (Exception ex) + { + string message = null; + + message += $"Overwrite:{overwrite}."; + + throw GetSyncError(context, ex, message); + } } /// /// Check /// - internal virtual async Task<(SyncContext, bool, ScopeInfo)> InternalIsConflictingSetupAsync(SyncContext context, SyncSetup inputSetup, ScopeInfo sScopeInfo, + internal virtual async Task<(SyncContext, bool, ScopeInfo)> InternalIsConflictingSetupAsync(SyncContext context, SyncSetup inputSetup, ScopeInfo sScopeInfo, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress progress = null) { if (sScopeInfo.Schema == null) return (context, false, sScopeInfo); - if (inputSetup != null && sScopeInfo.Setup != null && !sScopeInfo.Setup.EqualsByProperties(inputSetup)) + try { - var conflictingSetupArgs = new ConflictingSetupArgs(context, inputSetup, null, sScopeInfo, connection, transaction); - await this.InterceptAsync(conflictingSetupArgs, progress, cancellationToken).ConfigureAwait(false); + if (inputSetup != null && sScopeInfo.Setup != null && !sScopeInfo.Setup.EqualsByProperties(inputSetup)) + { + var conflictingSetupArgs = new ConflictingSetupArgs(context, inputSetup, null, sScopeInfo, connection, transaction); + 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 server scope database. Please create a new scope or deprovision and provision again your server scope."); + if (conflictingSetupArgs.Action == ConflictingSetupAction.Rollback) + throw new SetupConflictOnServerException(inputSetup, sScopeInfo.Setup); - if (conflictingSetupArgs.Action == ConflictingSetupAction.Abort) - return (context, true, sScopeInfo); + if (conflictingSetupArgs.Action == ConflictingSetupAction.Abort) + return (context, true, sScopeInfo); - // re affect scope infos - sScopeInfo = conflictingSetupArgs.ServerScopeInfo; - } + // re affect scope infos + sScopeInfo = 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 (inputSetup != null && sScopeInfo.Setup != null && !sScopeInfo.Setup.EqualsByProperties(inputSetup)) - throw new Exception("Seems you are trying another Setup tables that what is stored in your server scope database. Please make a migration or create a new scope"); + // We gave 2 chances to user to edit the setup and fill correct values. + // Final check, but if not valid, raise an error + if (inputSetup != null && sScopeInfo.Setup != null && !sScopeInfo.Setup.EqualsByProperties(inputSetup)) + throw new SetupConflictOnServerException(inputSetup, sScopeInfo.Setup); - return (context, false, sScopeInfo); - } + return (context, false, sScopeInfo); + } + catch (SetupConflictOnServerException) + { + // direct throw because message is already really long and we don't want to duplicate it + throw; + } + catch (Exception ex) + { + string message = null; + if (inputSetup != null) + message += $"Input Setup:{JsonConvert.SerializeObject(inputSetup)}."; + if (sScopeInfo != null && sScopeInfo.Setup != null) + message += $"Server Setup:{JsonConvert.SerializeObject(sScopeInfo.Setup)}."; + throw GetSyncError(context, ex, message); + } + } } } diff --git a/Projects/Dotmim.Sync.Core/SyncAgent.cs b/Projects/Dotmim.Sync.Core/SyncAgent.cs index c3803ed7a..75ea45d9c 100644 --- a/Projects/Dotmim.Sync.Core/SyncAgent.cs +++ b/Projects/Dotmim.Sync.Core/SyncAgent.cs @@ -5,9 +5,11 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Data.Common; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -440,7 +442,7 @@ await this.RemoteOrchestrator.InternalApplyThenGetChangesAsync( context.ProgressPercentage = 0.75; (context, clientSyncChanges, cScopeInfoClient) = await this.LocalOrchestrator.InternalApplyChangesAsync( - cScopeInfo, cScopeInfoClient, context, serverSyncChanges, clientSyncChanges, reverseConflictResolutionPolicy, snapshotApplied, default, default, + cScopeInfo, cScopeInfoClient, context, serverSyncChanges, clientSyncChanges, reverseConflictResolutionPolicy, snapshotApplied, default, default, cancellationToken, progress).ConfigureAwait(false); completeTime = DateTime.UtcNow; @@ -461,16 +463,34 @@ await this.RemoteOrchestrator.InternalApplyThenGetChangesAsync( } - catch (SyncException se) + catch (Exception exception) { - this.Options.Logger.LogError(SyncEventsId.Exception, se, se.TypeName); - syncException = se; - throw; - } - catch (Exception ex) - { - this.Options.Logger.LogCritical(SyncEventsId.Exception, ex, ex.Message); - syncException = new SyncException(ex, SyncStage.None); + // First we log the error before adding a new layer + if (this.Options.Logger != null) + this.Options.Logger.LogError(SyncEventsId.Exception, exception, exception.Message); + + string message = exception is SyncException se && se.BaseMessage != null ? se.BaseMessage : exception.Message; + if (this.Options.UseVerboseErrors) + { + var innerException = exception.InnerException; + int cpt = 1; + while (innerException != null) + { + message += Environment.NewLine; + var sign = innerException.InnerException != null ? "├" : "└"; + message += sign; + + for (int i = 0; i < cpt; i++) + message += "─"; + + message += $" {innerException.Message}"; + + innerException = innerException.InnerException; + cpt++; + } + } + + syncException = new SyncException(exception, message); throw syncException; } finally diff --git a/Projects/Dotmim.Sync.Core/SyncException.cs b/Projects/Dotmim.Sync.Core/SyncException.cs index 507149641..e69600ff6 100644 --- a/Projects/Dotmim.Sync.Core/SyncException.cs +++ b/Projects/Dotmim.Sync.Core/SyncException.cs @@ -1,5 +1,6 @@ using Dotmim.Sync.Enumerations; using Dotmim.Sync.Serialization; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Data; @@ -20,21 +21,38 @@ namespace Dotmim.Sync /// public class SyncException : Exception { - public SyncException(string message) : base(message) + public SyncException(string message, SyncStage stage = SyncStage.None) : base(message) { + this.SyncStage = stage; } - public SyncException(Exception exception, SyncStage stage = SyncStage.None) : base(exception.Message, exception) + public SyncException(Exception innerException, SyncStage stage = SyncStage.None) : base(innerException.Message, innerException) { this.SyncStage = stage; - if (exception is null) + if (innerException is null) return; - this.TypeName = exception.GetType().Name; + + this.TypeName = innerException.GetType().Name; } + public SyncException(Exception innerException, string message, SyncStage stage = SyncStage.None) : base(message, innerException) + { + this.SyncStage = stage; + + if (innerException is null) + return; + + this.TypeName = innerException.GetType().Name; + } + + /// + /// Base message + /// + public string BaseMessage { get; set; } + /// /// Gets or Sets type name of exception /// @@ -160,15 +178,6 @@ public class MissingConnectionException : Exception public MissingConnectionException() : base(message) { } } - /// - /// Occurs when a file is missing - /// - public class MissingFileException : Exception - { - const string message = "File {0} doesn't exist."; - - public MissingFileException(string fileName) : base(string.Format(message, fileName)) { } - } /// /// Occurs when a schema is needed, but does not exists @@ -239,9 +248,48 @@ public MissingPrimaryKeyException(string tableName) : base(string.Format(message /// public class MissingTableException : Exception { - const string message = "Table {0} does not exists in your scope info setup (scope name : {1})."; + const string message = "Table {0} does not exists in your scope info setup."; + + public MissingTableException(string tableName, string schemaName) : base(string.Format(message, string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}")) { } + } + + + /// + /// Setup Conflict, when setup provided by the user in code is different from the one in database. + /// + public class SetupConflictOnClientException : Exception + { + const string message = "Seems you are trying another Setup that what is stored in your client scope database.\n" + + "You have already made a sync with a setup that has been stored in the client database.\n" + + "And you are trying now a new setup in your code, different from the one you have used before.\n" + + "If you want to use 2 differents setups, please use a different a scope name for each setup.\n" + + "If you want to replace the setup stored in database with a new one, make a migration (see docs).\n" + + "-----------------------------------------------------\n" + + "Setup you trying to use from your code: {0}\n" + + "-----------------------------------------------------\n" + + "Setup found in your database: {1}\n" + + "-----------------------------------------------------\n"; + + public SetupConflictOnClientException(SyncSetup inputSetup, SyncSetup clientScopeInfoSetup) : base(string.Format(message, JsonConvert.SerializeObject(inputSetup), JsonConvert.SerializeObject(clientScopeInfoSetup))) { } + } + + /// + /// Setup Conflict, when setup provided by the user in code is different from the one in database. + /// + public class SetupConflictOnServerException : Exception + { + const string message = "Seems you are trying another Setup that what is stored in your server scope database.\n" + + "You have already made a sync with a setup that has been stored in the server (and client) database.\n" + + "And you are trying now a new setup in your code, different from the one you have used before.\n" + + "If you want to use 2 differents setups, please use a different a scope name for each setup.\n" + + "If you want to replace the setup stored in database with a new one, make a migration (see docs).\n" + + "-----------------------------------------------------\n" + + "Setup you trying to use from your code: {0}\n" + + "-----------------------------------------------------\n" + + "Setup found in your database: {1}\n" + + "-----------------------------------------------------\n"; - public MissingTableException(string tableName, string schemaName, string scopeName) : base(string.Format(message, string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}", scopeName)) { } + public SetupConflictOnServerException(SyncSetup inputSetup, SyncSetup clientScopeInfoSetup) : base(string.Format(message, JsonConvert.SerializeObject(inputSetup), JsonConvert.SerializeObject(clientScopeInfoSetup))) { } } /// @@ -289,9 +337,9 @@ public MissingProviderException(string methodName) : base(string.Format(message, /// public class MissingTablesException : Exception { - const string message = "Your setup with scope name {0} does not contains any table."; + const string message = "Your setup does not contains any table."; - public MissingTablesException(string scopeName) : base(string.Format(message, scopeName)) { } + public MissingTablesException() : base(message) { } } /// diff --git a/Samples/Dotmim.Sync.SampleConsole/Program.cs b/Samples/Dotmim.Sync.SampleConsole/Program.cs index 78c54b386..fac43afe0 100644 --- a/Samples/Dotmim.Sync.SampleConsole/Program.cs +++ b/Samples/Dotmim.Sync.SampleConsole/Program.cs @@ -49,7 +49,7 @@ internal class Program public static string[] allTables = new string[] {"ProductDescription", "ProductCategory", "ProductModel", "Product", "Address", "Customer", "CustomerAddress", - "SalesOrderHeader", "SalesOrderDetail" }; + "SalesOrderHeader", "SalesOrderDetail"}; public static string[] oneTable = new string[] { "ProductCategory" }; @@ -62,8 +62,8 @@ private static async Task Main(string[] args) //var serverProvider = new MariaDBSyncProvider(DBHelper.GetMariadbDatabaseConnectionString(serverDbName)); //var serverProvider = new MySqlSyncProvider(DBHelper.GetMySqlDatabaseConnectionString(serverDbName)); - var clientProvider = new SqliteSyncProvider(Path.GetRandomFileName().Replace(".", "").ToLowerInvariant() + ".db"); - //var clientProvider = new SqlSyncProvider(DBHelper.GetDatabaseConnectionString(clientDbName)); + //var clientProvider = new SqliteSyncProvider(Path.GetRandomFileName().Replace(".", "").ToLowerInvariant() + ".db"); + var clientProvider = new SqlSyncProvider(DBHelper.GetDatabaseConnectionString(clientDbName)); //clientProvider.UseBulkOperations = false; //var clientProvider = new MariaDBSyncProvider(DBHelper.GetMariadbDatabaseConnectionString(clientDbName)); @@ -75,6 +75,7 @@ private static async Task Main(string[] args) var options = new SyncOptions(); //options.Logger = new SyncLogger().AddDebug().SetMinimumLevel(LogLevel.Information); + //options.UseVerboseErrors = true; //setup.Tables["ProductCategory"].Columns.AddRange(new string[] { "ProductCategoryID", "ParentProductCategoryID", "Name" }); @@ -111,55 +112,7 @@ private static async Task Main(string[] args) //await GenerateErrorsAsync(); - // await SynchronizeAsync(clientProvider, serverProvider, setup, options); - - await SynchronizeBigTablesAsync(); - } - - private static async Task EditEntityOnceUploadedAsync(CoreProvider clientProvider, CoreProvider serverProvider, SyncSetup setup, SyncOptions options, string scopeName = SyncOptions.DefaultScopeName) - { - var progress = new SynchronousProgress(s => - Console.WriteLine($"{s.ProgressPercentage:p}: \t[{s?.Source[..Math.Min(4, s.Source.Length)]}] {s.TypeName}: {s.Message}")); - - // Creating an agent that will handle all the process - var agent = new SyncAgent(clientProvider, serverProvider, options); - - var results = await agent.SynchronizeAsync(setup, progress: progress); - - await DBHelper.AddProductCategoryRowAsync(clientProvider); - - agent.RemoteOrchestrator.OnRowsChangesApplied(args => - { - if (args.SchemaTable.TableName == "ProductCategory") - { - var command = args.Connection.CreateCommand(); - command.CommandText = "Update ProductCategory Set Name=@Name where ProductCategoryID=@ProductCategoryID"; - command.Connection = args.Connection; - command.Transaction = args.Transaction; - - var pId = command.CreateParameter(); - pId.ParameterName = "@ProductCategoryID"; - pId.DbType = DbType.Guid; - command.Parameters.Add(pId); - - var pName = command.CreateParameter(); - pName = command.CreateParameter(); - pName.ParameterName = "@Name"; - pName.DbType = DbType.String; - command.Parameters.Add(pName); - - foreach (var row in args.SyncRows) - { - pId.Value = new Guid(row["ProductCategoryId"].ToString()); - pName.Value = $"SV_{row["Name"]}"; - - command.ExecuteNonQuery(); - } - } - }); - results = await agent.SynchronizeAsync(setup, progress: progress); - - Console.WriteLine("Sync Ended. Press a key to start again, or Escapte to end"); + await SynchronizeAsync(clientProvider, serverProvider, setup, options); } private static async Task SynchronizeAsync(CoreProvider clientProvider, CoreProvider serverProvider, SyncSetup setup, SyncOptions options, string scopeName = SyncOptions.DefaultScopeName) @@ -170,16 +123,20 @@ private static async Task SynchronizeAsync(CoreProvider clientProvider, CoreProv var progress = new SynchronousProgress(s => Console.WriteLine($"{s.ProgressPercentage:p}: \t[{s?.Source[..Math.Min(4, s.Source.Length)]}] {s.TypeName}: {s.Message}")); + options.UseVerboseErrors = true; + options.DbCommandTimeout = 1; // Creating an agent that will handle all the process var agent = new SyncAgent(clientProvider, serverProvider, options); + //setup.Tables["Address"].Columns.AddRange("AddressID", "CreatedDate", "ModifiedDate2"); + do { try { Console.Clear(); Console.ForegroundColor = ConsoleColor.Green; - var s = await agent.SynchronizeAsync(setup, progress: progress); + var s = await agent.SynchronizeAsync(scopeName, setup, progress: progress); Console.ResetColor(); Console.WriteLine(s); @@ -228,7 +185,7 @@ private static async Task SynchronizeBigTablesAsync() Console.WriteLine(s); Console.ForegroundColor = ConsoleColor.Green; - var s2 = await agent.SynchronizeAsync(scopeName2, setup2, syncType:SyncType.Reinitialize, progress: progress); + var s2 = await agent.SynchronizeAsync(scopeName2, setup2, syncType: SyncType.Reinitialize, progress: progress); Console.ResetColor(); Console.WriteLine(s2); diff --git a/docs/Interceptors.rst b/docs/Interceptors.rst index adaffd841..7e589f061 100644 --- a/docs/Interceptors.rst +++ b/docs/Interceptors.rst @@ -355,6 +355,34 @@ OnTableChangesApplying }); +OnBatchChangesApplying +------------------------------- + +| The ``OnBatchChangesApplying`` interceptor is happening when a batch for a particular table is about to be applied on the local data source. +| The number of rows contained in each batch file is depending on the value you have set in your SyncOptions instance : ``SyncOptions.BatchSize`` (Default is 2 Mo) +| This interceptor is called for each batch file, and for each state (``Modified`` / ``Deleted``). +| That means that if you have **1000** batches, and **2** calls of this interceptor (one for ``Modified``, one for ``Deleted``), you will fire **2000** times this interceptor. + +.. code-block:: csharp + + agent.LocalOrchestrator.OnBatchChangesApplying(async args => + { + if (args.BatchPartInfo != null) + { + Console.WriteLine($"FileName:{args.BatchPartInfo.FileName}. RowsCount:{args.BatchPartInfo.RowsCount} "); + Console.WriteLine($"Applying rows from this batch part info:"); + + var table = await agent.LocalOrchestrator.LoadTableFromBatchPartInfoAsync(args.BatchInfo, + args.BatchPartInfo, args.State, args.Connection, args.Transaction); + + foreach (var row in table.Rows) + Console.WriteLine(row); + + } + }); + + + OnRowsChangesApplying ----------------------------------- @@ -385,6 +413,33 @@ The ``OnTableChangesApplied`` interceptor is happening when all rows, for a spec TODO + +OnBatchChangesApplying +------------------------------- + +| The ``OnBatchChangesApplied`` interceptor is happening when a batch for a particular table has been applied. + +.. code-block:: csharp + + agent.LocalOrchestrator.OnBatchChangesApplied(async args => + { + if (args.BatchPartInfo != null) + { + Console.WriteLine($"FileName:{args.BatchPartInfo.FileName}. RowsCount:{args.BatchPartInfo.RowsCount} "); + Console.WriteLine($"Applied rows from this batch part info:"); + + var table = await agent.LocalOrchestrator.LoadTableFromBatchPartInfoAsync(args.BatchInfo, + args.BatchPartInfo, args.State, args.Connection, args.Transaction); + + foreach (var row in table.Rows) + Console.WriteLine(row); + + } + }); + + + + OnDatabaseChangesApplied ------------------------------- diff --git a/docs/Orchestrators.rst b/docs/Orchestrators.rst index d1cc4d5b0..9388dcbba 100644 --- a/docs/Orchestrators.rst +++ b/docs/Orchestrators.rst @@ -85,18 +85,14 @@ Creating a stored procedure could be done like this: .. code-block:: csharp var provider = new SqlSyncProvider(serverConnectionString); - var options = new SyncOptions(); - var setup = new SyncSetup(new string[] { "ProductCategory", "ProductModel", "Product" }); - var orchestrator = new RemoteOrchestrator(provider, options, setup); - - // working on the product Table - var productSetupTable = setup.Tables["Product"]; + var remoteOrchestrator = new RemoteOrchestrator(provider, options); + var scopeInfo = await remoteOrchestrator.GetScopeInfoAsync(setup); - var spExists = await orchestrator.ExistStoredProcedureAsync(productSetupTable, - DbStoredProcedureType.SelectChanges); + var spExists = await orchestrator.ExistStoredProcedureAsync(scopeInfo, "Product", null, + DbStoredProcedureType.SelectChanges); if (!spExists) - await orchestrator.CreateStoredProcedureAsync(productSetupTable, - DbStoredProcedureType.SelectChanges); + await orchestrator.CreateStoredProcedureAsync(scopeInfo, "Product", null, + DbStoredProcedureType.SelectChanges); .. image:: https://user-images.githubusercontent.com/4592555/103882421-11683000-50dc-11eb-8805-d2fe79342f12.png @@ -111,16 +107,12 @@ Continuing on the last sample, we can create in the same way, the tracking table .. code-block:: csharp var provider = new SqlSyncProvider(serverConnectionString); - var options = new SyncOptions(); - var setup = new SyncSetup(new string[] { "ProductCategory", "ProductModel", "Product" }); - var orchestrator = new RemoteOrchestrator(provider, options, setup); - - // working on the product Table - var productSetupTable = setup.Tables["Product"]; + var remoteOrchestrator = new RemoteOrchestrator(provider, options); + var scopeInfo = await remoteOrchestrator.GetScopeInfoAsync(setup); - var spExists = await orchestrator.ExistTrackingTableAsync(productSetupTable); + var spExists = await remoteOrchestrator.ExistTrackingTableAsync(scopeInfo, "Employee"); if (!spExists) - await orchestrator.CreateTrackingTableAsync(productSetupTable); + await remoteOrchestrator.CreateTrackingTableAsync(scopeInfo, "Employee"); .. image:: https://user-images.githubusercontent.com/4592555/103882789-99e6d080-50dc-11eb-824d-47e564a91fa7.png @@ -132,14 +124,18 @@ Now we can drop this newly created stored procedure and tracking table: .. code-block:: csharp - var trExists = await orchestrator.ExistTrackingTableAsync(productSetupTable); + var provider = new SqlSyncProvider(serverConnectionString); + var remoteOrchestrator = new RemoteOrchestrator(provider, options); + var scopeInfo = await remoteOrchestrator.GetScopeInfoAsync(setup); + + var trExists = await orchestrator.ExistTrackingTableAsync(scopeInfo, "Employee"); if (trExists) - await orchestrator.DropTrackingTableAsync(productSetupTable); + await orchestrator.DropTrackingTableAsync(scopeInfo, "Employee"); - var spExists = await orchestrator.ExistStoredProcedureAsync(productSetupTable, + var spExists = await orchestrator.ExistStoredProcedureAsync(scopeInfo, "Employee", null, DbStoredProcedureType.SelectChanges); if (spExists) - await orchestrator.DropStoredProcedureAsync(productSetupTable, + await orchestrator.DropStoredProcedureAsync(scopeInfo, "Employee", null, DbStoredProcedureType.SelectChanges);