Updated the backup and restore functions to be more verbose and stable. Changed the version number slightly for appearance reasons. Fixed a bug on the Main Form that prevented it from refreshing the controls properly after all years have been wiped and after a database restore.

This commit is contained in:
2017-12-10 02:07:52 -06:00
parent 4b96f6c1fa
commit 46bddc7c5f
5 changed files with 204 additions and 31 deletions
+87 -9
View File
@@ -10,6 +10,12 @@ namespace AdvertisingProfitControlData
{
public static readonly string DefaultBackupLocation = Path.GetPathRoot(Environment.SystemDirectory) + @"Users\" + Environment.UserName + @"\AppData\Local\APC\Backups\";
public static readonly string DefaultBackupExtension = "apcdbbak";
//Define the error states that certain operations may return.
public const byte PrimaryBackupFailed = 5;
public const byte PrimaryBackupVerifyFailed = 10;
public const byte OptionalBackupFailed = 15;
public const byte OptionalBackupVerifyFailed = 25;
public byte State;
public string ErrorMessage = string.Empty;
public bool SingleFileBackup(string optionalBackupFilePath = "")
@@ -18,22 +24,94 @@ namespace AdvertisingProfitControlData
var sqlConStrBuilder = new SqlConnectionStringBuilder(AdvertisingProfitControlDbContext.ConnectionString);
try
{
using (var s = new SqlConnection(sqlConStrBuilder.ConnectionString))
using (var sqlConnection = new SqlConnection(sqlConStrBuilder.ConnectionString))
{
var query = $"BACKUP DATABASE {sqlConStrBuilder.InitialCatalog} TO DISK='{defaultBackupFile}'";
using (var command = new SqlCommand(query, s))
sqlConnection.InfoMessage += (s, e) =>
{
s.Open();
if (e.Errors.Count > 0)
{
State += e.Errors[e.Errors.Count - 1].State % 5 == 0 ? e.Errors[e.Errors.Count - 1].State : (byte) 0;
}
};
//Note: To get a SqlException an error must be raised at level 16 or higher.
//Checksum command needs to be applied in order to properly verify the media as pointed out here:
//http://www.edwinmsarmiento.com/the-scariest-lie-we-believed-about-backup-verification/
//RAISERROR(message string, level, state) levels above 11 throw SQLExceptions.
var query =
@"DECLARE @BackupSuccess BIT --1 is true 0 is false
SET @BackupSuccess = 0
BEGIN TRY
BACKUP DATABASE " + sqlConStrBuilder.InitialCatalog + @" TO DISK = N'" + defaultBackupFile + @"' WITH NOFORMAT, NOINIT, SKIP, REWIND, NOUNLOAD, CHECKSUM
SET @BackupSuccess = 1
RESTORE VERIFYONLY FROM DISK = N'"+ defaultBackupFile + @"' WITH NOUNLOAD, NOREWIND, CHECKSUM
END TRY
BEGIN CATCH
IF @BackupSuccess = 0
BEGIN
RAISERROR('Backup failed', 9, " + PrimaryBackupFailed + @")
END
ELSE
BEGIN
RAISERROR('Verify failed', 9, " + PrimaryBackupVerifyFailed + @")
END
END CATCH";
using (var command = new SqlCommand(query, sqlConnection))
{
sqlConnection.Open();
command.ExecuteNonQuery();
if (optionalBackupFilePath == string.Empty) return true;
var defaultBackupVerificationFailedFile =
defaultBackupFile.Insert(defaultBackupFile.IndexOf(".", StringComparison.Ordinal),
" (Verification Failed)");
if (File.Exists(defaultBackupVerificationFailedFile))
{
File.Delete(defaultBackupVerificationFailedFile);
}
if (State == PrimaryBackupVerifyFailed)
{
File.Move(defaultBackupFile, defaultBackupVerificationFailedFile);
}
if (optionalBackupFilePath == string.Empty)
{
return State != PrimaryBackupFailed;
}
var optionalBackupFile = optionalBackupFilePath + DateTime.Now.ToString("yy-MM-dd") + "." + DefaultBackupExtension;
command.CommandText =
$"BACKUP DATABASE {sqlConStrBuilder.InitialCatalog} TO DISK='{optionalBackupFilePath + DateTime.Now.ToString("yy-MM-dd") + "." + DefaultBackupExtension}'";
@"DECLARE @BackupSuccess BIT
SET @BackupSuccess = 0
BEGIN TRY
BACKUP DATABASE " + sqlConStrBuilder.InitialCatalog + @" TO DISK = N'" + optionalBackupFile + @"' WITH NOFORMAT, NOINIT, SKIP, REWIND, NOUNLOAD, CHECKSUM
SET @BackupSuccess = 1
RESTORE VERIFYONLY FROM DISK = N'" + optionalBackupFile + @"' WITH NOUNLOAD, NOREWIND, CHECKSUM
END TRY
BEGIN CATCH
IF @BackupSuccess = 0
BEGIN
RAISERROR('Optional backup failed', 9, " + OptionalBackupFailed + @")
END
ELSE
BEGIN
RAISERROR('Optional backup verify failed', 9, " + OptionalBackupVerifyFailed + @")
END
END CATCH";
command.ExecuteNonQuery();
var optionalBackupVerficationFailedFile =
optionalBackupFile.Insert(optionalBackupFile.IndexOf(".", StringComparison.Ordinal),
" (Verification Failed)");
if (State == OptionalBackupFailed || State == (OptionalBackupFailed + PrimaryBackupFailed) || State == (OptionalBackupFailed + PrimaryBackupVerifyFailed))
{
return false;
}
if (File.Exists(optionalBackupVerficationFailedFile))
{
File.Delete(optionalBackupVerficationFailedFile);
}
if (State == OptionalBackupVerifyFailed || State == (OptionalBackupVerifyFailed + PrimaryBackupVerifyFailed) || State == (OptionalBackupVerifyFailed + PrimaryBackupFailed))
{
File.Move(optionalBackupFile, optionalBackupVerficationFailedFile);
}
return State == PrimaryBackupFailed;
}
}
return true;
}
catch (SqlException e)
{
+28 -14
View File
@@ -6,28 +6,42 @@ namespace AdvertisingProfitControlData
{
public string ErrorMessage = string.Empty;
public bool SingleFileRestore(string databaseFilePath)
public bool SingleFileRestore(string databaseFilePath, string defaultServerName)
{
var sqlConStrBuilder = new SqlConnectionStringBuilder(AdvertisingProfitControlDbContext.ConnectionString);
var masterConnection = @"data source=" + defaultServerName + @";integrated security=True;MultipleActiveResultSets=True;App=EntityFramework";
try
{
using (var conn = new SqlConnection(sqlConStrBuilder.ConnectionString))
using (var conn = new SqlConnection(masterConnection))
{
var query = $"RESTORE DATABASE {sqlConStrBuilder.InitialCatalog} FROM DISK='{databaseFilePath}'";
var db = new AdvertisingProfitControlDbContext();
//Drop the database to make sure it can be recreated and restored (necessary since the database MUST be on line for its file to be wiped from disk).
db.Database.Delete();
var query =
@"BEGIN TRY
DECLARE @DefaultDataPath varchar(max)
DECLARE @DefaultLogPath varchar(max)
SELECT @DefaultDataPath = CONVERT(varchar(max),SERVERPROPERTY('INSTANCEDEFAULTDATAPATH'))
SELECT @DefaultLogPath = CONVERT(varchar(max),SERVERPROPERTY('INSTANCEDEFAULTLOGPATH'))
SELECT @DefaultDataPath = CONCAT(@DefaultDataPath, N'" + sqlConStrBuilder.InitialCatalog + @".mdf')
SELECT @DefaultLogPath = CONCAT(@DefaultLogPath, N'" + sqlConStrBuilder.InitialCatalog + @".ldf')
IF db_id(N'" + sqlConStrBuilder.InitialCatalog + @"') IS NOT NULL
BEGIN
CREATE DATABASE " + sqlConStrBuilder.InitialCatalog + @"
END
RESTORE DATABASE " + sqlConStrBuilder.InitialCatalog + @" FROM DISK ='" + databaseFilePath + @"' WITH REPLACE,
MOVE '" + sqlConStrBuilder.InitialCatalog + @"' TO @DefaultDataPath,
MOVE '" + sqlConStrBuilder.InitialCatalog + @"_log' TO @DefaultLogPath
END TRY
BEGIN CATCH
DECLARE @Error varchar(max)
SELECT @Error = ERROR_MESSAGE()
RAISERROR(@Error, 11, 1)
END CATCH";
//TODO: Verify that the database is good with DBCC command.
conn.Open();
var useMasterCommand = new SqlCommand("USE master", conn);
useMasterCommand.ExecuteNonQuery();
var alter1Cmd = new SqlCommand($"ALTER DATABASE {sqlConStrBuilder.InitialCatalog} SET Single_User WITH Rollback Immediate", conn);
alter1Cmd.ExecuteNonQuery();
var restoreCmd = new SqlCommand(query, conn);
restoreCmd.ExecuteNonQuery();
var alter2Cmd = new SqlCommand($"ALTER DATABASE {sqlConStrBuilder.InitialCatalog} SET Multi_User", conn);
alter2Cmd.ExecuteNonQuery();
conn.Close();
return true;
}