简体   繁体   English

C# SQL Server 将远程数据库备份到远程默认备份位置,无需直接访问远程位置?

[英]C# SQL Server backup of a remote database to the remote default backup location without direct access to the remote location?

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. TL;DR - 我希望服务器备份而不是我的应用程序,因为服务器已设置为这样做并且我的应用程序将无法访问。

Background背景

My company created software for clients 20 years ago written in Delphi 7/Pascal.我的公司在 20 年前为客户创建了用 Delphi 7/Pascal 编写的软件。 I am re-writing the software in C#.我正在用 C# 重写软件。 As part of the re-write I have created new Firebird, Oracle, and SQL Server databases.作为重写的一部分,我创建了新的 Firebird、Oracle 和 SQL Server 数据库。 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.该工具访问本地系统上的类似.ini的加密文件,以解析连接字符串的组成部分并创建连接对象。 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如果我单独保留默认备份路径,它会尝试备份到默认路径,但在本地机器上(技术人员无权创建,我们不希望技术人员访问 .bak)如果我修改默认备份路径是从连接字符串中获取的网络路径,我得到

SmoException: System.Data.SqlClient.SqlError: Cannot open backup device Operating system error 67(The network name cannot be found.). SmoException: System.Data.SqlClient.SqlError: 无法打开备份设备 操作系统错误 67(找不到网络名称。)。

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.因为文件路径不是网络共享(也不会),并且数据库用户凭据无法从 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.从下面的评论中,我将尝试 - 1. 在数据库上动态创建存储过程 [BackupToDefault] 然后运行它。 2. If that fails link the database to itself. 2. 如果失败,将数据库链接到自身。 3. Try - Exec [BackupToDefault] At [LinkedSelfSynonmym] 3. 尝试 - 在 [LinkedSelfSynonmym] 处执行 [BackupToDefault]

Wish me luck though it seems convoluted and the long way around I hope it works.祝我好运,虽然它看起来很复杂,但我希望它能奏效。

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)寻找灵感...备份分为 3 个文件,每个文件驻留在不同的目录中(sql 实例备份目录和 sql 实例默认目录和数据库主目录)

//// 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

        }
    }
}

Thank you @SeanLange谢谢@SeanLange

Pretty sure you would need to use dynamic sql in this case.很确定在这种情况下您需要使用动态 sql。 Then the path will relative to where the sql is executed.然后路径将相对于执行 sql 的位置。

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.这使我可以通过连接字符串将数据库备份到默认备份位置,而无需访问网络路径位置的凭据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM