/// <summary> /// Gets a value indicating whether the DmState has changes /// </summary> public bool HasChanges(DmRowState rowStates) { try { const DmRowState allRowStates = DmRowState.Detached | DmRowState.Unchanged | DmRowState.Added | DmRowState.Deleted | DmRowState.Modified; if ((rowStates & (~allRowStates)) != 0) { throw new ArgumentOutOfRangeException(nameof(rowStates)); } for (int i = 0; i < Tables.Count; i++) { DmTable table = Tables[i]; for (int j = 0; j < table.Rows.Count; j++) { DmRow row = table.Rows[j]; if ((row.RowState & rowStates) != 0) { return(true); } } } return(false); } finally { } }
public AppliedChangesEventArgs(DmView changes, DmRowState state, DbConnection connection, DbTransaction transaction) { this.Changes = changes; this.State = state; this.Connection = connection; this.Transaction = transaction; this.Action = ChangeApplicationAction.Continue; }
private void ConvertToSurrogateRecords(DmRow row) { int count = row.Table.Columns.Count; DmRowState rowState = row.RowState; DmRowVersion rowVersion = rowState == DmRowState.Deleted ? DmRowVersion.Original : DmRowVersion.Current; for (int i = 0; i < count; i++) { this.Records[i].Add(row[i, rowVersion]); } }
public DmView(DmTable table, DmRowState state) { if (table == null) { return; } this.Table = table; Predicate <DmRow> filter = new Predicate <DmRow>(r => r.RowState == state); this.internalFilter(filter, this.Table.Rows); }
/// <summary> /// Get a DmRow state to know is we have an inserted, updated, or deleted row to apply /// </summary> private DmRowState GetStateFromDmRow(DmRow dataRow, ScopeInfo scopeInfo) { DmRowState dmRowState = DmRowState.Unchanged; var isTombstone = Convert.ToInt64(dataRow["sync_row_is_tombstone"]) > 0; if (isTombstone) { dmRowState = DmRowState.Deleted; } else { var createdTimeStamp = DbManager.ParseTimestamp(dataRow["create_timestamp"]); var updatedTimeStamp = DbManager.ParseTimestamp(dataRow["update_timestamp"]); var updateScopeIdRow = dataRow["update_scope_id"]; var createScopeIdRow = dataRow["create_scope_id"]; Guid?updateScopeId = (updateScopeIdRow != DBNull.Value && updateScopeIdRow != null) ? (Guid)updateScopeIdRow : (Guid?)null; Guid?createScopeId = (createScopeIdRow != DBNull.Value && createScopeIdRow != null) ? (Guid)createScopeIdRow : (Guid?)null; var isLocallyCreated = !createScopeId.HasValue; var islocallyUpdated = !updateScopeId.HasValue || updateScopeId.Value != scopeInfo.Id; // Check if a row is modified : // 1) Row is not new // 2) Row update is AFTER last sync of asker // 3) Row insert is BEFORE last sync of asker (if insert is after last sync, it's not an update, it's an insert) if (!scopeInfo.IsNewScope && islocallyUpdated && updatedTimeStamp > scopeInfo.Timestamp && (createdTimeStamp <= scopeInfo.Timestamp || !isLocallyCreated)) { dmRowState = DmRowState.Modified; } else if (scopeInfo.IsNewScope || (isLocallyCreated && createdTimeStamp >= scopeInfo.Timestamp)) { dmRowState = DmRowState.Added; } // The line has been updated from an other host else if (islocallyUpdated && updateScopeId.HasValue && updateScopeId.Value != scopeInfo.Id) { dmRowState = DmRowState.Modified; } else { dmRowState = DmRowState.Unchanged; Debug.WriteLine($"Row is in Unchanegd state. " + $"\tscopeInfo.Id:{scopeInfo.Id}, scopeInfo.IsNewScope :{scopeInfo.IsNewScope}, scopeInfo.LastTimestamp:{scopeInfo.Timestamp}" + $"\tcreateScopeId:{createScopeId}, updateScopeId:{updateScopeId}, createdTimeStamp:{createdTimeStamp}, updatedTimeStamp:{updatedTimeStamp}."); } } return(dmRowState); }
public DmSet GetChanges(DmRowState rowStates) { DmSet dsNew = null; if (0 != (rowStates & ~(DmRowState.Added | DmRowState.Deleted | DmRowState.Modified | DmRowState.Unchanged))) { throw new Exception($"InvalidRowState {rowStates}"); } // Initialize all the individual table bitmaps. TableChanges[] bitMatrix = new TableChanges[Tables.Count]; for (int i = 0; i < bitMatrix.Length; ++i) { bitMatrix[i] = new TableChanges(Tables[i].Rows.Count); } // find all the modified rows and their parents MarkModifiedRows(bitMatrix, rowStates); // copy the changes to a cloned table for (int i = 0; i < bitMatrix.Length; ++i) { Debug.Assert(0 <= bitMatrix[i].HasChanges, "negative change count"); if (0 < bitMatrix[i].HasChanges) { if (dsNew == null) { dsNew = this.Clone(); } DmTable table = this.Tables[i]; DmTable destTable = dsNew.Tables.First(t => t.TableName == table.TableName); for (int j = 0; 0 < bitMatrix[i].HasChanges; ++j) { // Loop through the rows. if (bitMatrix[i][j]) { destTable.ImportRow(table.Rows[j]); bitMatrix[i].HasChanges--; } } } } return(dsNew); }
/// <summary> /// Get Changes from the DmTable, with a DmRowState value /// </summary> public DmTable GetChanges(DmRowState rowStates) { DmTable dtChanges = this.Clone(); DmRow row = null; for (int i = 0; i < Rows.Count; i++) { row = Rows[i]; if ((row.RowState & rowStates) == row.RowState) { dtChanges.ImportRow(row); } } if (dtChanges.Rows.Count == 0) { return(null); } return(dtChanges); }
private void MarkModifiedRows(TableChanges[] bitMatrix, DmRowState rowStates) { // for every table, every row & every relation find the modified rows and for non-deleted rows, their parents for (int tableIndex = 0; tableIndex < bitMatrix.Length; ++tableIndex) { var rows = Tables[tableIndex].Rows; int rowCount = rows.Count; for (int rowIndex = 0; rowIndex < rowCount; ++rowIndex) { DmRow row = rows[rowIndex]; DmRowState rowState = row.RowState; // if bit not already set and row is modified if (((rowStates & rowState) != 0) && !bitMatrix[tableIndex][rowIndex]) { bitMatrix[tableIndex][rowIndex] = true; } } } }
/// <summary> /// Enumerate all internal changes, no batch mode /// </summary> internal async Task <(BatchInfo, ChangesSelected)> EnumerateChangesInBatchesInternal (SyncContext context, ScopeInfo scopeInfo, int downloadBatchSizeInKB, DmSet configTables, string batchDirectory, ConflictResolutionPolicy policy, ICollection <FilterClause> filters) { DmTable dmTable = null; // memory size total double memorySizeFromDmRows = 0L; int batchIndex = 0; // this batch info won't be in memory, it will be be batched BatchInfo batchInfo = new BatchInfo { // directory where all files will be stored Directory = BatchInfo.GenerateNewDirectoryName(), // not in memory since we serialized all files in the tmp directory InMemory = false }; // Create stats object to store changes count ChangesSelected changes = new ChangesSelected(); using (var connection = this.CreateConnection()) { try { // Open the connection await connection.OpenAsync(); using (var transaction = connection.BeginTransaction()) { // create the in memory changes set DmSet changesSet = new DmSet(configTables.DmSetName); foreach (var tableDescription in configTables.Tables) { // if we are in upload stage, so check if table is not download only if (context.SyncWay == SyncWay.Upload && tableDescription.SyncDirection == SyncDirection.DownloadOnly) { continue; } // if we are in download stage, so check if table is not download only if (context.SyncWay == SyncWay.Download && tableDescription.SyncDirection == SyncDirection.UploadOnly) { continue; } var builder = this.GetDatabaseBuilder(tableDescription); var syncAdapter = builder.CreateSyncAdapter(connection, transaction); syncAdapter.ConflictApplyAction = SyncConfiguration.GetApplyAction(policy); // raise before event context.SyncStage = SyncStage.TableChangesSelecting; var beforeArgs = new TableChangesSelectingEventArgs(this.ProviderTypeName, context.SyncStage, tableDescription.TableName); this.TryRaiseProgressEvent(beforeArgs, this.TableChangesSelecting); // Get Command DbCommand selectIncrementalChangesCommand; DbCommandType dbCommandType; if (this.CanBeServerProvider && context.Parameters != null && context.Parameters.Count > 0 && filters != null && filters.Count > 0) { var filtersName = filters .Where(f => f.TableName.Equals(tableDescription.TableName, StringComparison.InvariantCultureIgnoreCase)) .Select(f => f.ColumnName); if (filtersName != null && filtersName.Count() > 0) { dbCommandType = DbCommandType.SelectChangesWitFilters; selectIncrementalChangesCommand = syncAdapter.GetCommand(dbCommandType, filtersName); } else { dbCommandType = DbCommandType.SelectChanges; selectIncrementalChangesCommand = syncAdapter.GetCommand(dbCommandType); } } else { dbCommandType = DbCommandType.SelectChanges; selectIncrementalChangesCommand = syncAdapter.GetCommand(dbCommandType); } // Deriving Parameters syncAdapter.SetCommandParameters(DbCommandType.SelectChanges, selectIncrementalChangesCommand); if (selectIncrementalChangesCommand == null) { var exc = "Missing command 'SelectIncrementalChangesCommand' "; throw new Exception(exc); } dmTable = BuildChangesTable(tableDescription.TableName, configTables); try { // Set commons parameters SetSelectChangesCommonParameters(context, scopeInfo, selectIncrementalChangesCommand); // Set filter parameters if any // Only on server side if (this.CanBeServerProvider && context.Parameters != null && context.Parameters.Count > 0 && filters != null && filters.Count > 0) { var filterTable = filters.Where(f => f.TableName.Equals(tableDescription.TableName, StringComparison.InvariantCultureIgnoreCase)).ToList(); if (filterTable != null && filterTable.Count > 0) { foreach (var filter in filterTable) { var parameter = context.Parameters.FirstOrDefault(p => p.ColumnName.Equals(filter.ColumnName, StringComparison.InvariantCultureIgnoreCase) && p.TableName.Equals(filter.TableName, StringComparison.InvariantCultureIgnoreCase)); if (parameter != null) { DbManager.SetParameterValue(selectIncrementalChangesCommand, parameter.ColumnName, parameter.Value); } } } } this.AddTrackingColumns <int>(dmTable, "sync_row_is_tombstone"); // Statistics TableChangesSelected tableChangesSelected = new TableChangesSelected { TableName = tableDescription.TableName }; changes.TableChangesSelected.Add(tableChangesSelected); // Get the reader using (var dataReader = selectIncrementalChangesCommand.ExecuteReader()) { while (dataReader.Read()) { DmRow dmRow = CreateRowFromReader(dataReader, dmTable); DmRowState state = DmRowState.Unchanged; state = GetStateFromDmRow(dmRow, scopeInfo); // If the row is not deleted inserted or modified, go next if (state != DmRowState.Deleted && state != DmRowState.Modified && state != DmRowState.Added) { continue; } var fieldsSize = DmTableSurrogate.GetRowSizeFromDataRow(dmRow); var dmRowSize = fieldsSize / 1024d; if (dmRowSize > downloadBatchSizeInKB) { var exc = $"Row is too big ({dmRowSize} kb.) for the current Configuration.DownloadBatchSizeInKB ({downloadBatchSizeInKB} kb.) Aborting Sync..."; throw new Exception(exc); } // Calculate the new memory size memorySizeFromDmRows = memorySizeFromDmRows + dmRowSize; // add row dmTable.Rows.Add(dmRow); tableChangesSelected.TotalChanges++; // acceptchanges before modifying dmRow.AcceptChanges(); // Set the correct state to be applied if (state == DmRowState.Deleted) { dmRow.Delete(); tableChangesSelected.Deletes++; } else if (state == DmRowState.Added) { dmRow.SetAdded(); tableChangesSelected.Inserts++; } else if (state == DmRowState.Modified) { dmRow.SetModified(); tableChangesSelected.Updates++; } // We exceed the memorySize, so we can add it to a batch if (memorySizeFromDmRows > downloadBatchSizeInKB) { // Since we dont need this column anymore, remove it this.RemoveTrackingColumns(dmTable, "sync_row_is_tombstone"); changesSet.Tables.Add(dmTable); // generate the batch part info batchInfo.GenerateBatchInfo(batchIndex, changesSet, batchDirectory); // increment batch index batchIndex++; changesSet.Clear(); // Recreate an empty DmSet, then a dmTable clone changesSet = new DmSet(configTables.DmSetName); dmTable = dmTable.Clone(); this.AddTrackingColumns <int>(dmTable, "sync_row_is_tombstone"); // Init the row memory size memorySizeFromDmRows = 0L; // add stats for a SyncProgress event context.SyncStage = SyncStage.TableChangesSelected; var args2 = new TableChangesSelectedEventArgs (this.ProviderTypeName, SyncStage.TableChangesSelected, tableChangesSelected); this.TryRaiseProgressEvent(args2, this.TableChangesSelected); } } // Since we dont need this column anymore, remove it this.RemoveTrackingColumns(dmTable, "sync_row_is_tombstone"); context.SyncStage = SyncStage.TableChangesSelected; changesSet.Tables.Add(dmTable); // Init the row memory size memorySizeFromDmRows = 0L; // Event progress context.SyncStage = SyncStage.TableChangesSelected; var args = new TableChangesSelectedEventArgs(this.ProviderTypeName, SyncStage.TableChangesSelected, tableChangesSelected); this.TryRaiseProgressEvent(args, this.TableChangesSelected); } } catch (Exception) { throw; } finally { } } // We are in batch mode, and we are at the last batchpart info if (changesSet != null && changesSet.HasTables && changesSet.HasChanges()) { var batchPartInfo = batchInfo.GenerateBatchInfo(batchIndex, changesSet, batchDirectory); if (batchPartInfo != null) { batchPartInfo.IsLastBatch = true; } } transaction.Commit(); } } catch (Exception) { throw; } finally { if (connection != null && connection.State == ConnectionState.Open) { connection.Close(); } } } return(batchInfo, changes); }
/// <summary> /// Enumerate all internal changes, no batch mode /// </summary> internal async Task <(BatchInfo, ChangesSelected)> EnumerateChangesInternal( SyncContext context, ScopeInfo scopeInfo, DmSet configTables, string batchDirectory, ConflictResolutionPolicy policy, ICollection <FilterClause> filters) { // create the in memory changes set DmSet changesSet = new DmSet(SyncConfiguration.DMSET_NAME); // Create the batch info, in memory var batchInfo = new BatchInfo { InMemory = true }; using (var connection = this.CreateConnection()) { // Open the connection await connection.OpenAsync(); using (var transaction = connection.BeginTransaction()) { try { // changes that will be returned as selected changes ChangesSelected changes = new ChangesSelected(); foreach (var tableDescription in configTables.Tables) { // if we are in upload stage, so check if table is not download only if (context.SyncWay == SyncWay.Upload && tableDescription.SyncDirection == SyncDirection.DownloadOnly) { continue; } // if we are in download stage, so check if table is not download only if (context.SyncWay == SyncWay.Download && tableDescription.SyncDirection == SyncDirection.UploadOnly) { continue; } var builder = this.GetDatabaseBuilder(tableDescription); var syncAdapter = builder.CreateSyncAdapter(connection, transaction); syncAdapter.ConflictApplyAction = SyncConfiguration.GetApplyAction(policy); // raise before event context.SyncStage = SyncStage.TableChangesSelecting; var beforeArgs = new TableChangesSelectingEventArgs(this.ProviderTypeName, context.SyncStage, tableDescription.TableName); this.TryRaiseProgressEvent(beforeArgs, this.TableChangesSelecting); // selected changes for the current table TableChangesSelected tableSelectedChanges = new TableChangesSelected { TableName = tableDescription.TableName }; // Get Command DbCommand selectIncrementalChangesCommand; DbCommandType dbCommandType; if (this.CanBeServerProvider && context.Parameters != null && context.Parameters.Count > 0 && filters != null && filters.Count > 0) { var filtersName = filters .Where(f => f.TableName.Equals(tableDescription.TableName, StringComparison.InvariantCultureIgnoreCase)) .Select(f => f.ColumnName); if (filtersName != null && filtersName.Count() > 0) { dbCommandType = DbCommandType.SelectChangesWitFilters; selectIncrementalChangesCommand = syncAdapter.GetCommand(dbCommandType, filtersName); } else { dbCommandType = DbCommandType.SelectChanges; selectIncrementalChangesCommand = syncAdapter.GetCommand(dbCommandType); } } else { dbCommandType = DbCommandType.SelectChanges; selectIncrementalChangesCommand = syncAdapter.GetCommand(dbCommandType); } if (selectIncrementalChangesCommand == null) { var exc = "Missing command 'SelectIncrementalChangesCommand' "; throw new Exception(exc); } // Deriving Parameters syncAdapter.SetCommandParameters(dbCommandType, selectIncrementalChangesCommand); // Get a clone of the table with tracking columns var dmTableChanges = BuildChangesTable(tableDescription.TableName, configTables); SetSelectChangesCommonParameters(context, scopeInfo, selectIncrementalChangesCommand); // Set filter parameters if any if (this.CanBeServerProvider && context.Parameters != null && context.Parameters.Count > 0 && filters != null && filters.Count > 0) { var tableFilters = filters.Where(f => f.TableName.Equals(tableDescription.TableName, StringComparison.InvariantCultureIgnoreCase)).ToList(); if (tableFilters != null && tableFilters.Count > 0) { foreach (var filter in tableFilters) { var parameter = context.Parameters.FirstOrDefault(p => p.ColumnName.Equals(filter.ColumnName, StringComparison.InvariantCultureIgnoreCase) && p.TableName.Equals(filter.TableName, StringComparison.InvariantCultureIgnoreCase)); if (parameter != null) { DbManager.SetParameterValue(selectIncrementalChangesCommand, parameter.ColumnName, parameter.Value); } } } } this.AddTrackingColumns <int>(dmTableChanges, "sync_row_is_tombstone"); // Get the reader using (var dataReader = selectIncrementalChangesCommand.ExecuteReader()) { while (dataReader.Read()) { DmRow dataRow = CreateRowFromReader(dataReader, dmTableChanges); //DmRow dataRow = dmTableChanges.NewRow(); // assuming the row is not inserted / modified DmRowState state = DmRowState.Unchanged; // get if the current row is inserted, modified, deleted state = GetStateFromDmRow(dataRow, scopeInfo); if (state != DmRowState.Deleted && state != DmRowState.Modified && state != DmRowState.Added) { continue; } // add row dmTableChanges.Rows.Add(dataRow); // acceptchanges before modifying dataRow.AcceptChanges(); tableSelectedChanges.TotalChanges++; // Set the correct state to be applied if (state == DmRowState.Deleted) { dataRow.Delete(); tableSelectedChanges.Deletes++; } else if (state == DmRowState.Added) { dataRow.SetAdded(); tableSelectedChanges.Inserts++; } else if (state == DmRowState.Modified) { dataRow.SetModified(); tableSelectedChanges.Updates++; } } // Since we dont need this column anymore, remove it this.RemoveTrackingColumns(dmTableChanges, "sync_row_is_tombstone"); // add it to the DmSet changesSet.Tables.Add(dmTableChanges); } // add the stats to global stats changes.TableChangesSelected.Add(tableSelectedChanges); // Raise event for this table context.SyncStage = SyncStage.TableChangesSelected; var args = new TableChangesSelectedEventArgs(this.ProviderTypeName, SyncStage.TableChangesSelected, tableSelectedChanges); this.TryRaiseProgressEvent(args, this.TableChangesSelected); } transaction.Commit(); // generate the batchpartinfo batchInfo.GenerateBatchInfo(0, changesSet, batchDirectory); // Create a new in-memory batch info with an the changes DmSet return(batchInfo, changes); } catch (Exception) { throw; } finally { if (connection != null && connection.State == ConnectionState.Open) { connection.Close(); } } } } }
/// <summary> /// Construct a row from a dmTable, a rowState and the bitIndex /// </summary> private DmRow ConstructRow(DmTable dt, DmRowState rowState, int bitIndex) { DmRow dmRow = dt.NewRow(); int count = dt.Columns.Count; dmRow.BeginEdit(); for (int i = 0; i < count; i++) { object dmRowObject = this.Records[i][bitIndex]; // Sometimes, a serializer could potentially serialize type into string // For example JSON.Net will serialize GUID into STRING // So we try to deserialize in correct type if (dmRowObject != null) { var columnType = dt.Columns[i].DataType; var dmRowObjectType = dmRowObject.GetType(); if (dmRowObjectType != columnType && columnType != typeof(object)) { if (columnType == typeof(Guid) && (dmRowObject as string) != null) { dmRowObject = new Guid(dmRowObject.ToString()); } if (columnType == typeof(Guid) && (dmRowObject.GetType() == typeof(byte[]))) { dmRowObject = new Guid((byte[])dmRowObject); } else if (columnType == typeof(Int32) && dmRowObjectType != typeof(Int32)) { dmRowObject = Convert.ToInt32(dmRowObject); } else if (columnType == typeof(UInt32) && dmRowObjectType != typeof(UInt32)) { dmRowObject = Convert.ToUInt32(dmRowObject); } else if (columnType == typeof(Int16) && dmRowObjectType != typeof(Int16)) { dmRowObject = Convert.ToInt16(dmRowObject); } else if (columnType == typeof(UInt16) && dmRowObjectType != typeof(UInt16)) { dmRowObject = Convert.ToUInt16(dmRowObject); } else if (columnType == typeof(Int64) && dmRowObjectType != typeof(Int64)) { dmRowObject = Convert.ToInt64(dmRowObject); } else if (columnType == typeof(UInt64) && dmRowObjectType != typeof(UInt64)) { dmRowObject = Convert.ToUInt64(dmRowObject); } else if (columnType == typeof(Byte) && dmRowObjectType != typeof(Byte)) { dmRowObject = Convert.ToByte(dmRowObject); } else if (columnType == typeof(Char) && dmRowObjectType != typeof(Char)) { dmRowObject = Convert.ToChar(dmRowObject); } else if (columnType == typeof(DateTime) && dmRowObjectType != typeof(DateTime)) { dmRowObject = Convert.ToDateTime(dmRowObject); } else if (columnType == typeof(Decimal) && dmRowObjectType != typeof(Decimal)) { dmRowObject = Convert.ToDecimal(dmRowObject); } else if (columnType == typeof(Double) && dmRowObjectType != typeof(Double)) { dmRowObject = Convert.ToDouble(dmRowObject); } else if (columnType == typeof(SByte) && dmRowObjectType != typeof(SByte)) { dmRowObject = Convert.ToSByte(dmRowObject); } else if (columnType == typeof(Single) && dmRowObjectType != typeof(Single)) { dmRowObject = Convert.ToSingle(dmRowObject); } else if (columnType == typeof(String) && dmRowObjectType != typeof(String)) { dmRowObject = Convert.ToString(dmRowObject); } else if (columnType == typeof(Boolean) && dmRowObjectType != typeof(Boolean)) { dmRowObject = Convert.ToBoolean(dmRowObject); } else if (columnType == typeof(Byte[]) && dmRowObjectType != typeof(Byte[]) && dmRowObjectType == typeof(String)) { dmRowObject = Convert.FromBase64String(dmRowObject.ToString()); } else if (dmRowObjectType != columnType) { var t = dmRowObject.GetType(); var converter = columnType.GetConverter(); if (converter != null && converter.CanConvertFrom(t)) { dmRowObject = converter.ConvertFrom(dmRowObject); } } } } if (rowState == DmRowState.Deleted) { // Since some columns might be not null (and we have null because the row is deleted) if (this.Records[i][bitIndex] != null) { dmRow[i] = dmRowObject; } } else { dmRow[i] = dmRowObject; } } dt.Rows.Add(dmRow); switch (rowState) { case DmRowState.Unchanged: { dmRow.AcceptChanges(); dmRow.EndEdit(); return(dmRow); } case DmRowState.Added: { dmRow.EndEdit(); return(dmRow); } case DmRowState.Deleted: { dmRow.AcceptChanges(); dmRow.Delete(); dmRow.EndEdit(); return(dmRow); } case DmRowState.Modified: { dmRow.AcceptChanges(); dmRow.SetModified(); dmRow.EndEdit(); return(dmRow); } default: throw new ArgumentException("InvalidRowState"); } }
private DmRow ConvertToDmRow(DmTable dt, int bitIndex) { DmRowState rowState = (DmRowState)this.RowStates[bitIndex]; return(this.ConstructRow(dt, rowState, bitIndex)); }
/// <summary> /// Apply changes internal method for one Insert or Update or Delete for every dbSyncAdapter /// </summary> internal ChangeApplicationAction ApplyChangesInternal( SyncContext context, MessageApplyChanges message, DbConnection connection, DbTransaction transaction, DmRowState applyType, ChangesApplied changesApplied) { ChangeApplicationAction changeApplicationAction = ChangeApplicationAction.Continue; // for each adapters (Zero to End for Insert / Updates -- End to Zero for Deletes for (int i = 0; i < message.Schema.Tables.Count; i++) { // If we have a delete we must go from Up to Down, orthewise Dow to Up index var tableDescription = (applyType != DmRowState.Deleted ? message.Schema.Tables[i] : message.Schema.Tables[message.Schema.Tables.Count - i - 1]); // if we are in upload stage, so check if table is not download only if (context.SyncWay == SyncWay.Upload && tableDescription.SyncDirection == SyncDirection.DownloadOnly) { continue; } // if we are in download stage, so check if table is not download only if (context.SyncWay == SyncWay.Download && tableDescription.SyncDirection == SyncDirection.UploadOnly) { continue; } var builder = this.GetDatabaseBuilder(tableDescription); var syncAdapter = builder.CreateSyncAdapter(connection, transaction); syncAdapter.ConflictApplyAction = SyncConfiguration.GetApplyAction(message.Policy); // Set syncAdapter properties syncAdapter.applyType = applyType; // Get conflict handler resolver if (syncAdapter.ConflictActionInvoker == null && this.ApplyChangedFailed != null) { syncAdapter.ConflictActionInvoker = GetConflictAction; } if (message.Changes.BatchPartsInfo != null && message.Changes.BatchPartsInfo.Count > 0) { // getting the table to be applied // we may have multiple batch files, so we can have multipe dmTable with the same Name // We can say that dmTable may be contained in several files foreach (DmTable dmTablePart in message.Changes.GetTable(tableDescription.TableName)) { if (dmTablePart == null || dmTablePart.Rows.Count == 0) { continue; } // check and filter var dmChangesView = new DmView(dmTablePart, (r) => r.RowState == applyType); if (dmChangesView.Count == 0) { dmChangesView.Dispose(); dmChangesView = null; continue; } // Conflicts occured when trying to apply rows List <SyncConflict> conflicts = new List <SyncConflict>(); // Raise event progress only if there are rows to be applied context.SyncStage = SyncStage.TableChangesApplying; var args = new TableChangesApplyingEventArgs(this.ProviderTypeName, context.SyncStage, tableDescription.TableName, applyType); this.TryRaiseProgressEvent(args, this.TableChangesApplying); int rowsApplied; // applying the bulkchanges command if (message.UseBulkOperations && this.SupportBulkOperations) { rowsApplied = syncAdapter.ApplyBulkChanges(dmChangesView, message.FromScope, conflicts); } else { rowsApplied = syncAdapter.ApplyChanges(dmChangesView, message.FromScope, conflicts); } // If conflicts occured // Eventuall, conflicts are resolved on server side. if (conflicts != null && conflicts.Count > 0) { foreach (var conflict in conflicts) { //var scopeBuilder = this.GetScopeBuilder(); //var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(message.ScopeInfoTableName, connection, transaction); //var localTimeStamp = scopeInfoBuilder.GetLocalTimestamp(); var fromScopeLocalTimeStamp = message.FromScope.Timestamp; changeApplicationAction = syncAdapter.HandleConflict(conflict, message.Policy, message.FromScope, fromScopeLocalTimeStamp, out DmRow resolvedRow); if (changeApplicationAction == ChangeApplicationAction.Continue) { // row resolved if (resolvedRow != null) { rowsApplied++; } } else { context.TotalSyncErrors++; // TODO : Should we break at the first error ? return(ChangeApplicationAction.Rollback); } } } // Get all conflicts resolved context.TotalSyncConflicts = conflicts.Where(c => c.Type != ConflictType.ErrorsOccurred).Sum(c => 1); // Handle sync progress for this syncadapter (so this table) var changedFailed = dmChangesView.Count - rowsApplied; // raise SyncProgress Event var existAppliedChanges = changesApplied.TableChangesApplied.FirstOrDefault( sc => string.Equals(sc.TableName, tableDescription.TableName) && sc.State == applyType); if (existAppliedChanges == null) { existAppliedChanges = new TableChangesApplied { TableName = tableDescription.TableName, Applied = rowsApplied, Failed = changedFailed, State = applyType }; changesApplied.TableChangesApplied.Add(existAppliedChanges); } else { existAppliedChanges.Applied += rowsApplied; existAppliedChanges.Failed += changedFailed; } // Event progress context.SyncStage = SyncStage.TableChangesApplied; var progressEventArgs = new TableChangesAppliedEventArgs(this.ProviderTypeName, context.SyncStage, existAppliedChanges); this.TryRaiseProgressEvent(progressEventArgs, this.TableChangesApplied); } } // Dispose conflict handler resolver if (syncAdapter.ConflictActionInvoker != null) { syncAdapter.ConflictActionInvoker = null; } } return(ChangeApplicationAction.Continue); }
/// <summary> /// Construct a row from a dmTable, a rowState and the bitIndex /// </summary> private DmRow ConstructRow(DmTable dt, DmRowState rowState, int bitIndex) { DmRow dmRow = dt.NewRow(); int count = dt.Columns.Count; dmRow.BeginEdit(); for (int i = 0; i < count; i++) { object dmRowObject = this.Records[i][bitIndex]; // Sometimes, a serializer could potentially serialize type into string // For example JSON.Net will serialize GUID into STRING // So we try to deserialize in correct type if (dmRowObject != null) { var columnType = dt.Columns[i].DataType; if (columnType == typeof(Guid) && (dmRowObject as string) != null) { dmRowObject = new Guid(dmRowObject.ToString()); } else if (columnType == typeof(Int32) && dmRowObject.GetType() != typeof(Int32)) { dmRowObject = Convert.ToInt32(dmRowObject); } else if (columnType == typeof(Int16) && dmRowObject.GetType() != typeof(Int16)) { dmRowObject = Convert.ToInt16(dmRowObject); } else if (dmRowObject.GetType() != columnType) { Debug.WriteLine($"Can't convert serialized value {dmRowObject.ToString()} to {columnType}"); var t = dmRowObject.GetType(); var converter = columnType.GetConverter(); if (converter.CanConvertFrom(t)) { dmRowObject = converter.ConvertFrom(dmRowObject); } else { dmRowObject = Convert.ChangeType(dmRowObject, columnType, CultureInfo.InvariantCulture); } } } if (rowState == DmRowState.Deleted) { // Since some columns might be not null (and we have null because the row is deleted) if (this.Records[i][bitIndex] != null) { dmRow[i] = dmRowObject; } } else { dmRow[i] = dmRowObject; } } //if (rowState == DmRowState.Deleted) //{ // // we are in a deleted state, so we have only primary keys available // for (int i = 0; i < count; i++) // { // // since some columns might be not null (and we have null because the row is deleted) // if (this.Records[i][bitIndex] != null) // dmRow[i] = this.Records[i][bitIndex]; // } //} //else //{ // for (int i = 0; i < count; i++) // dmRow[i] = this.Records[i][bitIndex]; //} dt.Rows.Add(dmRow); switch (rowState) { case DmRowState.Unchanged: { dmRow.AcceptChanges(); dmRow.EndEdit(); return(dmRow); } case DmRowState.Added: { dmRow.EndEdit(); return(dmRow); } case DmRowState.Deleted: { dmRow.AcceptChanges(); dmRow.Delete(); dmRow.EndEdit(); return(dmRow); } case DmRowState.Modified: { dmRow.AcceptChanges(); dmRow.SetModified(); dmRow.EndEdit(); return(dmRow); } default: throw new ArgumentException("InvalidRowState"); } }
public TableChangesApplyingEventArgs(string providerTypeName, SyncStage stage, string tableName, DmRowState state, DbConnection connection, DbTransaction transaction) : base(providerTypeName, stage, connection, transaction) { this.TableName = tableName; this.State = state; }
internal void MergeRow(DmRow row, DmRow targetRow, bool preserveChanges) { // Si le merge ne concerne pas une ligne déjà existante if (targetRow == null) { ImportRow(row); return; } int proposedRecord = targetRow.tempRecord; // by saving off the tempRecord, EndEdit won't free newRecord targetRow.tempRecord = -1; try { DmRowState saveRowState = targetRow.RowState; int saveIdxRecord = (saveRowState == DmRowState.Added) ? targetRow.newRecord : saveIdxRecord = targetRow.oldRecord; int newRecord; int oldRecord; if (targetRow.RowState == DmRowState.Unchanged && row.RowState == DmRowState.Unchanged) { // unchanged row merging with unchanged row oldRecord = targetRow.oldRecord; if (preserveChanges) { newRecord = this.Rows.GetNewVersionId(); CopyRecords(this, oldRecord, newRecord); } else { newRecord = targetRow.newRecord; } CopyRecords(row.Table, row.oldRecord, targetRow.oldRecord); } else if (row.newRecord == -1) { // Incoming row is deleted oldRecord = targetRow.oldRecord; if (preserveChanges) { if (targetRow.RowState == DmRowState.Unchanged) { newRecord = this.Rows.GetNewVersionId(); CopyRecords(this, oldRecord, newRecord); } else { newRecord = targetRow.newRecord; } } else { newRecord = -1; } CopyRecords(row.Table, row.oldRecord, oldRecord); } else { // incoming row is added, modified or unchanged (targetRow is not unchanged) oldRecord = targetRow.oldRecord; newRecord = targetRow.newRecord; if (targetRow.RowState == DmRowState.Unchanged) { newRecord = this.Rows.GetNewVersionId(); CopyRecords(this, oldRecord, newRecord); } CopyRecords(row.Table, row.oldRecord, oldRecord); if (!preserveChanges) { CopyRecords(row.Table, row.newRecord, newRecord); } } } finally { targetRow.tempRecord = proposedRecord; } }
/// <summary> /// Apply changes internal method for one Insert or Update or Delete for every dbSyncAdapter /// </summary> internal async Task <ChangeApplicationAction> ApplyChangesInternalAsync( DmTable table, SyncContext context, MessageApplyChanges message, DbConnection connection, DbTransaction transaction, DmRowState applyType, DatabaseChangesApplied changesApplied) { var changeApplicationAction = ChangeApplicationAction.Continue; // if we are in upload stage, so check if table is not download only if (context.SyncWay == SyncWay.Upload && table.SyncDirection == SyncDirection.DownloadOnly) { return(ChangeApplicationAction.Continue); } // if we are in download stage, so check if table is not download only if (context.SyncWay == SyncWay.Download && table.SyncDirection == SyncDirection.UploadOnly) { return(ChangeApplicationAction.Continue); } var builder = this.GetDatabaseBuilder(table); var syncAdapter = builder.CreateSyncAdapter(connection, transaction); syncAdapter.ApplyType = applyType; if (message.Changes.BatchPartsInfo != null && message.Changes.BatchPartsInfo.Count > 0) { // getting the table to be applied // we may have multiple batch files, so we can have multipe dmTable with the same Name // We can say that dmTable may be contained in several files foreach (var dmTablePart in message.Changes.GetTable(table.TableName)) { if (dmTablePart == null || dmTablePart.Rows.Count == 0) { continue; } // check and filter var dmChangesView = new DmView(dmTablePart, (r) => r.RowState == applyType); if (dmChangesView.Count == 0) { dmChangesView.Dispose(); dmChangesView = null; continue; } // Conflicts occured when trying to apply rows var conflicts = new List <SyncConflict>(); context.SyncStage = SyncStage.TableChangesApplying; // Launch any interceptor if available await this.InterceptAsync(new TableChangesApplyingArgs(context, table, applyType, connection, transaction)); int rowsApplied; // applying the bulkchanges command if (this.Options.UseBulkOperations && this.SupportBulkOperations) { rowsApplied = syncAdapter.ApplyBulkChanges(dmChangesView, message.FromScope, conflicts); } else { rowsApplied = syncAdapter.ApplyChanges(dmChangesView, message.FromScope, conflicts); } // If conflicts occured // Eventuall, conflicts are resolved on server side. if (conflicts != null && conflicts.Count > 0) { foreach (var conflict in conflicts) { //var scopeBuilder = this.GetScopeBuilder(); //var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(message.ScopeInfoTableName, connection, transaction); //var localTimeStamp = scopeInfoBuilder.GetLocalTimestamp(); var fromScopeLocalTimeStamp = message.FromScope.Timestamp; var conflictCount = 0; DmRow resolvedRow = null; (changeApplicationAction, conflictCount, resolvedRow) = await this.HandleConflictAsync(syncAdapter, context, conflict, message.Policy, message.FromScope, fromScopeLocalTimeStamp, connection, transaction); if (changeApplicationAction == ChangeApplicationAction.Continue) { // row resolved if (resolvedRow != null) { context.TotalSyncConflicts += conflictCount; rowsApplied++; } } else { context.TotalSyncErrors++; // TODO : Should we break at the first error ? return(ChangeApplicationAction.Rollback); } } } // Handle sync progress for this syncadapter (so this table) var changedFailed = dmChangesView.Count - rowsApplied; // raise SyncProgress Event var existAppliedChanges = changesApplied.TableChangesApplied.FirstOrDefault( sc => string.Equals(sc.Table.TableName, table.TableName) && sc.State == applyType); if (existAppliedChanges == null) { existAppliedChanges = new TableChangesApplied { Table = new DmTableSurrogate(table), Applied = rowsApplied, Failed = changedFailed, State = applyType }; changesApplied.TableChangesApplied.Add(existAppliedChanges); } else { existAppliedChanges.Applied += rowsApplied; existAppliedChanges.Failed += changedFailed; } // Progress & Interceptor context.SyncStage = SyncStage.TableChangesApplied; var tableChangesAppliedArgs = new TableChangesAppliedArgs(context, existAppliedChanges, connection, transaction); this.ReportProgress(context, tableChangesAppliedArgs, connection, transaction); await this.InterceptAsync(tableChangesAppliedArgs); } } return(ChangeApplicationAction.Continue); }
public TableChangesApplyingEventArgs(string providerTypeName, SyncStage stage, string tableName, DmRowState state) : base(providerTypeName, stage) { this.TableName = tableName; this.State = state; }