I am working on a database application to restore a database with its transaction logs. I am using the SQL Server Management Objects (SMO) Library.
The requirements of this application make it so that I must restore the database backup file and its transaction logs in separate processes. I can restore the backup file without a hitch however, when it comes to restoring the transaction logs I come across a problem:
public void RestoreTransactionLogs(Server srv, DirectoryInfo filePath, DatabaseType dbType)
{
Restore res = new Restore()
{
Database = dbType.ToString(),
Action = RestoreActionType.Log,
ReplaceDatabase = false
};
FileInfo[] files = filePath.Parent.GetFiles("*.trn");
foreach (FileInfo f in files)
{
res.Devices.AddDevice(f.FullName, DeviceType.File);
}
try
{
res.SqlRestore(srv);
}
catch (SmoException ex)
{
Log.Fatal("An SMO Exception has occurred when restoring the database: " + dbType.ToString() + ": " + ex.Message);
throw ex;
}
catch (Exception ex)
{
Log.Fatal("An exception has occurred when restoring the database: " + dbType.ToString() + ": " + ex.Message);
throw ex;
}
}
Using a test backup file and 20 transaction logs, I run into the following error:
SmoException: System.Data.SqlClient.SqlError: The media loaded on
"D:\Test Folder\testDatabase\log_00001.trn" is formatted to support
1 media families, but 20 media families are expected according to the
backup device specification.
I have a feeling I am not adding the transaction logs to my device collection properly or I should be adding them in a different manner but I am unsure where to check. The documentation from MSDN for transaction logs is scarce and I haven't been able to find much online. Thanks!
I think your problem is that you can't restore just a transaction log. You have to start with a full backup first and then apply the transaction log(s). This must happen while db is in no recovery state and thus no other change to the db occurs between the full backup restore and the transaction log restore. Also, keep in mind that you have to apply transaction logs in the order they were taken.
Here is what I got working based on the doco example here:
https://learn.microsoft.com/en-us/sql/relational-databases/server-management-objects-smo/tasks/backing-up-and-restoring-databases-and-transaction-logs?view=sql-server-ver15
using (SqlConnection connection = new SqlConnection(connectionString))
{
var server = new Server(new ServerConnection(connection));
Database targetDb = server.Databases["TargetDbName"];
// Make sure your user has ALTER ANY CONNECTION rights for this
// not needed if you can be sure db is not in use
server.KillAllProcesses(targetDb.Name);
targetDb.SetOffline();
Restore restoreDB = new Restore();
restoreDB.Database = targetDb.Name;
restoreDB.Action = RestoreActionType.Database;
restoreDB.ReplaceDatabase = true;
// Restore the full backup first
var fullBackupDevice = new BackupDeviceItem("fullBackupFile.bak", DeviceType.File);
restoreDB.Devices.Add(fullBackupDevice);
restoreDB.NoRecovery = true;
restoreDB.SqlRestore(server);
restoreDB.Devices.Remove(fullBackupDevice);
// Get the first taken transaction log file
var firstTransactionBackupDevice = new BackupDeviceItem("firstTrnFile.trn", DeviceType.File);
restoreDB.Devices.Add(firstTransactionBackupDevice);
restoreDB.SqlRestore(server);
restoreDB.Devices.Remove(firstTransactionBackupDevice);
// Get the second taken transaction log file
var secondTransactionBackupDevice = new BackupDeviceItem("secondTrnFile.trn", DeviceType.File);
restoreDB.Devices.Add(secondTransactionBackupDevice);
// You have to set this flag to false before the last file you will restore
// to return the db to the normal state
restoreDB.NoRecovery = false;
restoreDB.SqlRestore(server);
restoreDB.Devices.Remove(secondTransactionBackupDevice);
targetDb.SetOnline();
server.Refresh();
}
I know your question is a bit older and you've probably found a solution already, but hopefully this helps to someone else.
Hi man you can try the scripts below within your sql server. Do change the directory file path. Also do your research on "with recovery" and "no recovery" .. example beow
RESTORE DATABASE [DW] FROM DISK = 'G:\MSSQL\Data\FullBackups\db.bak' WITH NORECOVERY
Go
RESTORE DATABASE [DW] FROM DISK = 'G:\MSSQL\Data\DifferentialBackups\db.bak' WITH NORECOVERY
--repeat how many ever times if multiple based on time DB crashed.
GO
RESTORE DATABASE [DW] FROM DISK = 'G:\MSSQL\Data\TransactionLogs\db.bak' WITH NORECOVERY
--Final T-Log to exact point in time recovery.
RESTORE DATABASE [DW] FROM DISK = 'G:\MSSQL\Data\TransactionLogs\db.bak' WITH RECOVERY
Related
I'm trying to restore a database from a bak file. I found some code on how to do it grammatically but I'm not sure what I'm doing wrong. I'm getting an error:
Error:
Restore failed for Server 'www.freegamedata.com'.
I assume because i'm remotely connected? I'm not sure. The bak file is not on the server machine. I'm trying to build a desktop application that will install my database on the users server using my file. Here is my code:
private void Restore_Database()
{
try
{
Server server = new Server(Properties.Settings.Default.SQL_Server);
string filename = "Test.bak";
string filepath = System.IO.Directory.GetCurrentDirectory() + "\\file\\" + filename;
Restore res = new Restore();
res.Database = Properties.Settings.Default.SQL_Database;
res.Action = RestoreActionType.Database;
res.Devices.AddDevice(filepath, DeviceType.File);
res.PercentCompleteNotification = 10;
res.ReplaceDatabase = true;
res.PercentComplete += new PercentCompleteEventHandler(res_PercentComplete);
res.SqlRestore(server);
}
catch(Exception ex)
{
}
}
I'm not sure if I'm going about this the correct way. I'd like to add my database with my data to the users server as a base database. Am I doing something wrong? My connection string is good so I know its not a connection issue.
I have found a workaround for those whom do not have local access. This is a bit involved so I hope I explain this correctly and it makes sense.
Also note you will need to export your data to an excel spreadsheet before you do the steps listed below.
Exporting Data
Part 1:
Backup Your DATA!
This is a pretty simple process. Open SQL Management Studio and right click on your database. Choose export data and export it as an excel spreadsheet 2007. I'm not going to give detailed steps on this part because its pretty basic and you can google it. Sorry for the inconvenience.
Part 2:
Delete your database for testing purposes but make sure you have a working backup before you delete your database.
Importing Data
Part 1:
You need to create a script that will build your database for you automatically. You can do this by logging into SQL management Studio and right click on the database and choose:
Task -> Generate scripts
you should only need the default information. However, if your like me, I excluded the users in the list. This will generate a large SQL script.
Part 2:
Next you will want to store this file in your solution/project. Make sure you right click it and choose always copy or or copy if newer. I think that's the options. Basically it just copies your file when you debug or build it. This is critical because you will need to access this file to execute the script. Next you need to make a SQL function similar to mine to execute the script:
public bool SQLScript_ExecuteSQLScript(string ScriptLocation)
{
try
{
//5 min timeout
SqlConnection SQLConn = new SqlConnection(cn + "; Connection Timeout = 300;");
string script = File.ReadAllText(ScriptLocation);
Server server = new Server(new ServerConnection(SQLConn));
server.ConnectionContext.ExecuteNonQuery(script);
return true;
}
catch (Exception ex)
{
return false;
}
}
In my code sample please note I changed my timeout to 5 minutes. In the event you have a large script you may need to adjust the timeout to make sure your script fully executes.
Congrats you have rebuilt your database.
Part 3:
Load SQL Management Studio and make sure your database has been rebuilt successfully. You should see all your tables and Stored Procs but no data. If this is true, great you can continue. If not please go back and review your script. If you have SQL comments in your script, you may need to remove them. I had to in order for my script to execute without errors.
Part 4:
Now you need to import your data from your excel spreadsheet you created earlier. If your like me, you had multipal sheets. If you have multipal sheets then you will want to make a list to loop through each item in your list to import the sheets. If not then you can ignore my code on the list. I also put mine in a background worker but you don't need to depending on the size of your data. Also note I created a separate class containing my list but you dont have to do that if you don't want too. My sheet names are Table_1, Table_2 and Table_3 your will be differently most likely.
Sample Sheet List:
public List<string> GetTestTableList()
{
try
{
List<string> testlist = new List<string>();
testlist.Add("Table_1");
testlist.Add("Table_2");
testlist.Add("Table_3");
return testlist;
}
catch (Exception ex)
{
return null;
}
}
Part 5:
Next we will import the data from excel into SQL. This is a function I made but you can modify this to meet your needs.
Function:
private bool Import_Data_Into_SQL(string filepath, string SheetName, string Database, string Schema)
{
try
{
// sql table should match your sheet name in excel
string sqltable = SheetName;
// select all data from sheet by name
string exceldataquery = "select * from [" + SheetName + "$]";
//create our connection strings - Excel 2007 - This may differ based on Excel spreadsheet used
string excelconnectionstring = #"Provider=Microsoft.ACE.OLEDB.12.0; Data Source='" + filepath + " '; Extended Properties=Excel 8.0;";
string sqlconnectionstring = Properties.Settings.Default.SQL_Connection;
//series of commands to bulk copy data from the excel file into our sql table
OleDbConnection oledbconn = new OleDbConnection(excelconnectionstring);
OleDbCommand oledbcmd = new OleDbCommand(exceldataquery, oledbconn);
oledbconn.Open();
OleDbDataReader dr = oledbcmd.ExecuteReader();
SqlBulkCopy bulkcopy = new SqlBulkCopy(sqlconnectionstring);
bulkcopy.DestinationTableName = Database + "." + Schema +"." + sqltable;
while (dr.Read())
{
bulkcopy.WriteToServer(dr);
}
dr.Close();
oledbconn.Close();
return true;
}
catch (Exception ex)
{
return false;
}
}
I hope this helps. This was my workaround solution. Originally I wanted/tried to import my data using the .bak file but as pointed out above you can only do that if the sql server is local. So I hope this work around helps those who where faced with a similar issue as me. I'm not marking this as the answer because the above post answers the question but I'm posting this in case someone else needs this workaround. Thanks
Restore file must be on server. For installation use SQL script. This can be generated by SQL Server Management Studio (including data).
Right click on database. Choose "Tasks" - "Generate scripts". On second page of wizard choose "Advanced" and find "Types of data to script". Select "Schema and data" and save script to file.
Then use this code to run script on database
string scriptText = File.ReadAllText(scriptFile, Encoding.Default);
ExecuteBatch executeBatch = new ExecuteBatch();
StringCollection commandTexts = executeBatch.GetStatements(scriptText);
using (SqlConnection sqlConnection = new SqlConnection(conn))
{
sqlConnection.InfoMessage += SqlConnection_InfoMessage;
sqlConnection.Open();
for (int i = 0; i < commandTexts.Count; i++)
{
try
{
log.InfoFormat("Executing statement {0}", i + 1);
string commandText = commandTexts[i];
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
log.Debug(commandText);
sqlCommand.CommandText = commandText;
sqlCommand.CommandTimeout = 300;
int r = sqlCommand.ExecuteNonQuery();
log.DebugFormat("{0} rows affected", r);
}
}
catch (Exception ex)
{
log.Warn("Executing command failed", ex);
try
{
sqlConnection.Open();
}
catch (Exception ex2)
{
log.Error("Cannot reopen connection", ex2);
}
}
}
sqlConnection.Close();
}
I have created a class to backup a database, the code is:
public bool BackupDatabase(string databasename)
{
bool success = false;
try
{
Backup dbBackup = new Backup();
string SqlInstance = #"SERVER\INSTANCE";
string User = ExtractPureUsername(System.Security.Principal.WindowsIdentity.GetCurrent().Name);
string BackupLocation = #"\\SERVER\FOLDER\BACKUPTEST_" + User.ToString() + ".bak";
Server srv = new Server(SqlInstance);
dbBackup.Action = BackupActionType.Database;
dbBackup.Database = databasename;
dbBackup.Devices.AddDevice(BackupLocation, DeviceType.File);
dbBackup.BackupSetName = "Test database backup";
dbBackup.ExpirationDate = DateTime.Today.AddDays(10);
dbBackup.Initialize = false;
dbBackup.PercentComplete += CompletionStatusInPercent;
dbBackup.Complete += Backup_Completed;
dbBackup.SqlBackup(srv);
success = true;
}
catch(Exception ex)
{
Console.WriteLine(ex);
Console.ReadKey();
}
return success;
}
This runs through, and I get the error:
Cannot open backup device - Operating System error 5
If I were to run the backup on the instance referenced, in SSMS, I can backup to the Bakup location that is specified. So I am presuming that the error is occuring becuase the backup is being initiated as the user that is running the C# program, and SQL server is having none of it. Is there a way to specify which user to run the backup as?
You probably have to set the correct file name as local path on the SQL Server machine. So for instance instead of \\ServerName\Whatever, use c:\Whatever. Make sure the file name you generate doesn't contain illegal characters, like \ or something.
I have a database in SQL Server 2008 R2, that uses the Simple recovery model.
The database contains a filegroup, where the bulk of the data resides (>20GB of images). These images are not critical for the application.
I want to backup the database from C# using Sql Server SMO. But I only want to backup the database structure (the PRIMARY filegroup; everything except the non-essential images). I want to do this in order to keep the backup size small.
In my C# code, I am setting the backup action to BackupActionType.Files, and I am only including the PRIMARY filegroup inside the DatabaseFileGroups collection, so it should only backup the database structure, and not the images.
But when I run the backup, I get this exception:
System.Data.SqlClient.SqlError: The primary filegroup cannot be backed up as a file backup because the database is using the SIMPLE recovery model. Consider taking a partial backup by specifying READ_WRITE_FILEGROUPS.
My question is, how can I specify READ_WRITE_FILEGROUPS from inside C# code, using Sql Server SMO? The exception shows me how to do so in T-SQL, but I want to do the same thing in C#.
Here is the code I am using:
class Program
{
static string DbName = PATH_TO_DATABASE;
static string connString = CONNECTION_STRING;
static void Main(string[] args)
{
ServerConnection serverConn = new ServerConnection();
serverConn.ConnectionString = connString;
Server server = new Server(serverConn);
Backup backup = new Backup() { Database = DbName };
backup.Action = BackupActionType.Files;
backup.DatabaseFileGroups.Add("PRIMARY");
backup.Devices.AddDevice("D:\\backup.bak", DeviceType.File);
backup.Initialize = true;
backup.ContinueAfterError = false;
backup.Incremental = false;
backup.Complete += (snd, e) => { Console.WriteLine("Complete"); };
backup.PercentComplete += (snd, e) => { Console.WriteLine("Percent " + e.Percent); };
backup.SqlBackup(server);
serverConn.Disconnect();
}
}
solution is very simple.
Just in SQLSERVER rigth-click on database and in Properties Window in Option tab change Recovery Mode To Bulk-logged
secound Solution by T-SQL:
USE [master]
GO
ALTER DATABASE [databasename] SET RECOVERY BULK_LOGGED WITH NO_WAIT
GO
I'm using this function for restoring a SQL Server database.
It works perfectly, but my problem is when I am using this function, I have to make sure that the SQL Server connection is closed in my application.
If a SQL Server connection is open in other window this function will not work.
So, how can I restore my database even if a SQL Server connection is open in another window?
I mean is there any way to lock SQL Server during my restore function?
private void btnRestore_Click(object sender, EventArgs e)
{
// If there was a SQL connection created
try
{
if (srvSql != null)
{
saveBackupDialog.Title = "Restore Backup File";
saveBackupDialog.InitialDirectory = "D:";
// If the user has chosen the file from which he wants the database to be restored
if (openFD.ShowDialog() == DialogResult.OK)
{
Thread oThread = new Thread(new ThreadStart(frmWaitShow));
oThread.Start();
// Create a new database restore operation
Restore rstDatabase = new Restore();
// Set the restore type to a database restore
rstDatabase.Action = RestoreActionType.Database;
// Set the database that we want to perform the restore on
rstDatabase.Database = cmbDatabase.SelectedItem.ToString();
// Set the backup device from which we want to restore, to a file
BackupDeviceItem bkpDevice = new BackupDeviceItem(openFD.FileName, DeviceType.File);
// Add the backup device to the restore type
rstDatabase.Devices.Add(bkpDevice);
// If the database already exists, replace it
rstDatabase.ReplaceDatabase = true;
// Perform the restore
rstDatabase.SqlRestore(srvSql);
oThread.Suspend();
MessageBox.Show("DataBase Restore Successfull");
}
else
{
// There was no connection established; probably the Connect button was not clicked
MessageBox.Show("A connection to a SQL server was not established.", "Not Connected to Server", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
}
I've found killing all processes for the database, setting single user mode, and then detaching the database is effective and can be done with SMO. All three steps were necessary to cover different scenarios, though I couldn't tell you off hand what those are. Theoretically, only placing the DB in single-user mode is necessary.
// Kill all processes
sqlServer.KillAllProcesses(restore.Database);
// Set single-user mode
Database db = sqlServer.Databases[restore.Database];
db.DatabaseOptions.UserAccess = DatabaseUserAccess.Single;
db.Alter(TerminationClause.RollbackTransactionsImmediately);
// Detach database
sqlServer.DetachDatabase(restore.Database, false);
In your method:
private void btnRestore_Click(object sender, EventArgs e)
{
// If there was a SQL connection created
try
{
if (srvSql != null)
{
saveBackupDialog.Title = "Restore Backup File";
saveBackupDialog.InitialDirectory = "D:";
// If the user has chosen the file from which he wants the database to be restored
if (openFD.ShowDialog() == DialogResult.OK)
{
Thread oThread = new Thread(new ThreadStart(frmWaitShow));
oThread.Start();
// Create a new database restore operation
Restore rstDatabase = new Restore();
// Set the restore type to a database restore
rstDatabase.Action = RestoreActionType.Database;
// Set the database that we want to perform the restore on
rstDatabase.Database = cmbDatabase.SelectedItem.ToString();
// Set the backup device from which we want to restore, to a file
BackupDeviceItem bkpDevice = new BackupDeviceItem(openFD.FileName, DeviceType.File);
// Add the backup device to the restore type
rstDatabase.Devices.Add(bkpDevice);
// If the database already exists, replace it
rstDatabase.ReplaceDatabase = true;
// Kill all processes
srvSql.KillAllProcesses(rstDatabase.Database);
// Set single-user mode
Database db = srvSql.Databases[rstDatabase.Database];
db.DatabaseOptions.UserAccess = DatabaseUserAccess.Single;
db.Alter(TerminationClause.RollbackTransactionsImmediately);
// Detach database
srvSql.DetachDatabase(rstDatabase.Database, false);
// Perform the restore
rstDatabase.SqlRestore(srvSql);
oThread.Suspend();
MessageBox.Show("DataBase Restore Successfull");
}
else
{
// There was no connection established; probably the Connect button was not clicked
MessageBox.Show("A connection to a SQL server was not established.", "Not Connected to Server", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
}
Well you need to close all active connections before beginning your process of restoring. As it will fail if any of the user is connected to your database.
You can achieve that by using the following script and executing it before restore.
use master
ALTER DATABASE YourDatabase SET SINGLE_USER WITH ROLLBACK IMMEDIATE
--do you stuff here
ALTER DATABASE YourDatabase SET MULTI_USER
In SMO, there's a KillAllProcesses method on the Server object. It takes a database as the sole argument.
I have make Some Differential Backup for a Database, which will take the Backup for the Last Modified Data which will be appended to the previously happend Full Backup file. Now, when i am trying to take Restore of the .bak file entire Data is getting Backup, is it possible to take the Backup only the Last Backup taken data i wanted? Can, any one help me on this.
private void RestoreDataBase(Server MyServer, Database MyDataBase, string DevicePath, string Type)
{
try
{
progressBar1.Value = 0;
Restore restoreDB = new Restore();
restoreDB.Action = RestoreActionType.Database;
restoreDB.Database = MyDataBase.Name;
restoreDB.Devices.AddDevice(DevicePath, DeviceType.File);
restoreDB.ReplaceDatabase = true;
restoreDB.NoRecovery = true;
restoreDB.PercentComplete += new PercentCompleteEventHandler(rstDatabase_PercentComplete);
restoreDB.Complete += new ServerMessageEventHandler(rstDatabase_Complete);
restoreDB.SqlRestore(MyServer);
}
catch (Exception ex)
{
WriteToListView(ex.Message.ToString());
writetoLog(ex.Message.ToString());
}
}
The above is the coding i am using, and the database is Sql Server 2008, The coding i am using for Differential Backup is as follows
private void BackupDataBaseDifferential(Server MyServer, Database MyDataBase, string DestinationPath, string Type)
{
try
{
WriteToListView("Taking the Differential Backup for " + MyDataBase.Name);
Backup backDB = new Backup();
backDB.Action = BackupActionType.Database;
backDB.Database = MyDataBase.Name;
backDB.Devices.AddDevice(DestinationPath, DeviceType.File);
backDB.BackupSetName = "Sql Database Backup Differential";
backDB.BackupSetDescription = "Sql Database Backup - DifferentialType";
backDB.ExpirationDate = DateTime.Now.AddDays(5);
backDB.Initialize = false;
backDB.Incremental = true;
if (Type == "Manual")
{
progressBar1.Value = 0;
backDB.PercentComplete += new PercentCompleteEventHandler(bd_PercentComplete);
backDB.Complete += new ServerMessageEventHandler(bd_Complete);
}
else if (Type == "Automatic")
{
backDB.PercentComplete += CompletionStatusInPercent;
backDB.Complete += Backup_Completed;
}
backDB.SqlBackup(MyServer);
}
catch (Exception ex)
{
WriteToListView(ex.Message.ToString());
writetoLog(ex.Message.ToString());
}
}
So, if i use the above coding the last data which is modified in the Database (which was already taken Backup Fully previously) will be taken. So, my problem is when i use the RestoreDataBase() method the entire Database is getting restore, bcoz all the Full Backup data and the Differential Backup data will be in only one file ex: Sample.bak. If i want only the last modified data is it not possible to take by Specifying the Date of the Data modified to the Database?
Maybe silly, but if you export your db as .sql, when you need to restore only some data, you can read file and "filter" rows to restore...
Anyway there is another thread talking about this, take a look here.