Can't restore a .bak file to a database - c#

I have successfully restored a database using smo restore method. But when I try to restore the same .bak file to another database it returns an exception as seen below
I have created the .bak file from a database template I used which is GC_BranchName. GC_Muntinlupa is the first database I have created and restored the .bak file on to it successfully. Can anyone advise me on this?
Here is my restore code:
bool RestoreDB(string name)
{
try
{
var connection = new ServerConnection(Properties.Settings.Default.Well);
var sqlServer = new Server(connection);
var rstDatabase = new Restore();
rstDatabase.Database = name;
rstDatabase.Action = RestoreActionType.Database;
rstDatabase.Devices.AddDevice(System.Environment.CurrentDirectory + "\\GC.bak", DeviceType.File);
rstDatabase.ReplaceDatabase = true;
rstDatabase.SqlRestore(sqlServer);
connection.Disconnect();
return true;
}
catch (Exception ex)
{
MessageBox.Show("A problem occured when building the branch!" + ex, "Monytron Consolidator", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}

Sorry J.P Masangcay but i didn't had time to put it all together for you until now.
Here is the solution that works for you, verified by me.
Your problem is that you have file conficts if you restore your database backup to a new database.
Why file conflicts?
The default behavior of SQL Server is to restore the database using file paths and names for data and log files as they have been at the time of the backup. Therefore, if that originating database exists at the time of the restore process this leads to filesystem conflicts. They same conflict arises if you restore your database backup template to another server multiple times.
Your Case:
Database: GC_BranchName
Data File: GC_BranchName.mdf
Log File: GC_BranchName_log.ldf
Resore Database: GC_Muntinlupa
Data File: GC_BranchName.mdf
Log File: GC_BranchName_log.ldf
To solve the proplem you have to relocate files to have distinct filenames for your databases. Here is a solution, which simply adds/prepends the "new database name" to all files. This relocating under the hood restores the database using the TSQL "RESTORE DATABASE ... WITH MOVE" option, as your exception above is suggesting.
Resore Database: GC_Muntinlupa
Data File: GC_Muntinlupa_GC_BranchName.mdf
Log File: GC_Muntinlupa_GC_BranchName_log.ldf
bool RestoreDB(string name)
{
try
{
var connection = ServerConnection(Properties.Settings.Default.Well);
var sqlServer = new Server(connection);
var rstDatabase = new Restore();
rstDatabase.Database = name;
rstDatabase.Action = RestoreActionType.Database;
rstDatabase.Devices.AddDevice(System.Environment.CurrentDirectory + "\\GC.bak", DeviceType.File);
rstDatabase.ReplaceDatabase = true;
foreach (DataRow r in rstDatabase.ReadFileList(sqlServer).Rows)
{
var relocateFile = new RelocateFile();
relocateFile.LogicalFileName = r["LogicalName"].ToString();
// move/rename physical filename by prepending database name to prevent FileSystem conflicts
var physicalName = r["PhysicalName"].ToString();
var path = System.IO.Path.GetDirectoryName(physicalName);
var filename = System.IO.Path.GetFileName(physicalName);
physicalName = System.IO.Path.Combine(path, string.Format("{0}_{1}", name, filename));
relocateFile.PhysicalFileName = physicalName;
rstDatabase.RelocateFiles.Add(relocateFile);
}
rstDatabase.SqlRestore(sqlServer);
connection.Disconnect();
return true;
}
catch (Exception ex)
{
MessageBox.Show("A problem occured when building the branch!" + ex, "Monytron Consolidator", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}

This should solve the issue
namespace DB_Restore
{
class Program
{
static void Main(string[] args)
{
RestoreDatabase();
}
public static void RestoreDatabase()
{
try
{
ServerConnection connection = new ServerConnection(#"Server\instance", "uname", "PWD");
Server sqlServer = new Server(connection);
Restore rstDatabase = new Restore();
rstDatabase.Action = RestoreActionType.Database;
rstDatabase.Database = "H5MI_Automation_Restore_Backup";
BackupDeviceItem bkpDevice = new BackupDeviceItem(#"E:\DATA\QA_SP\MSSQL11.QA_SP\MSSQL\Backup\H5MI_Automation.bak", DeviceType.File);
rstDatabase.Devices.Add(bkpDevice);
rstDatabase.ReplaceDatabase = true;
//As mentioned in the above solution this code will take care .mdf and .ldf file location issue
foreach (DataRow r in rstDatabase.ReadFileList(sqlServer).Rows)
{
var relocateFile = new RelocateFile();
relocateFile.LogicalFileName = r["LogicalName"].ToString();
Console.WriteLine(relocateFile.LogicalFileName);
var physicalName = r["PhysicalName"].ToString();
Console.WriteLine(physicalName);
var path = System.IO.Path.GetDirectoryName(physicalName);
Console.WriteLine(path);
var filename = System.IO.Path.GetFileName(physicalName);
Console.WriteLine(filename);
physicalName = path + #"\H5MI_Automation_Restore_Backup_" + filename;
Console.WriteLine(physicalName);
relocateFile.PhysicalFileName = physicalName;
Console.WriteLine(relocateFile.PhysicalFileName);
Console.WriteLine(relocateFile);
rstDatabase.RelocateFiles.Add(relocateFile);
}
rstDatabase.SqlRestore(sqlServer);
connection.Disconnect();
}
catch (Exception e)
{
Console.Write(e);
}
}
}
}
Add the explanation of the code. Essentially the issue in the above-attached screenshot is because of the .mdf and .ldf file location. When you try to manually restore the DB u will also face the same issue. attaching the screenshot.Manual DB restore
Why its throwing error?
Its because the file is used by the actual DB(from where .bak file is genrated)
So, this piece of code will help you to change the location of both .mdf and .ldf file path.
foreach (DataRow r in rstDatabase.ReadFileList(sqlServer).Rows)
{
var relocateFile = new RelocateFile();
relocateFile.LogicalFileName = r["LogicalName"].ToString();
Console.WriteLine(relocateFile.LogicalFileName);
var physicalName = r["PhysicalName"].ToString();
Console.WriteLine(physicalName);
var path = System.IO.Path.GetDirectoryName(physicalName);
Console.WriteLine(path);
var filename = System.IO.Path.GetFileName(physicalName);
Console.WriteLine(filename);
physicalName = path + #"\H5MI_Automation_Restore_Backup_" + filename;
Console.WriteLine(physicalName);
relocateFile.PhysicalFileName = physicalName;
Console.WriteLine(relocateFile.PhysicalFileName);
Console.WriteLine(relocateFile);
rstDatabase.RelocateFiles.Add(relocateFile);
}

Related

Backup SQLite database using SaveFileDialog?

I am following the guidance provided here:
Sqlite Online Backup Using System.Data.Sqlite
And it's working as I expect, but I'd like to pass the BackupDb.db file into a SaveFileDialog so that user can save the file outside of the applications folder, preferably on an external drive. I just can't figure this out.
This is what I have so far:
private void btn_backup_db_Click(object sender, RoutedEventArgs e)
{
var ofd1 = new Microsoft.Win32.SaveFileDialog();
using (var source = new SQLiteConnection("Data Source=database.db; Version=3;"))
using (var destination = new SQLiteConnection("Data Source=BackupDb.db; Version=3;"))
{
source.Open();
destination.Open();
source.BackupDatabase(destination, "main", "main", -1, null, 0);
//Microsoft.Win32.SaveFileDialog saveFileDialog;
ofd1.ShowDialog();
}
I know I am missing a step, but I'm not sure what step. I have the ofd1 variable defined as opening a SaveFileDialog, and I'm telling it to open with of1.ShowDialog(); but I can't pass "main" or "destination" into the SaveFileDialog because SQLite doesn't have a definition for showing a dialog.
I'm trying to get the path to the file using:
private string MyDirectory()
{
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
But I'm getting an exception stating that I don't have read access to the directory.
So then, I switched to trying this, but I'm not sure how to use it correctly. It's pointing to the base directory, but can't locate anything in bin\debug:
var dir = AppDomain.CurrentDomain.BaseDirectory;
This ended up being the answer, I included everything from btn_backup_db_Click:
private void btn_backup_db_Click(object sender, RoutedEventArgs e)
{
var ofd1 = new Microsoft.Win32.SaveFileDialog();
ofd1.Filter = "Database Files (*.db)|*.db";
ofd1.FileName = "database";
// customize file dialog properties here
if (ofd1.ShowDialog() == true)
{
var path = Path.GetFullPath(ofd1.FileName);
var destinationCnx = "Data Source=" + path + "; Version=3;";
using (var source = new SQLiteConnection("Data Source=database.db; Version=3;"))
using (var destination = new SQLiteConnection(destinationCnx))
{
source.Open();
destination.Open();
source.BackupDatabase(destination, "main", "main", -1, null, 0);
}
}
else
{
MessageBox.Show("Error");
}
}
Thanks so much everyone!
The SaveFileDialog class is only an utility to let the user to select where he want to save a file (in the form of path).
To summarize, you need to display the file dialog before doing anything with sqlite. When you got the path, you need to build the back connectionstring.
var ofd1 = new SaveFileDialog();
// customize file dialog properties here
if (ofd1.ShowDialog() == true)
{
var path = Path.GetFullPath(ofd1.FileName);
var destinationCnx = "Data Source=" + path + "; Version=3;"
// do sqlite stuff here
}

Restore network database on SQL Server without copy it

I trying to restore a SQL Server backup over the network.
I found a lot of way to copy file over network but backup files are big I don't want to copy them.
My problem is simple: this line
var fileList = restore.ReadFileList(smoServer);
executes on SQL Server.
This one define the backup I try to restore
var backupDeviceItem = new BackupDeviceItem(path, DeviceType.File);
The problem is the path looks like: 10.\*.\*.\*\\file.bak
So I get an exception that says
"10.*.*.*" path not found on server
That is logical - I need to provide credential for the machine 10.\*.\*.\*
If path parameter is C:\file.bak, my code works - if it's \\10.\*.\*.\*\\file.bak, my code says "file not found".
The BackupDeviceItem has a constructor with credentials but its SQL credential... or I'm already on my SQL Server... I need credentials for the machine that has the .bak file ...
Any ideas?
Just in case someone want my entire code here is my helper
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using System;
using System.IO;
namespace SqlManager.Tools
{
public class SMOHelper
{
public static void RestoreDatabase(string path, string restore_name, SqlConfig sqlAccess)
{
ServerConnection SqlConnection;
if (String.IsNullOrWhiteSpace(sqlAccess.username))
{
SqlConnection = new ServerConnection(sqlAccess.uri);
}
else
{
SqlConnection = new ServerConnection(sqlAccess.uri, sqlAccess.username, sqlAccess.password);
}
Server smoServer = new Server(SqlConnection);
try
{
// On crée la BDD
if (!smoServer.Databases.Contains(restore_name))
{
Logger.Info("Creation de la base:");
Logger.Info(restore_name);
var database = new Database(smoServer, restore_name);
database.Create();
}
Logger.Success("Connexion SQL réussi");
var targetDatabase = smoServer.Databases[restore_name];
targetDatabase.RecoveryModel = RecoveryModel.Simple;
targetDatabase.Alter();
Restore restore = new Restore();
var backupDeviceItem = new BackupDeviceItem(path, DeviceType.File);
restore.Devices.Add(backupDeviceItem);
restore.Database = restore_name;
restore.ReplaceDatabase = true;
restore.NoRecovery = false;
restore.Action = RestoreActionType.Database;
var fileList = restore.ReadFileList(smoServer);
Logger.Success("Fichier de backup trouvé sur le serveur distant");
var dataFile = new RelocateFile();
dataFile.LogicalFileName = fileList.Rows[0][0].ToString();
dataFile.PhysicalFileName = smoServer.Databases[restore_name].FileGroups[0].Files[0].FileName;
var logFile = new RelocateFile();
logFile.LogicalFileName = fileList.Rows[1][0].ToString();
logFile.PhysicalFileName = smoServer.Databases[restore_name].LogFiles[0].FileName;
restore.RelocateFiles.Add(dataFile);
restore.RelocateFiles.Add(logFile);
var backupHeaderInfo = restore.ReadBackupHeader(smoServer);
smoServer.KillAllProcesses(restore_name);
Logger.Info("Debut de la restauration sur le server");
restore.SqlRestore(smoServer);
Logger.Success("Restauration terminer");
targetDatabase.SetOnline();
SqlConnection.Disconnect();
}
catch (SmoException ex)
{
Logger.Error("SMO Message : " + ex.Message);
Logger.Error("SMO Exception : " + ex.InnerException);
}
catch (IOException ex)
{
Logger.Error("IO Message : " + ex.Message);
Logger.Error("IO Exception : " + ex.InnerException);
}
catch (Exception ex)
{
Logger.Error("Message : " + ex.Message);
Logger.Error("Exception : " + ex.InnerException);
}
}
}
}
You say
So i get an exception that say "10.*.*.*" path not found on server
You cannot use an asterisk in a folder name or a file name on a Windows server. See Naming Files, Paths, and Namespaces

Backup and restore Azure database programmatically with smo

We have a running web application. Everything is hosted on azure.
We have one sql server and two databases: production and test.
Client requested functionality to backup production db and restore it in place of test db. I am using following code:
public static void BackupDatabase(string databaseName, string userName, string password, string serverName, string destinationPath)
{
try
{
Backup sqlBackup = new Backup();
sqlBackup.Action = BackupActionType.Database;
sqlBackup.BackupSetDescription = "ArchiveDataBase:" + DateTime.Now.ToShortDateString();
sqlBackup.BackupSetName = "Archive";
sqlBackup.Database = databaseName;
BackupDeviceItem deviceItem = new BackupDeviceItem(destinationPath, DeviceType.File);
ServerConnection connection = new ServerConnection(serverName, userName, password);
Server sqlServer = new Server(connection);
Database db = sqlServer.Databases[databaseName];
sqlBackup.Initialize = true;
sqlBackup.Checksum = true;
sqlBackup.ContinueAfterError = true;
sqlBackup.Devices.Add(deviceItem);
sqlBackup.Incremental = false;
sqlBackup.ExpirationDate = DateTime.Now.AddDays(3);
sqlBackup.LogTruncation = BackupTruncateLogType.Truncate;
sqlBackup.FormatMedia = false;
sqlBackup.SqlBackup(sqlServer);
}
catch (FailedOperationException ex)
{
throw;
}
catch(ConnectionFailureException ex)
{
throw;
}
catch (Exception ex)
{
throw;
}
}
public static void RestoreDatabase(string databaseName, string filePath, string serverName, string userName, string password, string dataFilePath, string logFilePath)
{
try
{
Restore sqlRestore = new Restore();
BackupDeviceItem deviceItem = new BackupDeviceItem(filePath, DeviceType.File);
sqlRestore.Devices.Add(deviceItem);
sqlRestore.Database = databaseName;
ServerConnection connection = new ServerConnection(serverName, userName, password);
Server sqlServer = new Server(connection);
Database db = sqlServer.Databases[databaseName];
db.Drop();
sqlRestore.Action = RestoreActionType.Database;
string dataFileLocation = dataFilePath + databaseName + ".mdf";
string logFileLocation = logFilePath + databaseName + "_Log.ldf";
db = sqlServer.Databases[databaseName];
RelocateFile rf = new RelocateFile(databaseName, dataFileLocation);
System.Data.DataTable logicalRestoreFiles = sqlRestore.ReadFileList(sqlServer);
sqlRestore.RelocateFiles.Add(new RelocateFile(logicalRestoreFiles.Rows[0][0].ToString(), dataFileLocation));
sqlRestore.RelocateFiles.Add(new RelocateFile(logicalRestoreFiles.Rows[1][0].ToString(), logFileLocation));
sqlRestore.SqlRestore(sqlServer);
db = sqlServer.Databases[databaseName];
db.SetOnline();
sqlServer.Refresh();
}
catch(ExecutionFailureException ex)
{
throw;
}
}
Everything works locally and on all virtual machines. But now we have everything on Azure and I have no idea what backup directory should I set. It must be directory on server to which sqlclient (probably azure sql client) has access. Same thing goes when it comes to provide location during restoring database to test database.
How to get access to directories where backup can be stored and where mdf and log files can be saved?
Kind regards

File being used by another process error

Can anyone tell me how to get rid of the error
The process cannot access the file because it is being used by another process
Here is my code
if (!File.Exists(FlagFilePath))
{
Debug.WriteLine("Trying to download sales data file ");
SessionOptions sessionOptions = new SessionOptions
{
Protocol = Protocol.Sftp,
HostName = ConfigurationManager.AppSettings["SFTPDomain"],
UserName = ConfigurationManager.AppSettings["SFTPUser"],
Password = ConfigurationManager.AppSettings["SFTPPass"],
PortNumber = Convert.ToInt32(ConfigurationManager.AppSettings["SFTPPortNumber"]),
GiveUpSecurityAndAcceptAnySshHostKey = true,
};
using (Session session = new Session())
{
//Attempts to connect to your SFtp site
session.Open(sessionOptions);
//Get SFtp File
TransferOptions transferOptions = new TransferOptions();
transferOptions.TransferMode = TransferMode.Binary; //The Transfer Mode - Automatic, Binary, or Ascii
transferOptions.FilePermissions = null; //Permissions applied to remote files;
transferOptions.PreserveTimestamp = false; //Set last write time of destination file
//to that of source file - basically change the timestamp to match destination and source files.
transferOptions.ResumeSupport.State = TransferResumeSupportState.Off;
//SFTP File Path
Sftp_RemotePath = ConfigurationManager.AppSettings["SFTPFileName"].ToString();
//Delete File if Exist
if (System.IO.File.Exists(FilePath))
{
System.IO.File.Delete(FilePath);
}
//the parameter list is: remote Path, Local Path with filename
TransferOperationResult transferOperationResult = session.GetFiles(Sftp_RemotePath, FilePath , false, transferOptions);
//Throw on any error
transferOperationResult.Check();
Debug.WriteLine("Downloaded fresh sales data file!");
}
}
I am using MVC and have two controllers which access this class. When I run the controllers one at a time then it works fine but when I run both controllers together then I get this error in one of the controller:
WinSCP.SessionRemoteException: Can't create file 'D:\TESTING\SFTP\Data.csv'. ---> WinSCP.SessionRemoteException: System Error.
Code: 32.
The process cannot access the file because it is being used by another process
--- End of inner exception stack trace ---
at WinSCP.OperationResultBase.Check()
at JetStarAPI.Models.SFTPClient.DownloadFile(String FilePath) in D:\TESTING\SFTP\Models\SFTPClient.cs:line 65}
I am getting this error after this line
transferOperationResult.Check();
If I change the name of the file here
TransferOperationResult transferOperationResult = session.GetFiles(Sftp_RemotePath, FilePath+Path.GetRandomFileName() , false, transferOptions);
It works fine and save the file with random file name but I want to pass my FileName. How to solve this?
static bool IsDownloadInProgress = false;
public static string DownloadFile(string FilePath)
{
string SalesStatus = "ok";
try
{
if (!File.Exists(FlagFilePath) && !IsDownloadInProgress)
{
Debug.WriteLine("Trying to download sales data file ");
SessionOptions sessionOptions = new SessionOptions
{
Protocol = Protocol.Sftp,
HostName = ConfigurationManager.AppSettings["SFTPDomain"],
UserName = ConfigurationManager.AppSettings["SFTPUser"],
Password = ConfigurationManager.AppSettings["SFTPPass"],
PortNumber = Convert.ToInt32(ConfigurationManager.AppSettings["SFTPPortNumber"]),
GiveUpSecurityAndAcceptAnySshHostKey = true,
};
using (Session session = new Session())
{
//Attempts to connect to your SFtp site
session.Open(sessionOptions);
//Get SFtp File
TransferOptions transferOptions = new TransferOptions();
transferOptions.TransferMode = TransferMode.Binary; //The Transfer Mode - Automatic, Binary, or Ascii
transferOptions.FilePermissions = null; //Permissions applied to remote files;
transferOptions.PreserveTimestamp = false; //Set last write time of destination file
//to that of source file - basically change the timestamp to match destination and source files.
transferOptions.ResumeSupport.State = TransferResumeSupportState.On;
//SFTP File Path
Sftp_RemotePath = ConfigurationManager.AppSettings["SFTPFileName"].ToString();
//Delete File if Exist
if (System.IO.File.Exists(FilePath))
{
System.IO.File.Delete(FilePath);
}
//Throw on any error
session.FileTransferred += OnFileTransferComplete;
IsDownloadInProgress = true;
//the parameter list is: remote Path, Local Path with filename
// string result = Path.GetRandomFileName();
session.GetFiles(Sftp_RemotePath,FilePath,false, transferOptions).Check();
session.Dispose();
// File.Move(FilePath, "foo2.png");
Debug.WriteLine("Downloaded fresh sales data file!");
}
}
}
catch (Exception ex)
{
string _errorMsg = "";
// Setting Sales Status values
if (ex.InnerException != null)
{
if (ex.InnerException.Message.Contains("Authentication failed"))
{
_errorMsg = ex.InnerException.Message;
Debug.WriteLine("wrong username/password");
SalesStatus = "2";
}
else if (ex.InnerException.Message.Contains("No such file or directory"))
{
_errorMsg = ex.InnerException.Message;
Debug.WriteLine("File is not Available");
SalesStatus = "3";
}
}
else
{
_errorMsg = ex.Message;
Debug.WriteLine("General SFTP Error");
SalesStatus = "4";
}
//Create log error file
if (!File.Exists(FlagFilePath))
{
// create SFTP LocalErrorFlag
Debug.WriteLine("Creating SFTP flag file");
System.IO.File.WriteAllText(FlagFilePath, "SFTP Error: " + _errorMsg);
}
else
{
Debug.WriteLine("SFTP error Flag file already exists");
}
}
return SalesStatus;
}
private static void OnFileTransferComplete(object sender, TransferEventArgs e)
{
IsDownloadInProgress = false;
((Session)sender).FileTransferred -= OnFileTransferComplete;
}

BackupDevice.PhysicalLocation does not add to specified location

Using C# and SMO, when I create backups they are being copied to the default backup location used by SQL Server (C:\Program Files\Microsoft SQL Server\MSSQL11.SQLEXPRESS\MSSQL\Backup), instead of the physical location that I specify in code:
Database database = Server.Databases[dbName]);
Backup backup = new Backup();
device = new BackupDevice();
device.Parent = Server;
device.Name = dbName + ".bak";
device.BackupDeviceType = BackupDeviceType.Disk;
device.PhysicalLocation = Path.Combine(filePath + device.Name); // doesn't appear to do anything
device.Create();
backup.Action = BackupActionType.Database;
backup.Database = database.Name;
backup.Devices.AddDevice(filePath, DeviceType.File);
backup.SqlBackup(server);
When I run my code, I find that the path that I specified ("C:\backupTest") is empty and the backup has been added to the default backup location.
Anyone know why this is?
try with below code
static void BackupDataBase(string databaseName, string destinationPath)
{
try
{
Server myServer = GetServer();
Backup backup = new Backup();
backup.Action = BackupActionType.Database;
backup.Database = databaseName;
destinationPath = System.IO.Path.Combine(destinationPath, databaseName + ".bak");
backup.Devices.Add(new BackupDeviceItem(destinationPath, DeviceType.File));
backup.Initialize = true;
backup.Checksum = true;
backup.ContinueAfterError = true;
backup.Incremental = false;
backup.LogTruncation = BackupTruncateLogType.Truncate;
backup.SqlBackup(myServer);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static Server GetServer()
{
ServerConnection conn = new ServerConnection("server", "username", "pw");
Server myServer = new Server(conn);
return myServer;
}
refere this codeproject article for more information.

Categories

Resources