Backing up database with SMO - c#

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.

Related

Failed to connect to server "xxx" but works in Visual Studio Start

I am trying to backup a database on a remote development server and send it to a network drive folder that is shared between mine and the development pc.
I have attached the function I'm calling and the error that I am getting too.
However when I run this using the Visual Studio Start button in the top it works perfectly fine (When running in admin mode)
Everyone keeps saying permissions but I dont understand how the permissions on my pc will be taken into account on the development server
public static string BackupDatabase(string DatabaseName)
{
String sqlServerLogin = Properties.Resources.Username;
String password = Properties.Resources.Password;
String remoteSvrName = Properties.Resources.DataSource;
// Authentication
ServerConnection srvConn2 = new ServerConnection(remoteSvrName);
srvConn2.LoginSecure = false;
srvConn2.Login = sqlServerLogin;
srvConn2.Password = password;
// Initialisation of the server object
Server srv = new Server(srvConn2);
Backup bk = new Backup();
bk.Action = BackupActionType.Database;
bk.BackupSetDescription = "Staging Transfer";
bk.BackupSetName = DatabaseName + " Backup";
bk.Database = DatabaseName;
// Declare a BackupDeviceItem by supplying the backup device file name in the constructor, and the type of device is a file.
BackupDeviceItem bdi = default(BackupDeviceItem);
string backupPath = Properties.Resources.DatabaseBackupPath + DatabaseName + DateTime.Now.ToString("ddMMyyyy") + ".bak";
if (File.Exists(backupPath))
{
File.Delete(backupPath);
}
bdi = new BackupDeviceItem(backupPath, DeviceType.File);
// Add the device to the Backup object.
bk.Devices.Add(bdi);
bk.Checksum = true;
bk.ContinueAfterError = true;
bk.Initialize = true;
// Set the Incremental property to False to specify that this is a full database backup.
bk.Incremental = false;
// Specify that the log must be truncated after the backup is complete.
bk.LogTruncation = BackupTruncateLogType.Truncate;
// Run SqlBackup to perform the full database backup on the instance of SQL Server.
bk.SqlBackup(srv);
bk.Wait();
return backupPath;
}
Is anyone able to advise me on this? It's driving me crazy!
private void backupDatabaseWorker_DoWork(object sender, DoWorkEventArgs e)
{
backupDatabaseWorker.WorkerReportsProgress = true;
Utilities util = new Utilities();
string database = util.GetDatabase(filePathSelected.Text);
string backupReturn = SQL.BackupDatabase(database);
backupPath = backupReturn;
MessageBox.Show(database);
MessageBox.Show(backupReturn);
}
The GetDatabase function works fine, that uses the web config to grab the database name that the site uses.
Any help would be greatly appreciated
Error
Failed to connect to server dev. at Microsoft.SqlServer.Management.Common.ConnectionManager.Connect()
at Microsoft.SqlServer.Management.Common.ConnectionManager.PoolConnect()
at Microsoft.SqlServer.Management.Common.ConnectionManager.GetServerInformation()
at Microsoft.SqlServer.Management.Smo.ExecutionManager.GetServerVersion()
at Microsoft.SqlServer.Management.Smo.SqlSmoObject.get_ServerVersion()
at Microsoft.SqlServer.Management.Smo.Backup.Script(Server targetServer)
at Microsoft.SqlServer.Management.Smo.Backup.SqlBackup(Server srv)
at Microsoft.SqlServer.Management.Smo.Backup.SqlBackup(Server srv)
at PCS_Lithium.SQL.BackupDatabase(String DatabaseName)
at PCS_Lithium.PushToStagingForm.backupDatabaseWorker_DoWork(Object sender, DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)
Microsoft.SqlServer.SmoExtended

How to restore multiple transaction logs using SMO for .NET

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

Restore database using c# and smo

