【问题标题】:C# - Restore SQL Server database with Inline SQLC# - 使用内联 SQL 恢复 SQL Server 数据库
【发布时间】:2016-08-15 17:18:16
【问题描述】:

我正在构建一个 C# 控制台应用程序,该应用程序使用可在切换菜单中修改的预定义参数恢复 SQL Server 数据库。此应用程序的重点是简单性。它是为任何人设计的,只要按一下按钮就可以恢复 SQL Server 数据库。是的,应该有人可以胜任这项任务,但是应该有很多方法可以完成......无论如何我已经建立了循环遍历备份目录并根据点选择正确的备份文件的逻辑及时。除了实际的恢复部分外,一切正常。

从我读过的其他问题来看,我担心不可能从以这种方式修改服务器的 C# 运行 SQL。我不愿意使用 SMO 对象,因为这涉及到我想避免的额外复杂性,但如果这是我能做到这一点的唯一方法,那么我将这样做。

每当我尝试运行此代码时,它都会抱怨 @dbName 值无效。

public static void RestoreDatabase(string Server_Name, string Instance_Name, string DB_Name, FileInfo BakFile, FileInfo DiffFile, FileInfo TrnFile, DateTime Point_In_Time)
{
    SqlConnection conn = new SqlConnection();
    conn.ConnectionString = "Data Source=" + Server_Name + "\\" + Instance_Name + ";Initial Catalog=master;Integrated Security=True";

    string SqlQuery = @"ALTER DATABASE @dbName
                        SET SINGLE_USER
                        WITH ROLLBACK IMMEDIATE;

                        RESTORE DATABASE @dbName
                        FROM DISK = @BakFilePath

                        WITH NORECOVERY, REPLACE;

                        RESTORE DATABASE @dbName
                        FROM DISK = @DiffFilePath
                        WITH NORECOVERY;

                        RESTORE DATABASE @dbName
                        FROM DISK = @TrnFilePath
                        WITH RECOVERY, STOPAT = @RecoveryTime;

                        ALTER DATABASE @dbName
                        SET MULTI_USER;";

    SqlCommand cmd = new SqlCommand();
    cmd.CommandType = System.Data.CommandType.Text;
    cmd.Connection = conn;
    cmd.CommandText = SqlQuery;

    try
    {
        cmd.Parameters.Add(new SqlParameter("@dbName", SqlDbType.NVarChar, 30));
        cmd.Parameters.Add(new SqlParameter("@BakFilePath", SqlDbType.NVarChar, 255));
        cmd.Parameters.Add(new SqlParameter("@DiffFilePath", SqlDbType.NVarChar, 255));
        cmd.Parameters.Add(new SqlParameter("@TrnFilePath", SqlDbType.NVarChar, 255));
        cmd.Parameters.Add(new SqlParameter("@RecoveryTime", SqlDbType.DateTime));
        cmd.Parameters["@dbName"].Value = DB_Name;
        cmd.Parameters["@BakFilePath"].Value = BakFile.FullName;
        cmd.Parameters["@DiffFilePath"].Value = DiffFile.FullName;
        cmd.Parameters["@TrnFilePath"].Value = TrnFile.FullName;
        cmd.Parameters["@RecoveryTime"].Value = Point_In_Time;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
        Console.ReadLine();
    }

    try
    {
        Console.WriteLine("Restoring {0}...", DB_Name);

        cmd.Connection.Open();
        cmd.ExecuteNonQuery();
        cmd.Connection.Close();

        Console.WriteLine("Restore Complete!");
        Console.ReadLine();
    }
    catch (SqlException ex)
    {
        Console.WriteLine("Connection could not open. Error: {0}", ex);
        Console.ReadLine();
    }     
}

2016 年 8 月 15 日 10:38

根据一些答案,你们推荐使用动态 SQL。我担心 SQL 注入,但只有管理员才能访问该程序。无论如何,我仍然会添加更严格的验证,因为谁不喜欢将垃圾输入放入其中。

我尝试过动态 SQL。这是我的代码,但是我没有花太多时间检查错误,所以我会再次走这条路并尝试清理任何错误。

