问题
TL;DR - I want the server to take a backup not my application because the server is set up to do so and my application won't have access.
Background
My company created software for clients 20 years ago written in Delphi 7/Pascal. I am re-writing the software in C#. As part of the re-write I have created new Firebird, Oracle, and SQL Server databases. Federal regulation requires that all of the existing data is maintained so I have created a database modification / transformation tool in order to change from the old database structure to the new one.
Before I start making the change I need to backup the existing database. The technician who will run this tool has no access to their local file structure and no manual access to the remote server where the database is housed. The tool accesses an encrypted .ini
-like file on the local system to parse out the components of the connection string and create a connection object. I then use that connection object to connect to the same database that the technicians computer is setup to connect to. This part all works
If I leave the default backup path alone it attempts to backup to default path but on the local machine(which technicians do not have access to create and we don't want a technician to have access to the .bak anyway) If I modify the default backup path to be a network path taken from the connection string, I get
SmoException: System.Data.SqlClient.SqlError: Cannot open backup device Operating system error 67(The network name cannot be found.).
because the filepath is not a network share (and won't be) and the database User credentials cannot access that path from outside of SQL Server.
So the question is: how do I have a backup taken to the remote default path as if I were on the server?
Here is the code that generates the error above (its the null case for remote).
public static void FullSqlBackup (Connection oldProactiveSql)
{
String sqlServerLogin = oldProactiveSql.UserName;
String password = oldProactiveSql.PassWord;
String instanceName = oldProactiveSql.InstanceName;
String remoteSvrName = oldProactiveSql.Ip + "," + oldProactiveSql.Port;
Server srv2;
Server srv3;
string device;
switch (oldProactiveSql.InstanceName)
{
case null:
ServerConnection srvConn2 = new ServerConnection(remoteSvrName);
srvConn2.LoginSecure = false;
srvConn2.Login = sqlServerLogin;
srvConn2.Password = password;
srv3 = new Server(srvConn2);
srv2 = null;
Console.WriteLine(srv3.Information.Version);
if (srv3.Settings.DefaultFile is null)
{
device = srv3.Information.RootDirectory + "\\DATA\\";
device = device.Substring(2);
device = oldProactiveSql.Ip + device;
}
else device = srv3.Settings.DefaultFile;
device = device.Substring(2);
device = string.Concat("\\\\", oldProactiveSql.Ip, device);
break;
default:
ServerConnection srvConn = new ServerConnection();
srvConn.ServerInstance = @".\" + instanceName;
srvConn.LoginSecure = false;
srvConn.Login = sqlServerLogin;
srvConn.Password = password;
srv2 = new Server(srvConn);
srv3 = null;
Console.WriteLine(srv2.Information.Version);
if (srv2.Settings.DefaultFile is null)
{
device = srv2.Information.RootDirectory + "\\DATA\\";
}
else device = srv2.Settings.DefaultFile;
break;
}
Backup bkpDbFull = new Backup();
bkpDbFull.Action = BackupActionType.Database;
bkpDbFull.Database = oldProactiveSql.DbName;
bkpDbFull.Devices.AddDevice(device, DeviceType.File);
bkpDbFull.BackupSetName = oldProactiveSql.DbName + " database Backup";
bkpDbFull.BackupSetDescription = oldProactiveSql.DbName + " database - Full Backup";
bkpDbFull.Initialize = true;
bkpDbFull.PercentComplete += CompletionStatusInPercent;
bkpDbFull.Complete += Backup_Completed;
switch (oldProactiveSql.InstanceName)
{
case null:
try
{
bkpDbFull.SqlBackup(srv3);
}
catch (Exception e)
{
Console.WriteLine (e);
Console.WriteLine(e.InnerException.Message);
throw;
}
break;
default:
try
{
bkpDbFull.SqlBackup(srv2);
}
catch (Exception e)
{
Console.WriteLine(e);
Console.WriteLine(e.InnerException.Message);
throw;
}
break;
}
}
Any help would be appreciated as I'm just running around in circles now.
From comments below I will try - 1. Dynamically create Stored Procedure [BackupToDefault] on database then run it. 2. If that fails link the database to itself. 3. Try - Exec [BackupToDefault] At [LinkedSelfSynonmym]
Wish me luck though it seems convoluted and the long way around I hope it works.
回答1:
For inspiration... the backup is split into 3 files, each file residing in a different directory(sql instance backup dir & sql instance default dir & database primary dir)
//// compile with:
// /r:Microsoft.SqlServer.Smo.dll
// /r:Microsoft.SqlServer.SmoExtended.dll
// /r:Microsoft.SqlServer.ConnectionInfo.dll
using System;
using System.Data;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
namespace SMObackup
{
class Program
{
static void Main()
{
// For remote connection, remote server name / ServerInstance needs to be specified
ServerConnection srvConn2 = new ServerConnection("machinename"/* <--default sql instance on machinename*/); // or (@"machinename\sqlinstance") for named instances
srvConn2.LoginSecure = false;
srvConn2.Login = "smologin";
srvConn2.Password = "SmoL@gin11";
srvConn2.DatabaseName = "msdb";
Server srv3 = new Server(srvConn2);
//server info
Console.WriteLine("servername:{0} ---- version:{1}", srv3.Name, srv3.Information.Version);
//server root directory
string serverRootDir = srv3.Information.RootDirectory;
//server backup directory
string serverBackupDir = srv3.Settings.BackupDirectory;
//database primary directory
string databasePrimaryFilepath = srv3.Databases[srvConn2.DatabaseName].PrimaryFilePath;
Console.WriteLine("server_root_dir:{0}\nserver_backup_dir:{1}\ndatabase_primary_dir{2}", serverRootDir, serverBackupDir, databasePrimaryFilepath);
Backup bkpDbFull = new Backup();
bkpDbFull.Action = BackupActionType.Database;
//comment out copyonly ....
bkpDbFull.CopyOnly = true; //copy only, just for testing....avoid messing up with existing backup processes
bkpDbFull.Database = srvConn2.DatabaseName;
//backup file name
string backupfile = $"\\backuptest_{DateTime.Now.ToString("dd/MM/yyyy/hh/mm/ss")}.bak";
//add multiple files, in each location
bkpDbFull.Devices.AddDevice(serverRootDir + backupfile, DeviceType.File);
bkpDbFull.Devices.AddDevice(serverBackupDir + backupfile, DeviceType.File);
bkpDbFull.Devices.AddDevice(databasePrimaryFilepath + backupfile, DeviceType.File);
bkpDbFull.Initialize = true;
foreach (BackupDeviceItem backupdevice in bkpDbFull.Devices)
{
Console.WriteLine("deviceitem:{0}", backupdevice.Name);
}
//backup is split/divided amongst the 3 devices
bkpDbFull.SqlBackup(srv3);
Restore restore = new Restore();
restore.Devices.AddRange(bkpDbFull.Devices);
DataTable backupHeader = restore.ReadBackupHeader(srv3);
//IsCopyOnly=True
for (int r = 0; r < backupHeader.Rows.Count; r++)
{
for (int c = 0; c < backupHeader.Columns.Count; c++)
{
Console.Write("{0}={1}\n", backupHeader.Columns[c].ColumnName, (string.IsNullOrEmpty(backupHeader.Rows[r].ItemArray[c].ToString())? "**": backupHeader.Rows[r].ItemArray[c].ToString()) );
}
}
srvConn2.Disconnect(); //redundant
}
}
}
回答2:
Thank you @SeanLange
Pretty sure you would need to use dynamic sql in this case. Then the path will relative to where the sql is executed.
I changed my code to add:
private static void WriteBackupSp (Server remoteServer, string dbName, out string storedProcedure)
{
var s = "CREATE PROCEDURE [dbo].[ProactiveDBBackup]\n";
s += "AS\n";
s += "BEGIN\n";
s += "SET NOCOUNT ON\n";
s += "BACKUP DATABASE " + dbName + " TO DISK = \'" + string.Concat (remoteServer.BackupDirectory,@"\", dbName, ".bak") + "\'\n";
s += "END\n";
storedProcedure = s;
}
then changed the last switch to be:
switch (oldProactiveSql.InstanceName)
{
case null:
try
{
WriteBackupSp (srv3, oldProactiveSql.DbName, out var storedProcedure);
ConnectionToolsUtility.GenerateSqlConnectionString (oldProactiveSql, out var cs);
using (SqlConnection connection = new SqlConnection (cs))
{
using (SqlCommand command = new SqlCommand (storedProcedure, connection))
{
connection.Open ();
command.ExecuteNonQuery ();
connection.Close ();
}
}
var execBackup = "EXEC [dbo].[ProactiveDBBackup]\n";
using (SqlConnection connection = new SqlConnection (cs))
{
using (SqlCommand command = new SqlCommand (execBackup, connection))
{
connection.Open ();
command.ExecuteNonQuery ();
connection.Close ();
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
Console.WriteLine(e.InnerException.Message);
throw;
}
break;
default:
try { bkpDbFull.SqlBackup(srv2); }
catch (Exception e)
{
Console.WriteLine(e);
Console.WriteLine(e.InnerException.Message);
throw;
}
break;
}
And that allowed me to take a backup of the database through the connection string to the default backup location without having credentials with access to the network path location.
来源:https://stackoverflow.com/questions/60062201/c-sharp-sql-server-backup-of-a-remote-database-to-the-remote-default-backup-loca