Ejemplo n.º 1
0
        private void ComputeForeignKeyConstraintsToDisable(ChangeInfo changeInfo)
        {
            var changes = changeInfo.Changes;

            for (int i = 0; i < changes.Count; i++)
            {
                var change = changes[i];
                if (change.CreationVersion < change.Version) // was inserted then later updated
                {
                    for (int j = i + 1; j < changes.Count; j++)
                    {
                        var intermediateChange = changes[j];
                        if (intermediateChange.CreationVersion > change.Version) // created later than last update to change
                        {
                            break;
                        }
                        if (intermediateChange.Operation != 'I')
                        {
                            continue;
                        }

                        // let's look at intermediateChange if it collides with change
                        foreach (var fk in change.Table.ForeignKeyConstraints.Where(f => f.ReferencedTableName == intermediateChange.Table.Name))
                        {
                            var val    = change.GetValue(fk.ColumnName);
                            var refVal = intermediateChange.GetValue(fk.ReferencedColumnName);
                            if (val != null && val.Equals(refVal))
                            {
                                // this foreign key constraint needs to be disabled
                                Log.Info($"Foreign key constraint {fk.ForeignKeyName} needs to be disabled for change #{i + 1} from version {change.CreationVersion} until version {intermediateChange.CreationVersion}");
                                change.ForeignKeyConstraintsToDisable[fk] = intermediateChange.CreationVersion;
                            }
                        }
                    }
                }
            }
        }
Ejemplo n.º 2
0
        private ChangeInfo RetrieveChanges(DatabaseInfo source, IGrouping <long, DatabaseInfo> destinations, IList <TableInfo> tables)
        {
            var destinationVersion = destinations.Key;
            var changeInfo         = new ChangeInfo();
            var changes            = new List <Change>();

            using (var db = GetDatabase(source.ConnectionString, DatabaseType.SqlServer2008))
            {
                var snapshotIsolationEnabled = db.ExecuteScalar <int>("select snapshot_isolation_state from sys.databases where name = DB_NAME()") == 1;
                if (snapshotIsolationEnabled)
                {
                    Log.Info($"Snapshot isolation is enabled in database {source.Name}");
                    db.BeginTransaction(System.Data.IsolationLevel.Snapshot);
                }
                else
                {
                    Log.Info($"Snapshot isolation is not enabled in database {source.Name}, ignoring all changes above current version");
                }

                changeInfo.Version = db.ExecuteScalar <long>("select CHANGE_TRACKING_CURRENT_VERSION()");
                Log.Info($"Current version of database {source.Name} is {changeInfo.Version}");

                foreach (var table in tables)
                {
                    var tableName  = table.Name;
                    var minVersion = db.ExecuteScalar <long?>("select CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(@0))", tableName);

                    Log.Info($"Minimum version of table {tableName} in database {source.Name} is {minVersion}");

                    if (minVersion > destinationVersion)
                    {
                        Log.Error($"Cannot replicate table {tableName} to {"destination".ToQuantity(destinations.Count(), ShowQuantityAs.None)} {string.Join(", ", destinations.Select(d => d.Name))} because minimum source version {minVersion} is greater than destination version {destinationVersion}");
                        Error = true;
                        return(null);
                    }

                    var sql = $@"select c.SYS_CHANGE_OPERATION, c.SYS_CHANGE_VERSION, c.SYS_CHANGE_CREATION_VERSION,
                        {string.Join(", ", table.KeyColumns.Select(c => "c." + c).Concat(table.OtherColumns.Select(c => "t." + c)))}
                        from CHANGETABLE (CHANGES {tableName}, @0) c
                        left outer join {tableName} t on ";
                    sql += string.Join(" and ", table.KeyColumns.Select(k => $"c.{k} = t.{k}"));
                    sql += " order by coalesce(c.SYS_CHANGE_CREATION_VERSION, c.SYS_CHANGE_VERSION)";

                    Log.Debug($"Retrieving changes for table {tableName}: {sql}");

                    db.OpenSharedConnection();
                    var cmd = db.CreateCommand(db.Connection, System.Data.CommandType.Text, sql, destinationVersion);

                    using (var reader = cmd.ExecuteReader())
                    {
                        var numChanges = 0;

                        while (reader.Read())
                        {
                            var col    = 0;
                            var change = new Change {
                                Operation = ((string)reader[col])[0], Table = table
                            };
                            col++;
                            var version = reader.GetInt64(col);
                            change.Version = version;
                            col++;
                            var creationVersion = reader.IsDBNull(col) ? version : reader.GetInt64(col);
                            change.CreationVersion = creationVersion;
                            col++;

                            if (!snapshotIsolationEnabled && Math.Min(version, creationVersion) > changeInfo.Version)
                            {
                                Log.Warn($"Ignoring change version {Math.Min(version, creationVersion)}");
                                continue;
                            }

                            for (int i = 0; i < table.KeyColumns.Count; i++, col++)
                            {
                                change.Keys[table.KeyColumns[i]] = reader.GetValue(col);
                            }
                            for (int i = 0; i < table.OtherColumns.Count; i++, col++)
                            {
                                change.Others[table.OtherColumns[i]] = reader.GetValue(col);
                            }

                            changes.Add(change);
                            numChanges++;
                        }

                        Log.Info($"Table {tableName} has {"change".ToQuantity(numChanges)}");
                    }
                }

                if (snapshotIsolationEnabled)
                {
                    db.CompleteTransaction();
                }
            }

            changeInfo.Changes.AddRange(changes.OrderBy(c => c.CreationVersion).ThenBy(c => c.Table.Name));

            ComputeForeignKeyConstraintsToDisable(changeInfo);

            return(changeInfo);
        }