2016-08-15 55 views
2

我正在构建一个C#控制台应用程序,该应用程序使用预定义参数恢复SQL Server数据库,该参数可以在交换机菜单中进行修改。这个应用程序的重点是简单。它是为任何人设计的,只要按下一个按钮即可恢复SQL Server数据库。是的,应该有人可以胜任这项工作,但是有很多方法可以完成工作......无论如何,我已经建立了逻辑来循环备份目录,并根据点选择正确的备份文件及时。除了实际恢复部分之外,一切都可以工C# - 使用内联SQL恢复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); 
+1

你可以显示它的“抱怨”关于dbName值的整个输出吗? –

+0

目前试图找出如何将我的程序的输出发送到屏幕和文件...在Linux上很容易做到哈哈。 –

+0

c#console程序? console.writeline(),如果你导入system.windows.forms,你可以使用messagebox.show(),system.file.io,你可以使用File.WriteAllText(filepath)。您还可以在命令行运行程序时使用语法(如c:\ program.exe> results.txt)将其保存到文件中。 – Matt

回答

0

谢谢大家的回答,他们引导我选择动态SQL。下面的代码是为我工作的SQL解决方案。我会解释发生了什么,因为圣牛会变得复杂(至少对我而言)。 我的C#应用​​程序调用的函数,我通过五个参数:

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

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

更新:修改后的解决方案包含Scott Chamberlains请求中输入值的参数,并将整个函数作为解决方案。

公共静态无效RestoreDatabase(串SERVER_NAME,串INSTANCE_NAME,串DB_NAME, 的FileInfo BakFile,FileInfo的DiffFile,FileInfo的TrnFile,日期时间POINT_IN_TIME) { 的SqlConnection康恩=新的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(); 
    } 

} 
+1

你的声明是超级不安全的,请删除它们并返回使用'SqlParameter'就像你在原来的问题中所做的那样。但是随后将它们与您现在使用的代码结合使用(“SELECT”后的所有内容都可以) –

4

不幸的是,您的SQL编写方式存在问题。我提供一个答案,那也有类似的根本问题一个问题,你可以在这里找到:

DROP PROCEDURE throws syntax error

SQL服务器会抱怨的用法:

Restore Database @dbName 

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

declare @sql varchar(64); 

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

exec(@sql); 

更新
按照斯科特的评论,使用:

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

会逃跑的字符串,创建有效的SQL Server分隔标识符:

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

+2

请注意,您实际上可能需要执行set @sql ='RESTORE DATABASE'+ QUOTENAME(@dbName);'[QUOTENAME'](https://msdn.microsoft.com/zh-cn/library/ms176114 。aspx)函数将确保数据库名称周围有适当的'[]'',这样如果你的名字中有一个空格的数据库会正确恢复。 –

+0

谢谢,我没有听说过/在 –

1

@DbName是无效的。

当传递一个参数时,你必须遵守允许在sql语法中使用变量的规则。使用SELECT语句时,可以在列定义或where子句中使用变量,但不能用于引用特定的数据库对象。对于ALTER DATABASE,ATLER TABLE,CREATE TABLE等也是如此.....

因此,基本上参数不能取代对象名,除非您使用动态sql,例如从参数构建sql字符串正如另一个答案所示。这种动态SQL对SQL注入攻击非常开放,所以你应该确保你的源代码是可信的。

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

1

您无法在SQL中参数化所有内容。

SELECT * FROM myTable WHERE customerID = @custID 

有效。

SELECT * FROM @tablename 

不是。 ALTER DATABASE或RESTORE DATABASE都不带参数。

这是设计。解决方法是在将字符串发送给SQL之前先对字符串进行替换,但当然,您必须清理传入的参数中的不良内容。

+1

之前使用过那个函数。你不需要清理,SQL提供了一个函数来转义这个名字,所以它用[QUOTENAME']代替了一个合适的对象(https://msdn.microsoft .com/en-us/library/ms176114.aspx),所以如果你想有一个名为'This Is [a Weird] name的数据库; drop database foo;'它会被转换成'[This Is [a Weird]]的名字; drop database foo;]'这是[有效的数据库名称](http://i.stack.imgur.com/opZYS.png) –

相关问题