I am trying to programatically restore an SQL database backup using Microsoft.SqlServer.Management.Smo.
This is my code so far:
Server corp = new Server(SQLServerName);
Restore restore = new Restore();
restore.Database = databaseName;
restore.Action = RestoreActionType.Database;
restore.Devices.AddDevice(LocalBackupFilePath, DeviceType.File);
restore.PercentCompleteNotification = 5;
restore.ReplaceDatabase = true;
restore.PercentComplete += DatabaseRestorePercentComplete;
restore.Complete += DatabaseRestoreComplete;
DataTable dbFiles = restore.ReadFileList(corp);
string backupLogicalName_MDF = dbFiles.Rows[0][0].ToString();
string backupLogicalName_LDF = dbFiles.Rows[1][0].ToString();
restore.RelocateFiles.Add(new RelocateFile(backupLogicalName_MDF, #"G:\Databases"));
restore.RelocateFiles.Add(new RelocateFile(backupLogicalName_LDF, #"G:\SqlTranslogs"));
restore.SqlRestore(corp);
When I execute this it gives this error:
Microsoft.SqlServer.Management.Smo.FailedOperationException: Restore failed for Server '<server>'. ---> Microsoft.SqlServer.Management.Smo.SmoException: System.Data.SqlClient.SqlError: The operating system returned the error '5(Access is denied.)' while attempting 'RestoreContainer::ValidateTargetForCreation' on 'G:\Databases'.
From what I read, this is because the user the code is running as doesn't have the right permissions. I don't know what user the code is running as, neither how to change it.
Could you, please, advise me if there is a way to solve this without changing the user and if not - how to change it?
Related
I am attempting to use SMO to write a simple utility to backup/restore databases. This works very well when there is only one point in time in the backup file. However when there is a backup file that has multiple backup points defined (not backup sets) SMO always chooses the earliest, whereas SSMS will always choose the latest.
This leads to an incorrect restore of the data, and I would like to figure out if there is a property that I can set that will force the Restore class to always use the latest backup point.
I have already tried to set the Restore.ToPointInTime but that won't work due to the recovery model of the database being simple.
I have found a MSDN article describing how to choose your restore time, and it includes setting the database to full recovery mode:
http://technet.microsoft.com/en-us/library/ms179451(v=sql.105).aspx
Is this necessary when using SMO, and is there a way to do it using pure SMO (no C# sql commands)? I have used the Restore.ReadBackupHeaders and from that I am able to extract the available backup points in time, but not able to set the one to restore anywhere.
EDIT:
Here is the code I am using, including a recent change which attempts to set the database recovery model via smo:
public void RestoreDatabase(string databaseName, string backupPath)
{
var server = new Server(GetServerConnection());
//If the database doesn't exist, create it so that we have something
//to overwrite.
if (!server.Databases.Contains(databaseName))
{
var database = new Database(server, databaseName);
database.Create();
}
var targetDatabase = server.Databases[databaseName];
targetDatabase.RecoveryModel = RecoveryModel.Full;
targetDatabase.Alter();
Restore restore = new Restore();
var backupDeviceItem = new BackupDeviceItem(backupPath, DeviceType.File);
restore.Devices.Add(backupDeviceItem);
restore.Database = databaseName;
restore.ReplaceDatabase = true;
restore.Action = RestoreActionType.Database;
var fileList = restore.ReadFileList(server);
var dataFile = new RelocateFile();
string mdf = fileList.Rows[0][1].ToString();
dataFile.LogicalFileName = fileList.Rows[0][0].ToString();
dataFile.PhysicalFileName = server.Databases[databaseName].FileGroups[0].Files[0].FileName;
var logFile = new RelocateFile();
string ldf = fileList.Rows[1][1].ToString();
logFile.LogicalFileName = fileList.Rows[1][0].ToString();
logFile.PhysicalFileName = server.Databases[databaseName].LogFiles[0].FileName;
restore.RelocateFiles.Add(dataFile);
restore.RelocateFiles.Add(logFile);
var backupHeaderInfo = GetBackupHeaderInformation(restore, server);
var latestBackupDate = backupHeaderInfo.Max(backupInfo => backupInfo.BackupStartDate);
restore.ToPointInTime = latestBackupDate.ToString();
server.KillAllProcesses(databaseName);
restore.SqlRestore(server);
}
It seems like this should do the trick, however the line
targetDatabase.RecoveryModel = RecoveryModel.Full
does not seem to do anything to change the recovery model, leading me to still get the following exception:
The STOPAT option is not supported for databases that use the SIMPLE recovery model.
RESTORE DATABASE is terminating abnormally.
EDIT 2:
I added the line
targetDatabase.Alter();
and it fixed the not updating problem. However It now restores but leaves the database in restoring mode, so it is unable to be queried.
EDIT 3:
I got the code working by setting the Restore.FileNumber property to be the maximum value of the positions in the BackupHeaders, which seems to do the trick, though I'm still unsure why the backup file has multiple backup headers, but only a single backup set.
The working code is below.
public void RestoreDatabase(string databaseName, string backupPath)
{
var server = new Server(GetServerConnection());
//If the database doesn't exist, create it so that we have something
//to overwrite.
if (!server.Databases.Contains(databaseName))
{
var database = new Database(server, databaseName);
database.Create();
}
var targetDatabase = server.Databases[databaseName];
targetDatabase.RecoveryModel = RecoveryModel.Full;
targetDatabase.Alter();
Restore restore = new Restore();
var backupDeviceItem = new BackupDeviceItem(backupPath, DeviceType.File);
restore.Devices.Add(backupDeviceItem);
restore.Database = databaseName;
restore.ReplaceDatabase = true;
restore.NoRecovery = false;
restore.Action = RestoreActionType.Database;
var fileList = restore.ReadFileList(server);
var dataFile = new RelocateFile();
dataFile.LogicalFileName = fileList.Rows[0][0].ToString();
dataFile.PhysicalFileName = server.Databases[databaseName].FileGroups[0].Files[0].FileName;
var logFile = new RelocateFile();
logFile.LogicalFileName = fileList.Rows[1][0].ToString();
logFile.PhysicalFileName = server.Databases[databaseName].LogFiles[0].FileName;
restore.RelocateFiles.Add(dataFile);
restore.RelocateFiles.Add(logFile);
var backupHeaderInfo = GetBackupHeaderInformation(restore, server);
restore.FileNumber = backupHeaderInfo.Where(backupInfo => backupInfo.BackupType == BackupType.Database).Max(backupInfo => backupInfo.Position);
server.KillAllProcesses(databaseName);
restore.SqlRestore(server);
targetDatabase.SetOnline();
}
Despite your saying that you don't have multiple backup sets, I think you do. From the documentation for the backupset table:
A backup set contains the backup from a single, successful backup operation.
So, if you have "multiple restore points" in a single backup file, you have multiple backup sets. Verify this by querying the dbo.backupset table in msdb
Pedantry aside, I think you're looking for the FileNumber property on the Restore object. This corresponds to the FILE = n backup set option in the T-SQL restore command. In order to get the last one, just pull the last row from your ReadBackupHeaders call.
To test for yourself, go through the motions of performing a restore through SSMS and then, instead of hitting "ok", hit the "Script" button near the top. I suspect that you'll see a FILE = <some number> in there somewhere.
I am trying to backup and restore sql server 2008r2 database, and i successfully backup my database , But the problem is when i am trying to restore my DB its showing the following error
Restore failed for Server 'XXXX-PC\SQLEXPRESS'
And Another important thing is that , When i Close the DB Connection (From server Explorer [right-click->Close Connection]) its working Normally And Correctly
Below is my Backup And Restore Code
Please help me to fix the problem
public static void BackupDatabase(string backUpFile)
{
ServerConnection con = new ServerConnection(#"XXXX-PC\SQLEXPRESS");
Server server = new Server(con);
Backup source = new Backup();
source.Action = BackupActionType.Database;
source.Database = "testBD";
BackupDeviceItem destination = new BackupDeviceItem(backUpFile, DeviceType.File);
source.Devices.Add(destination);
//source.Devices.AddDevice(#"G:\MyBackUp.bak", DeviceType.File);
source.SqlBackup(server);
con.Disconnect();
}
public static void RestoreDatabase(string backUpFile)
{
ServerConnection con = new ServerConnection(#"XXXX-PC\SQLEXPRESS");
Server server = new Server(con);
Restore destination = new Restore();
destination.Action = RestoreActionType.Database;
destination.Database = "testBD";
BackupDeviceItem source = new BackupDeviceItem(backUpFile, DeviceType.File);
destination.Devices.Add(source);
destination.ReplaceDatabase = true;
con.Disconnect();
server.ConnectionContext.Disconnect();
destination.SqlRestore(server);
}
This is the error :
This is the error message in detail:
My guess is that your restore process can't get exclusive access to the database. The Server SMO object has a KillAllProcesses method that takes a database as an argument and does what it says on the tin.
I am writing a simple database backup and restore routine for an application. I can backup my database without issues, however when I restore is I am unable to gain exclusive access to my database.
I am trying all the combinations of fixes on SO, putting in single user mode, taking it offline then placing it back only with no success.
I can successfully restore the database within studio manager (express)
This method is the only connection to the SQL server at the time, so I don't understand why I can't perform the restore.
Appreciate the help to point out where the issue may be.
internal void RestoreDatabase(string databaseFile)
{
//get database details
var databaseConfiguration = new DatabaseConfiguration().GetDatabaseConfiguration();
try
{
//construct server connection string
var connection = databaseConfiguration.IsSqlAuthentication
? new ServerConnection(databaseConfiguration.ServerInstance,
databaseConfiguration.SqlUsername,
databaseConfiguration.SqlPassword)
: new ServerConnection(databaseConfiguration.ServerInstance);
//set database to single user and kick everyone off
using (
var sqlconnection =
new SqlConnection(new DatabaseConfiguration().MakeConnectionString(databaseConfiguration)))
{
sqlconnection.Open();
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET Single_User WITH Rollback IMMEDIATE",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET OFFLINE",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET ONLINE",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
sqlconnection.Close();
}
//setup server connection and restore
var server = new Server(connection);
var restore = new Restore();
restore.Database = databaseConfiguration.DatabaseName;
restore.Action = RestoreActionType.Database;
restore.Devices.AddDevice(databaseFile, DeviceType.File);
restore.ReplaceDatabase = true;
restore.Complete += Restore_Complete;
restore.SqlRestore(server);
}
catch (Exception ex)
{
//my bad
restoreDatabaseServerError(ex.InnerException.Message, EventArgs.Empty);
}
finally
{
//set database to multi user
using (
var sqlconnection =
new SqlConnection(new DatabaseConfiguration().MakeConnectionString(databaseConfiguration)))
{
sqlconnection.Open();
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET Multi_User",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
sqlcommand.Dispose();
}
sqlconnection.Close();
}
}
}
If anybody is connected to your database, SQL Server cannot drop it, so you have to disconnect existing connections, as you have tried. The problem with single_user is, that it still allows a single user to connect. As you yourself cannot be connected to the database when dropping it you have to get out of there. That opens up that slot for someone else to connect and in turn prevent you from dropping it.
There are a few SQL Server processes that are particularly good at connecting to a database in that split second. Replication is one example. (You shouldn't really drop a database that is published anyway, bat that is another story.)
So what can we do about this? The only 100% safe way is to prevent users from connecting to the database. The only practical way is to switch the database offline and then drop it. However, that has the nasty side effect, that SQL Server does not delete the files of that database, so you have to do that manually.
Another option is to just be fast enough. In your example you bring the database back online before you drop it. That is a fairly resource intensive process that gives an "intruder" lots of time to connect.
The solution I have been using with success looks like this:
ALTER DATABASE MyDb SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE;
USE MyDb;
ALTER DATABASE MyDb SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
USE tempdb;
DROP DATABASE MyDb;
This first sets the database to restricted user and connects to it. Then, while still connected it sets the database to single user. Afterwards the context is switched to tempdb and the drop is executed immediately thereafter. Important here is, to send these commands as one batch to SQL Server to minimize the time between the USE tempdb; and the DROP. Setting the database to restricted user in the beginning catches some rare edge cases, so leave it in even though it does not make sense at first glance.
While this still leaves a theoretical gap for someone else to get in, I have never seen it fail.
After the database is dropped you can run your restore as normal.
Good luck.
Your restore needs to take place on the same connection you set the DB server to single user mode.
In summary for the changes below, I moved the end of the using to below your restore code, and moved the close for the SQL connection to after the restore so it uses the same connection. Also removed set offline and online since they aren't needed. Can't test at the moment, so let me know if it works.
//set database to single user and kick everyone off
using (var sqlconnection = new SqlConnection(new DatabaseConfiguration().MakeConnectionString(databaseConfiguration)))
{
sqlconnection.Open();
using (var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET Single_User WITH Rollback IMMEDIATE",sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
//setup server connection and restore
var server = new Server(sqlconnection);
var restore = new Restore();
restore.Database = databaseConfiguration.DatabaseName;
restore.Action = RestoreActionType.Database;
restore.Devices.AddDevice(databaseFile, DeviceType.File);
restore.ReplaceDatabase = true;
restore.Complete += Restore_Complete;
restore.SqlRestore(server);
sqlconnection.Close();
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Exclusive access could not be obtained because the database is in use
I use C#, .net4 and SQL Server 2008 R2 in project and use this code to restore a database:
_comm = new SqlCommand("use master; RESTORE DATABASE [DB1] FROM DISK = #Address WITH RESTRICTED_USER, FILE = 1, NOUNLOAD, REPLACE, STATS = 10; use DB1;", _conn);
_comm.CommandType = System.Data.CommandType.Text;
_comm.Parameters.AddRange(new SqlParameter[]
{
new SqlParameter("#Address", _path)
});
_conn.Open();
_comm.ExecuteNonQuery();
The following error is displayed:
Exclusive access could not be obtained because the database is in use.
RESTORE DATABASE is terminating abnormally.
Changed database context to 'master'.
Changed database context to 'DB1'.
its better to use SMO (cause no need for master) once you are connected to the server you do whatever you want
using Microsoft.SqlServer.Management.Smo;
...
Server myServer = new Server(#".\SQLExpress");
Database mydb = myServer.Databases["DB1"];
if(mydb!=null)
myServer.KillAllProcesses(mydb.Name);//detach
Restore restoreDB = new Restore();
restoreDB.Database = mydb.Name;
restoreDB.Action = RestoreActionType.Database;
restoreDB.Devices.AddDevice(_path, DeviceType.File);
restoreDB.ReplaceDatabase = true;
restoreDB.NoRecovery = false;
restoreDB.SqlRestore(myServer);
BACKUP
Server myServer = new Server(#".\SQLExpress");
Database mydb = myServer.Databases["DB1"];
Backup bkp = new Backup();
bkp.Action = BackupActionType.Database;
bkp.Database = mydb.Name;
bkp.Devices.AddDevice(_path, DeviceType.File);
bkp.BackupSetName = "DB1 backup";//optional
bkp.BackupSetDescription = "mybackup dated " + DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss");//optional
bkp.Initialize = true;
bkp.Incremental = false;
bkp.SqlBackup(myServer);
References :
C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.ConnectionInfo.dll
C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.Management.Sdk.Sfc.dll
C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.Smo.dll
C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.SmoExtended.dll
Insert the line below before calling the SqlCommand:
_conn.ChangeDatabase("master");
When running the code below, I keep getting "access is denied", but i have full administrative rights on the folder.
{"Cannot open backup device '\\\\networkDrive\backups\\'. Operating system error 5(Access is denied.).\r\nBACKUP DATABASE is terminating abnormally."}
the folder target is #"\networkDrive\backups";
I've also tried #"C:\backups"\ and #"C:\backups"
private static string publicConString = "server=myServer;Trusted_Connection=Yes;persist security info=False;connection timeout=120";
public static void BackupDatabase(String databaseName, String userName, String password, String serverName, String destinationPath)
{
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);
SqlConnection sqlCon = new SqlConnection(publicConString);
ServerConnection connection = new ServerConnection(sqlCon);
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);
From your description, I think you would like to back up database to a network share folder, right?
Based on the error message, I think the SQL Server service account doesn’t have sufficient permission on the share folder. Please ensure the SQL Server service account is a domain account and it has sufficient permissions.
Please refer to this info for more information.
You cannot back up databases to a network drive if your account have not sufficient permissions to access the network drive.