string SqlQuery = string.Format(@"DECLARE @dbName NVARCHAR(MAX), @strSQL NVarchar(MAX)=N'';
                            SET @dbName = {0};

                            SELECT
                            @strSQL += 'DECLARE @BakFilePath nvarchar(255) = N''{1}'','
                             + N' @DiffFilePath nvarchar(255) = N''{2}'','
                             + N' @TrnFilePath nvarchar(255) = N''{3}'','
                             + N' @RecoveryTime DateTime = N''{4}'''
                             + N' ALTER DATABASE '+ @dbName
                             + N' SET SINGLE_USER'
                             + N' WITH ROLLBACK IMMEDIATE;'
                             + N' RESTORE DATABASE ' + @dbName 
                             + N' FROM DISK = @BakFilePath'
                             + N' WITH NORECOVERY, REPLACE;'
                             + N' RESTORE DATABASE ' + @dbName 
                             + N' FROM DISK = @DiffFilePath'
                             + N' WITH NORECOVERY;'  
                             + N' RESTORE DATABASE ' + @dbName 
                             + N' FROM DISK = @TrnFilePath'
                             + N' WITH RECOVERY, STOPAT = @RecoveryTime;'
                             + N' ALTER DATABASE ' + @dbName 
                             + N' SET MULTI_USER;
                             '
                             EXEC sp_executesql @strSQL",DB_Name, BakFile.FullName, DiffFile.FullName, TrnFile.FullName, Point_In_Time);

【问题讨论】:

  • 你能显示它的整个输出“抱怨”关于 dbName 值吗?
  • 目前正试图弄清楚如何将我的程序输出到屏幕和文件...在 Linux 上很容易做到哈哈。
  • c# 控制台程序? console.writeline(),如果你导入 system.windows.forms,你可以使用 messagebox.show(),system.file.io 你可以使用 File.WriteAllText(filepath)。您还可以在命令行运行程序时使用 c:\program.exe > results.txt 等语法将其保存到文件中。

标签: c# sql sql-server restore


【解决方案1】:

很遗憾,您的 SQL 的编写方式存在问题。我提供了一个具有类似潜在问题的问题的答案,您可以在此处找到它:

DROP PROCEDURE throws syntax error

sql-server 会报错:

Restore Database @dbName

因为它是作为文字字符串传递的。你需要写这样的东西来执行你的代码。

declare @sql varchar(64);

set @sql = 'RESTORE DATABASE ' + @dbName;

exec(@sql);

更新
根据 Scott 的评论,使用:

set @sql = 'RESTORE DATABASE ' + QUOTENAME(@dbName);

将转义字符串以创建有效的 SQL Server 分隔标识符:

https://msdn.microsoft.com/en-us/library/ms176114.aspx

【讨论】:

  • 注意,你可能真的想做set @sql = 'RESTORE DATABASE ' + QUOTENAME(@dbName);QUOTENAME函数将确保数据库名称周围有正确的[ ],这样如果你得到一个名称中有空格的数据库它会正确恢复。
  • 谢谢,我之前没有听说过/使用过这个功能
【解决方案2】:

@dbName 无效。

在传递参数时,您必须遵守允许在 sql 语法中使用变量的规则。使用 SELECT 语句时,您可以在列定义或 where 子句中使用变量,但它不能用于引用特定的数据库对象。同样适用于 ALTER DATABASE、ATLER TABLE、CREATE TABLE 等.....

所以基本上参数不能代替对象名称,除非您随后使用动态 sql,例如按照其他答案的建议从参数构建 sql 字符串。这种方式的动态 SQL 很容易受到 SQL 注入攻击,因此您应该确保您的来源是可信的。

最后你问是否可能,答案是肯定的,只要你的用户有权限并且你的 sql 语句正确,它就可以从 c# 运行备份/更改数据库命令等。

【讨论】:

    【解决方案3】:

    您不能在 SQL 中对所有内容进行参数化。

    SELECT * FROM myTable WHERE customerID = @custID
    

    有效。

    SELECT * FROM @tablename 
    

    不是。使用带参数的 ALTER DATABASE 或 RESTORE DATABASE 也不行。

    这是设计使然。解决方法是在将字符串发送到 SQL 之前对其进行替换,但是当然您必须清理传入的参数以防出现问题。

    【讨论】:

    • 你不必擦洗,SQL提供了一个函数来转义名称,所以它用QUOTENAME表示一个正确的对象,所以如果你想要一个名为This Is [a Weird] name; drop database foo;的数据库,它将是转至[This Is [a Weird]] name; drop database foo;] 其中is a valid database name
    【解决方案4】:

    谢谢大家的回答,他们指引我选择动态SQL的方向。下面的代码是对我有用的 SQL 解决方案。我会解释发生了什么,因为圣牛会变得复杂(至少对我来说)。 我的 C# 应用程序调用了一个函数,我传递了五个参数:

    1. 数据库名称
    2. 完整备份文件路径
    3. 差异备份文件路径
    4. 事务备份文件路径
    5. 要恢复到的时间点

    这些值随后用于 SQL 字符串的变量定义中。一旦在 SQL 字符串中设置了变量,它就会变成常规的动态 SQL。动态 SQL 仍然是一个巨大的痛苦;弄清楚所有的单引号让我很伤心。一旦动态 SQL 的格式正确,它就像使用 C# 执行的任何其他 SQL 语句一样。

    更新:根据 Scott Chamberlains 的要求修改解决方案以包含输入值的参数,并将整个函数作为解决方案包含在内。

    public static void RestoreDatabase(string Server_Name, string Instance_Name, string DB_Name,
                                       FileInfo BakFile, FileInfo DiffFile, FileInfo TrnFile, DateTime Point_In_Time)
    {
        SqlConnection conn = new SqlConnection();
        conn.ConnectionString = "Data Source=" + Server_Name + "\\" + Instance_Name + ";Initial Catalog=master;Integrated Security=True";
    
        string SqlQuery = string.Format(@"DECLARE @strSQL NVARCHAR(MAX) =''
                                        SELECT
                                        @strSQL +=  N'ALTER DATABASE ' + QUOTENAME(@dbName)
                                         + N' SET SINGLE_USER'
                                         + N' WITH ROLLBACK IMMEDIATE;'
                                         + N' RESTORE DATABASE ' + QUOTENAME(@dbName) 
                                         + N' FROM DISK = N''' + @BakFilePath + ''''
                                         + N' WITH NORECOVERY, REPLACE;'
                                         + N' RESTORE DATABASE ' + QUOTENAME(@dbName)  + 
                                         + N' FROM DISK = N''' + @DiffFilePath + ''''
                                         + N' WITH NORECOVERY;'  
                                         + N' RESTORE DATABASE ' + QUOTENAME(@dbName) 
                                         + N' FROM DISK = N''' + @TrnFilePath + ''''
                                         + N' WITH NORECOVERY, STOPAT =  N''' + @RecoveryTime + ''';'
                                         + N' RESTORE DATABASE ' + QUOTENAME(@dbName)
                                         + N' WITH RECOVERY;'
                                         + N' ALTER DATABASE ' + QUOTENAME(@dbName)
                                         + N' SET MULTI_USER;
                                        ' 
                                         EXEC sp_executesql @strSQL");
    
        SqlCommand cmd = new SqlCommand();
        cmd.CommandType = System.Data.CommandType.Text;
        cmd.Connection = conn;
        cmd.CommandText = SqlQuery;
        try
        {
            cmd.Parameters.Add(new SqlParameter("@dbName", SqlDbType.NVarChar, 30));
            cmd.Parameters.Add(new SqlParameter("@BakFilePath", SqlDbType.NVarChar, 255));
            cmd.Parameters.Add(new SqlParameter("@DiffFilePath", SqlDbType.NVarChar, 255));
            cmd.Parameters.Add(new SqlParameter("@TrnFilePath", SqlDbType.NVarChar, 255));
            cmd.Parameters.Add(new SqlParameter("@RecoveryTime", SqlDbType.NVarChar,30));
            cmd.Parameters["@dbName"].Value = DB_Name;
            cmd.Parameters["@BakFilePath"].Value = BakFile.FullName;
            cmd.Parameters["@DiffFilePath"].Value = DiffFile.FullName;
            cmd.Parameters["@TrnFilePath"].Value = TrnFile.FullName;
            cmd.Parameters["@RecoveryTime"].Value = Point_In_Time.ToString();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            Console.WriteLine("Press any key to exit");
            Console.ReadLine();
        }
        try
        {
            Console.WriteLine("Restoring {0}...", DB_Name);
            cmd.Connection.Open();
            cmd.ExecuteNonQuery();
            cmd.Connection.Close();
            Console.WriteLine("Restore Complete!");
            Console.WriteLine("Press any key to exit");
            Console.ReadLine();
        }
        catch (SqlException ex)
        {
            Console.WriteLine("Connection could not open. Error: {0}", ex);
            Console.ReadLine();
        }
    
    }
    

    【讨论】:

    • 您的声明非常不安全,请摆脱它们并像您在原始问题中所做的那样重新使用SqlParameters。但是然后将它们与您现在拥有的代码结合使用(SELECT 之后的一切都很好)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多