I am trying to restore a sql server .bak into an empty database using the following c# code:
string dbBakFile = GetBackFileFromZip(restoreConfig.TmpUnZipFolder,restoreConfig.DatabaseFileToRestore);
if (string.IsNullOrEmpty(dbBakFile))
{
response.Status = DatabaseResponseStatus.Error;
response.Message = "No .bak file found in " + restoreConfig.DatabaseToRestore;
return response;
}
var builder =
new SqlConnectionStringBuilder(
ConfigurationManager.ConnectionStrings["myserver"].ConnectionString);
var smoServer =
new Server(new ServerConnection(builder.DataSource,builder.UserID,builder.Password));
var db = smoServer.Databases[restoreConfig.DatabaseToRestore];
if (db != null)
{
smoServer.KillAllProcesses(restoreConfig.DatabaseToRestore);
log.Debug("all processes on db killed");
}
string dbPath = Path.Combine(db.PrimaryFilePath, restoreConfig.DatabaseToRestore + ".mdf");
log.Debug("db path is " +dbPath);
string logPath = Path.Combine(db.PrimaryFilePath,restoreConfig.DatabaseToRestore + "_Log.ldf");
log.Debug("log path is " + logPath);
var restore = new Restore();
var deviceItem =
new BackupDeviceItem(dbBakFile, DeviceType.File);
restore.DatabaseFiles.Add(dbPath);
restore.DatabaseFiles.Add(logPath);
restore.Devices.Add(deviceItem);
restore.Database = restoreConfig.DatabaseToRestore;
restore.FileNumber = 1;
restore.Action = RestoreActionType.Files;
restore.ReplaceDatabase = true;
restore.PercentCompleteNotification = 10;
restore.PercentComplete +=restore_PercentComplete;
restore.Complete += restore_Complete;
restore.SqlRestore(smoServer);
db = smoServer.Databases[restoreConfig.DatabaseToRestore];
db.SetOnline();
smoServer.Refresh();
db.Refresh();
I get the following error:
Microsoft.SqlServer.Management.Smo.FailedOperationException: Restore failed for Server 'IM-M4500\SQLEXPRESS'. ---> Microsoft.SqlServer.Management.Smo.SmoException: System.Data.SqlClient.SqlError: The backup set holds a backup of a database other than the existing 'new-test-44444' database
Yes it does hold different backup and I want to overwrite and replace it also want to move mdf and log files to new files. Am I missing something here in the options of restore?
Many thanks
Ismail
Ok fixed the issue I need to give it the current db logical file name what i was actually doing was giving it the new db logical file name so
//get the logical file names
DataTable dtFileList = restore.ReadFileList(smoServer);
string dbLogicalName = dtFileList.Rows[0][0].ToString();
string logLogicalName = dtFileList.Rows[1][0].ToString();
restore.RelocateFiles.Add(GetRelocateFile(dbLogicalName, dbPath));
restore.RelocateFiles.Add(GetRelocateFile(logLogicalName, logPath));
This works nicely.
Don't create a new database and try to restore on it. Instead use the below query.
RESTORE DATABASE dbname from disk='location' WITH MOVE 'data' TO 'name.mdf' MOVE '_Log' TO 'name_log.ldf'
To Replace the existing database, put its name on the dbname and use WITH REPLACE on the query

Problem in Restoring a Database using C#.net Coding

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.

Using SMO to copy a database and data

