public bool IsNull(DmColumn column, DmRowVersion version) { CheckColumn(column); int record = GetRecordFromVersion(version); return(column.IsNull(record)); }
public bool HasVersion(DmRowVersion version) { switch (version) { // si oldRecord == -1 && newRecord != -1 alors l'enregistrement est Added // si oldRecord != -1 && newRecord == -1 alors l'enregistrement est Deleted // si oldRecord != -1 && newRecord != -1 alors l'enregistrement est Modified // si oldRecord == -1 && newRecord == -1 alors l'enregisrement est Detached case DmRowVersion.Original: return(oldRecord != -1); case DmRowVersion.Current: return(newRecord != -1); case DmRowVersion.Proposed: return(tempRecord != -1); case DmRowVersion.Default: return(tempRecord != -1 || newRecord != -1); default: throw new Exception("InvalidRowVersion"); } }
internal bool HasKeyChanged(DmKey key, DmRowVersion version1, DmRowVersion version2) { if (!HasVersion(version1) || !HasVersion(version2)) { return(true); } return(!key.RecordsEqual(GetRecordFromVersion(version1), GetRecordFromVersion(version2))); }
/// <summary> /// Gets the specified version of data stored in the specified DmColumn />. /// </summary> public object this[DmColumn row, DmRowVersion version] { get { CheckColumn(row); int record = GetRecordFromVersion(version); return(row[record]); } }
/// <summary> /// Gets the specified version of data stored in the named row. /// </summary> public object this[string rowName, DmRowVersion version] { get { DmColumn row = GetDataColumn(rowName); int record = GetRecordFromVersion(version); return(row[record]); } }
/// <summary> /// Gets the data stored in the row, specified by index and version of the data to retrieve. /// </summary> public object this[int rowIndex, DmRowVersion version] { get { DmColumn row = columns[rowIndex]; int record = GetRecordFromVersion(version); return(row[record]); } }
internal bool HaveValuesChanged(DmColumn[] rows, DmRowVersion version1, DmRowVersion version2) { for (int i = 0; i < rows.Length; i++) { CheckColumn(rows[i]); } DmKey key = new DmKey(rows); // temporary key, don't copy rows return(HasKeyChanged(key, version1, version2)); }
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 string ToString(DmRowVersion version) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < columns.Count; i++) { var c = columns[i]; var o = this[c, version]; var os = o == null ? "<NULL />" : o.ToString(); sb.Append($"{c.ColumnName}: {os}, "); } return(sb.ToString()); }
/// <summary> /// Get the record id from a version, if exists /// </summary> internal int GetRecordFromVersion(DmRowVersion version) { switch (version) { case DmRowVersion.Original: return(GetOriginalRecordId()); case DmRowVersion.Current: return(GetCurrentRecordId()); case DmRowVersion.Proposed: return(GetProposedRecordId()); case DmRowVersion.Default: return(GetRecordId()); default: throw new Exception("InvalidRowVersion"); } }
/// <summary> /// Gets the parent row of this <see cref='System.Data.DataRow'/> /// using the specified <see cref='System.Data.DataRelation'/> and <see cref='System.Data.DataRowVersion'/>. /// </summary> public DmRow GetParentRow(DmRelation relation, DmRowVersion version) { if (relation == null) { return(null); } if (relation.DmSet != table.DmSet) { throw new Exception("RowNotInTheDataSet"); } if (relation.ChildKey.Table != table) { throw new Exception("RelationForeignTable"); } return(DmRelation.GetParentRow(relation.ParentKey, relation.ChildKey, this, version)); }
internal object[] GetKeyValues(DmKey key, DmRowVersion version) { int record = GetRecordFromVersion(version); return(key.GetKeyValues(record)); }
/// <summary> /// Gets the parent rows for the given child row across the relation using the version given /// </summary> internal static DmRow[] GetParentRows(DmKey parentKey, DmKey childKey, DmRow childRow, DmRowVersion version) { object[] values = childRow.GetKeyValues(childKey, version); if (IsKeyNull(values)) { return new DmRow[] { parentKey.Table.NewRow() } } ; return(parentKey.Table.Rows.Where(r => parentKey.ValuesAreEqual(r, values)).ToArray()); }
/// <summary> /// Insert or update a metadata line /// </summary> internal bool InsertOrUpdateMetadatas(DbCommand command, DmRow row, Guid?fromScopeId) { int rowsApplied = 0; if (command == null) { throw new Exception("Missing command for apply metadata "); } // Set the id parameter this.SetColumnParametersValues(command, row); DmRowVersion version = row.RowState == DmRowState.Deleted ? DmRowVersion.Original : DmRowVersion.Current; long createTimestamp = row["create_timestamp", version] != null?Convert.ToInt64(row["create_timestamp", version]) : 0; long updateTimestamp = row["update_timestamp", version] != null?Convert.ToInt64(row["update_timestamp", version]) : 0; Guid?create_scope_id = row["create_scope_id", version] != null ? (Guid?)(row["create_scope_id", version]) : null; Guid?update_scope_id = row["update_scope_id", version] != null ? (Guid?)(row["update_scope_id", version]) : null; // Override create and update scope id to reflect who change the value // if it's an update, the createscope is staying the same (because not present in dbCommand) Guid?createScopeId = fromScopeId.HasValue ? fromScopeId : create_scope_id; Guid?updateScopeId = fromScopeId.HasValue ? fromScopeId : update_scope_id; // some proc stock does not differentiate update_scope_id and create_scope_id and use sync_scope_id DbManager.SetParameterValue(command, "sync_scope_id", createScopeId); // else they use create_scope_id and update_scope_id DbManager.SetParameterValue(command, "create_scope_id", createScopeId); DbManager.SetParameterValue(command, "update_scope_id", updateScopeId); // 2 choices for getting deleted bool isTombstone = false; if (row.RowState == DmRowState.Deleted) { isTombstone = true; } if (row.Table != null && row.Table.Columns.Contains("sync_row_is_tombstone")) { var rowValue = row["sync_row_is_tombstone", version] != null && row["sync_row_is_tombstone", version] != DBNull.Value ? row["sync_row_is_tombstone", version] : false; if (rowValue.GetType() == typeof(bool)) { isTombstone = (bool)rowValue; } else { isTombstone = Convert.ToInt64(rowValue) > 0; } //else //{ // string rowValueString = rowValue.ToString(); // if (Boolean.TryParse(rowValueString.Trim(), out Boolean v)) // { // isTombstone = v; // } // else if (rowValueString.Trim() == "0") // { // isTombstone = false; // } // else if (rowValueString.Trim() == "1") // { // isTombstone = true; // } // else // { // var converter = TypeDescriptor.GetConverter(typeof(bool)); // if (converter.CanConvertFrom(rowValue.GetType())) // { // isTombstone = (bool)converter.ConvertFrom(rowValue); // } // else // { // isTombstone = false; // } // } //} } DbManager.SetParameterValue(command, "sync_row_is_tombstone", isTombstone ? 1 : 0); DbManager.SetParameterValue(command, "create_timestamp", createTimestamp); DbManager.SetParameterValue(command, "update_timestamp", updateTimestamp); var alreadyOpened = Connection.State == ConnectionState.Open; try { if (!alreadyOpened) { Connection.Open(); } if (Transaction != null) { command.Transaction = Transaction; } rowsApplied = command.ExecuteNonQuery(); } finally { // Close Connection if (!alreadyOpened) { Connection.Close(); } } return(rowsApplied > 0); }
/// <summary> /// Gets the parent rows of this DmRow using the specified DmRelation . /// </summary> public DmRow[] GetParentRows(string relationName, DmRowVersion version) => GetParentRows(table.ParentRelations.FirstOrDefault(r => r.RelationName == relationName), version);
/// <summary> /// For a foreignkey, actually we have only one row /// </summary> internal static DmRow GetParentRow(DmKey parentKey, DmKey childKey, DmRow childRow, DmRowVersion version) { if (!childRow.HasVersion((version == DmRowVersion.Original) ? DmRowVersion.Original : DmRowVersion.Current)) { if (childRow.tempRecord == -1) { return(null); } } object[] values = childRow.GetKeyValues(childKey, version); if (IsKeyNull(values)) { return(null); } return(parentKey.Table.Rows.FirstOrDefault(r => parentKey.ValuesAreEqual(r, values))); }
/// <summary> /// Insert or update a metadata line /// </summary> internal bool InsertOrUpdateMetadatas(DbCommand command, DmRow row, Guid?fromScopeId) { int rowsApplied = 0; if (command == null) { var exc = $"Missing command for apply metadata "; Debug.WriteLine(exc); throw new Exception(exc); } // Set the id parameter this.SetColumnParametersValues(command, row); DmRowVersion version = row.RowState == DmRowState.Deleted ? DmRowVersion.Original : DmRowVersion.Current; long createTimestamp = row["create_timestamp", version] != null?Convert.ToInt64(row["create_timestamp", version]) : 0; long updateTimestamp = row["update_timestamp", version] != null?Convert.ToInt64(row["update_timestamp", version]) : 0; // Override create and update scope id to reflect who change the value // if it's an update, the createscope is staying the same (because not present in dbCommand) Guid?createScopeId = fromScopeId; Guid?updateScopeId = fromScopeId; // some proc stock does not differentiate update_scope_id and create_scope_id and use sync_scope_id DbManager.SetParameterValue(command, "sync_scope_id", createScopeId); // else they use create_scope_id and update_scope_id DbManager.SetParameterValue(command, "create_scope_id", createScopeId); DbManager.SetParameterValue(command, "update_scope_id", updateScopeId); // 2 choices for getting deleted bool isTombstone = false; if (row.RowState == DmRowState.Deleted) { isTombstone = true; } if (row.Table != null && row.Table.Columns.Contains("sync_row_is_tombstone")) { isTombstone = row["sync_row_is_tombstone", version] != null && row["sync_row_is_tombstone", version] != DBNull.Value ? (bool)row["sync_row_is_tombstone", version] : false; } DbManager.SetParameterValue(command, "sync_row_is_tombstone", isTombstone ? 1 : 0); DbManager.SetParameterValue(command, "create_timestamp", createTimestamp); DbManager.SetParameterValue(command, "update_timestamp", updateTimestamp); var alreadyOpened = Connection.State == ConnectionState.Open; try { // Open Connection if (!alreadyOpened) { Connection.Open(); } if (Transaction != null) { command.Transaction = Transaction; } rowsApplied = command.ExecuteNonQuery(); } catch (DbException ex) { Debug.WriteLine(ex.Message); throw; } finally { // Close Connection if (!alreadyOpened) { Connection.Close(); } } return(rowsApplied > 0); }
internal object[] GetColumnValues(DmColumn[] rows, DmRowVersion version) { DmKey key = new DmKey(rows); // temporary key, don't copy rows return(GetKeyValues(key, version)); }
internal object[] GetKeyValues(DmRowVersion version) { int record = GetRecordFromVersion(version); return(this.table.PrimaryKey.GetKeyValues(record)); }
/// <summary> /// Handle a conflict /// The int returned is the conflict count I need /// </summary> internal (ChangeApplicationAction, int) HandleConflict(SyncConflict conflict, ConflictResolutionPolicy policy, ScopeInfo scope, long fromScopeLocalTimeStamp, out DmRow finalRow) { finalRow = null; // overwrite apply action if we handle it (ie : user wants to change the action) if (this.ConflictActionInvoker != null) { (ConflictApplyAction, finalRow) = this.ConflictActionInvoker(conflict, policy, Connection, Transaction); } // Default behavior and an error occured if (ConflictApplyAction == ApplyAction.Rollback) { conflict.ErrorMessage = "Rollback action taken on conflict"; conflict.Type = ConflictType.ErrorsOccurred; return(ChangeApplicationAction.Rollback, 0); } // Local provider wins, update metadata if (ConflictApplyAction == ApplyAction.Continue) { var isMergeAction = finalRow != null; var row = isMergeAction ? finalRow : conflict.LocalRow; // Conflict on a line that is not present on the datasource if (row == null) { return(ChangeApplicationAction.Continue, 0); } if (row != null) { // if we have a merge action, we apply the row on the server if (isMergeAction) { bool isUpdated = false; bool isInserted = false; // Insert metadata is a merge, actually DbCommandType commandType = DbCommandType.UpdateMetadata; isUpdated = this.ApplyUpdate(row, scope, true); if (!isUpdated) { // Insert the row isInserted = this.ApplyInsert(row, scope, true); // Then update the row to mark this row as updated from server // and get it back to client isUpdated = this.ApplyUpdate(row, scope, true); commandType = DbCommandType.InsertMetadata; } if (!isUpdated && !isInserted) { throw new Exception("Can't update the merge row."); } // IF we have insert the row in the server side, to resolve the conflict // Whe should update the metadatas correctly if (isUpdated || isInserted) { using (var metadataCommand = GetCommand(commandType)) { // getting the row updated from server var dmTableRow = GetRow(row); row = dmTableRow.Rows[0]; // Deriving Parameters this.SetCommandParameters(commandType, metadataCommand); // Set the id parameter this.SetColumnParametersValues(metadataCommand, row); DmRowVersion version = row.RowState == DmRowState.Deleted ? DmRowVersion.Original : DmRowVersion.Current; Guid?create_scope_id = row["create_scope_id"] != DBNull.Value ? (Guid?)row["create_scope_id"] : null; long createTimestamp = row["create_timestamp", version] != DBNull.Value ? Convert.ToInt64(row["create_timestamp", version]) : 0; // The trick is to force the row to be "created before last sync" // Even if we just inserted it // to be able to get the row in state Updated (and not Added) row["create_scope_id"] = create_scope_id; row["create_timestamp"] = fromScopeLocalTimeStamp - 1; // Update scope id is set to server side Guid?update_scope_id = row["update_scope_id"] != DBNull.Value ? (Guid?)row["update_scope_id"] : null; long updateTimestamp = row["update_timestamp", version] != DBNull.Value ? Convert.ToInt64(row["update_timestamp", version]) : 0; row["update_scope_id"] = null; row["update_timestamp"] = updateTimestamp; // apply local row, set scope.id to null becoz applied locally var rowsApplied = this.InsertOrUpdateMetadatas(metadataCommand, row, null); if (!rowsApplied) { throw new Exception("No metadatas rows found, can't update the server side"); } } } } finalRow = isMergeAction ? row : conflict.LocalRow; // We don't do anything on the local provider, so we do not need to return a +1 on syncConflicts count return(ChangeApplicationAction.Continue, 0); } return(ChangeApplicationAction.Rollback, 0); } // We gonna apply with force the line if (ConflictApplyAction == ApplyAction.RetryWithForceWrite) { if (conflict.RemoteRow == null) { // TODO : Should Raise an error ? return(ChangeApplicationAction.Rollback, 0); } bool operationComplete = false; // create a localscope to override values var localScope = new ScopeInfo { Name = scope.Name, Timestamp = fromScopeLocalTimeStamp }; DbCommandType commandType = DbCommandType.InsertMetadata; bool needToUpdateMetadata = true; switch (conflict.Type) { // Remote source has row, Local don't have the row, so insert it case ConflictType.RemoteUpdateLocalNoRow: case ConflictType.RemoteInsertLocalNoRow: operationComplete = this.ApplyInsert(conflict.RemoteRow, localScope, true); commandType = DbCommandType.InsertMetadata; break; // Conflict, but both have delete the row, so nothing to do case ConflictType.RemoteDeleteLocalDelete: case ConflictType.RemoteDeleteLocalNoRow: operationComplete = true; needToUpdateMetadata = false; break; // The remote has delete the row, and local has insert or update it // So delete the local row case ConflictType.RemoteDeleteLocalUpdate: case ConflictType.RemoteDeleteLocalInsert: operationComplete = this.ApplyDelete(conflict.RemoteRow, localScope, true); commandType = DbCommandType.UpdateMetadata; break; // Remote insert and local delete, sor insert again on local // but tracking line exist, so make an update on metadata case ConflictType.RemoteInsertLocalDelete: case ConflictType.RemoteUpdateLocalDelete: operationComplete = this.ApplyInsert(conflict.RemoteRow, localScope, true); commandType = DbCommandType.UpdateMetadata; break; // Remote insert and local insert/ update, take the remote row and update the local row case ConflictType.RemoteUpdateLocalInsert: case ConflictType.RemoteUpdateLocalUpdate: case ConflictType.RemoteInsertLocalInsert: case ConflictType.RemoteInsertLocalUpdate: operationComplete = this.ApplyUpdate(conflict.RemoteRow, localScope, true); commandType = DbCommandType.UpdateMetadata; break; case ConflictType.RemoteCleanedupDeleteLocalUpdate: case ConflictType.ErrorsOccurred: return(ChangeApplicationAction.Rollback, 0); } if (needToUpdateMetadata) { using (var metadataCommand = GetCommand(commandType)) { // Deriving Parameters this.SetCommandParameters(commandType, metadataCommand); // force applying client row, so apply scope.id (client scope here) var rowsApplied = this.InsertOrUpdateMetadatas(metadataCommand, conflict.RemoteRow, scope.Id); if (!rowsApplied) { throw new Exception("No metadatas rows found, can't update the server side"); } } } finalRow = conflict.RemoteRow; //After a force update, there is a problem, so raise exception if (!operationComplete) { var ex = $"Can't force operation for applyType {ApplyType}"; finalRow = null; return(ChangeApplicationAction.Continue, 0); } // tableProgress.ChangesApplied += 1; return(ChangeApplicationAction.Continue, 1); } return(ChangeApplicationAction.Rollback, 0); }