/// <summary> /// /// </summary> /// <param name="backupItem"></param> /// <param name="restore"></param> /// <remarks> /// <paramref name="restore"/> must have backup device already added /// </remarks> private void AddMoveFileInstructionsIfNeeded(BackupItem backupItem, Restore restore) { Check.DoRequireArgumentNotNull(restore, "restore"); Check.DoCheckArgument(restore.Devices.Count > 0, "Restore instance must have backup device already added"); if (backupItem.BackupType == BackupType.Full) { var databaseFilesInBackup = ReadDatabaseFileList(restore); foreach (var dbFile in databaseFilesInBackup) { var targetDir = dbFile.IsLog ? DefaultLogPath : DefaultDataPath; var physicalFilePath = FindExistingDatabaseFile(dbFile.LogicalName, dbFile.IsLog); if (string.IsNullOrEmpty(physicalFilePath)) { // no such file in the existing database; maybe it was dropped physicalFilePath = Path.Combine(targetDir, Path.GetFileName(dbFile.PhysicalName)); physicalFilePath = PickNewFileName(physicalFilePath); } Log.InfoFormat("Moving {0} to {1}", dbFile.LogicalName, physicalFilePath); restore.RelocateFiles.Add(new RelocateFile(dbFile.LogicalName, physicalFilePath)); } } }
private bool CheckSequence(BackupItem first, BackupItem next, bool raiseException) { bool result; if (first.BackupType == BackupType.Full) { if (next.BackupType == BackupType.DifferentialDatabase) { result = Verify(next.DatabaseBackupLsn == first.CheckpointLsn, raiseException , $"The differential backup {next} cannot be applied after the full backup {first} due to LSN mismatch: {next.DatabaseBackupLsn}" + $" vs {first.CheckpointLsn}; another full backup was likely taken between the 2."); } else { result = Verify(next.BackupType == BackupType.Log, raiseException , $"Next backup {next} is of unsupported type {next.BackupType} after full {first}") && Verify(next.FirstLsn <= first.LastLsn && next.LastLsn >= first.LastLsn, raiseException , $"The log backup {next} is out of sequence with the full backup {first}"); } } else if (first.BackupType == BackupType.DifferentialDatabase) { result = Verify(next.BackupType == BackupType.Log, raiseException , $"Cannot apply {next.BackupType} type backup after differential") && Verify(next.FirstLsn <= first.LastLsn && next.LastLsn >= first.LastLsn, raiseException , $"The log backup {next} is out of sequence with the diff backup {first}") && Verify(next.DatabaseBackupLsn == first.DatabaseBackupLsn, raiseException , $"The log backup {next} has a different base with the diff backup {first}"); } else { result = Verify(first.BackupType == BackupType.Log && next.BackupType == BackupType.Log, raiseException , $"Pair {first.BackupType} - {next.BackupType} is not supported") && Verify(first.LastLsn == next.FirstLsn, raiseException , $"Log backups {first.FileInfo.Name}#{first.Position} and {next.FileInfo.Name}#{next.Position} are out of sequence"); } return(result); }
/// <summary> /// Verified sequence of backup items to restore to achieve configured outcome. /// Never returns null. /// </summary> /// <param name="lastDatabaseBackupItem"></param> /// <returns></returns> private List <BackupItem> GetLogBackupItems(BackupItem lastDatabaseBackupItem, DateTime?targetTime) { Check.DoRequireArgumentNotNull(lastDatabaseBackupItem, "lastDatabaseBackupItem"); var result = new List <BackupItem>(1000); var logFiles = BackupFileCatalog.GetLogBackupsSequence(lastDatabaseBackupItem.BackupStartTime); // need only first log backup after target time; flag indicates that file is already processed var targetReached = false; var fileIterator = logFiles.GetEnumerator(); var lastBackupItem = lastDatabaseBackupItem; while (!targetReached && fileIterator.MoveNext()) { var header = ParseBackupHeader(fileIterator.Current.FileInfo); if (header != null) { var itemIterator = header.ValidItems.GetEnumerator(); while (!targetReached && itemIterator.MoveNext()) { var item = itemIterator.Current; Check.DoAssertLambda(CheckSequence(lastBackupItem, item, false), () => $"Item #{item.Position} in file {item.FileInfo.Name} cannot be applied after item #{lastBackupItem.Position} in" + $" file {lastBackupItem.FileInfo.Name}"); lastBackupItem = item; result.Add(item); targetReached = (targetTime.HasValue && item.BackupStartTime > targetTime.Value); } } } return(result); }
private BackupItem GetBackupItem(DataRow row, FileInfo backupFileInfo) { var result = new BackupItem() { DatabaseName = (string)row["DatabaseName"], BackupType = (BackupType)Convert.ToInt32(row["BackupType"]), Position = Convert.ToInt32(row["Position"]), FirstLsn = (decimal)row["FirstLSN"], // numeric(25,0) LastLsn = (decimal)row["LastLSN"], DatabaseBackupLsn = (decimal)row["DatabaseBackupLSN"], DifferentialBaseLsn = (decimal?)(row["DifferentialBaseLSN"] != DBNull.Value ? row["DifferentialBaseLSN"] : null), CheckpointLsn = Convert.ToDecimal(row["CheckpointLSN"]), DifferentialBaseGuid = (Guid?)(row["DifferentialBaseGUID"] != DBNull.Value ? row["DifferentialBaseGUID"] : null), FileInfo = backupFileInfo, BackupStartTime = (DateTime)row["BackupStartDate"], BackupEndTime = (DateTime)row["BackupFinishDate"], IsCopyOnly = (bool)row["IsCopyOnly"], RecoveryModel = (RecoveryModel)Enum.Parse(typeof(RecoveryModel), (string)row["RecoveryModel"], true) }; return(result); }
private void Restore(BackupItem item) { Check.DoRequireArgumentNotNull(item, "item"); Log.InfoFormat("{0}: restoring {1} from {2}", DatabaseName, item, item.FileInfo.FullName); var actionType = item.BackupType == BackupType.Log ? RestoreActionType.Log : RestoreActionType.Database; var restore = CreateRestoreInstance(); restore.Action = actionType; restore.Devices.AddDevice(item.FileInfo.FullName, DeviceType.File); restore.FileNumber = item.Position; if (TargetTime.HasValue && item.BackupType == BackupType.Log) { // note that another format, yyyy-MM-ddTHH:mm:ss.fff failed with error saying "already restored past the point" restore.ToPointInTime = TargetTime.Value.ToString("MMM dd, yyyy hh:mm:ss tt"); Log.InfoFormat("Setting Point In Time {0} on {1}", restore.ToPointInTime, item); } AddMoveFileInstructionsIfNeeded(item, restore); restore.PercentCompleteNotification = 10; restore.PercentComplete += RestoreOnPercentComplete; try { Server.ConnectionContext.ExecuteNonQuery("use master;"); restore.SqlRestore(Server); } finally { restore.PercentComplete -= RestoreOnPercentComplete; } }