I am trying to make a copy of a database to a new database on the same server. The server is my local computer running SQL 2008 Express under Windows XP.
Doing this should be quite easy using the SMO.Transfer class and it almost works!
My code is as follows (somewhat simplified):
Server server = new Server("server");
Database sourceDatabase = server.Databases["source database"];
Database newDatbase = new Database(server, "new name");
newDatbase.Create();
Transfer transfer = new Transfer(sourceDatabase);
transfer.CopyAllObjects = true;
transfer.Options.WithDependencies = true;
transfer.DestinationDatabase = newDatbase.Name;
transfer.CopySchema = true;
transfer.CopyData = true;
StringCollection transferScript = transfer.ScriptTransfer();
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand switchDatabase = new SqlCommand("USE " + newDatbase.Name, conn))
{
switchDatabase.ExecuteNonQuery();
}
foreach (string scriptLine in transferScript)
{
using (SqlCommand scriptCmd = new SqlCommand(scriptLine, conn, transaction))
{
int res = scriptCmd.ExecuteNonQuery();
}
}
}
What I do here is to first create a new database, then generate a copy script using the Transfer class and finally running the script in the new database.
This works fine for copying the structure, but the CopyData option doesn't work!
Are there any undocumented limits to the CopyData option? The documentation only says that the option specifies whether data is copied.
I tried using the TransferData() method to copy the databse without using a script but then I get an exception that says "Failed to connect to server" with an inner exception that says "A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 - Could not open a connection to SQL Server)"
I also tried to enable Named Pipes on the server, but that doesn't help.
Edit:
I found a solution that works by making a backup and then restoring it to a new database. It's quite clumsy though, and slower than it should be, so I'm still looking for a better solution.
Well, after contacting Microsft Support I got it working properly, but it is slow and more or less useless. Doing a backup and then a restore is much faster and I will be using it as long as the new copy should live on the same server as the original.
The working code is as follows:
ServerConnection conn = new ServerConnection("rune\\sql2008");
Server server = new Server(conn);
Database newdb = new Database(server, "new database");
newdb.Create();
Transfer transfer = new Transfer(server.Databases["source database"]);
transfer.CopyAllObjects = true;
transfer.CopyAllUsers = true;
transfer.Options.WithDependencies = true;
transfer.DestinationDatabase = newdb.Name;
transfer.DestinationServer = server.Name;
transfer.DestinationLoginSecure = true;
transfer.CopySchema = true;
transfer.CopyData = true;
transfer.Options.ContinueScriptingOnError = true;
transfer.TransferData();
The trick was to set the DestinationDatabase property. This must be set even if the target is that same as the source. In addition I had to connect to the server as a named instance instead of using the other connection options.
Try setting SetDefaultInitFields to true on the Server object.
I had the same issue with the SMO database object running slowly. I guess this is because sql server doesn't like to retrieve entire objects and collections at once, instead lazy loading everything, causing a round-trip for each field, which for an entire database is pretty inefficient.
I had a go at getting this working and have come up with an answer that doesn't use the Transfer class. Here is the Method i used:
public bool CreateScript(string oldDatabase, string newDatabase)
{
SqlConnection conn = new SqlConnection("Data Source=.;Initial Catalog=" + newDatabase + ";User Id=sa;Password=sa;");
try
{
Server sv = new Server();
Database db = sv.Databases[oldDatabase];
Database newDatbase = new Database(sv, newDatabase);
newDatbase.Create();
ScriptingOptions options = new ScriptingOptions();
StringBuilder sb = new StringBuilder();
options.ScriptData = true;
options.ScriptDrops = false;
options.ScriptSchema = true;
options.EnforceScriptingOptions = true;
options.Indexes = true;
options.IncludeHeaders = true;
options.WithDependencies = true;
TableCollection tables = db.Tables;
conn.Open();
foreach (Table mytable in tables)
{
foreach (string line in db.Tables[mytable.Name].EnumScript(options))
{
sb.Append(line + "\r\n");
}
}
string[] splitter = new string[] { "\r\nGO\r\n" };
string[] commandTexts = sb.ToString().Split(splitter, StringSplitOptions.RemoveEmptyEntries);
foreach (string command in commandTexts)
{
SqlCommand comm = new SqlCommand(command, conn);
comm.ExecuteNonQuery();
}
return true;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("PROGRAM FAILED: " + e.Message);
return false;
}
finally
{
conn.Close();
}
}
Here is my solution:
I have a Database named is Olddatabase
I backup it to E:\databackup\Old.bak
If you want to create a Duplicate Database from Olddatabase in the same server with name NewDatabase
3.1 You can use command in query tool : EXEC OldDatabase.dbo.sp_helpfile;
to determinat path of OldDatabase is stored in case you want to save NewDatabase in the same folder.
or You can save NewDatabase in new Path which you want
use this command in Query tool
RESTORE DATABASE NewDatabase FROM DISK = 'E:\databackup\Old.bak'
WITH MOVE 'OldDatabase' TO 'E:\New path (or the same path)\NewDatabase_Data.mdf',
MOVE 'OldDatabase_log' TO 'E:\New path (or the same path)\NewDatabase_Log.ldf';
Note: you can Use these command obove in c# with : Create a Store procedure in sql which include Above commands. And you can call the store procedure in C #

Categories