Example #1
0
        public bool IsNull(DmColumn column, DmRowVersion version)
        {
            CheckColumn(column);
            int record = GetRecordFromVersion(version);

            return(column.IsNull(record));
        }
Example #2
0
        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");
            }
        }
Example #3
0
 internal bool HasKeyChanged(DmKey key, DmRowVersion version1, DmRowVersion version2)
 {
     if (!HasVersion(version1) || !HasVersion(version2))
     {
         return(true);
     }
     return(!key.RecordsEqual(GetRecordFromVersion(version1), GetRecordFromVersion(version2)));
 }
Example #4
0
 /// <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]);
     }
 }
Example #5
0
 /// <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]);
     }
 }
Example #6
0
 /// <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]);
     }
 }
Example #7
0
        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));
        }
Example #8
0
        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]);
            }
        }
Example #9
0
        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());
        }
Example #10
0
        /// <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");
            }
        }
Example #11
0
        /// <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));
        }
Example #12
0
        internal object[] GetKeyValues(DmKey key, DmRowVersion version)
        {
            int record = GetRecordFromVersion(version);

            return(key.GetKeyValues(record));
        }
Example #13
0
        /// <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());
        }
Example #14
0
        /// <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);
        }
Example #15
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);
Example #16
0
        /// <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)));
        }
Example #17
0
        /// <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);
        }
Example #18
0
        internal object[] GetColumnValues(DmColumn[] rows, DmRowVersion version)
        {
            DmKey key = new DmKey(rows); // temporary key, don't copy rows

            return(GetKeyValues(key, version));
        }
Example #19
0
        internal object[] GetKeyValues(DmRowVersion version)
        {
            int record = GetRecordFromVersion(version);

            return(this.table.PrimaryKey.GetKeyValues(record));
        }
Example #20
0
        /// <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);
        }