C# - 使用内联 SQL 恢复 SQL 服务器数据库
C# - Restore SQL Server database with Inline SQL
我正在构建一个 C# 控制台应用程序,它使用可以在开关菜单中修改的预定义参数来恢复 SQL 服务器数据库。此应用程序的重点是简单性。它专为任何人设计,只需按一下按钮即可恢复 SQL 服务器数据库。是的,应该有人可以胜任这项任务,但是有很多方法可以完成……不管我已经建立了循环遍历备份目录的逻辑,并根据一个点挑选出正确的备份文件及时。除实际恢复部分外,一切正常。
从我读过的其他问题来看,我担心无法从以这种方式修改服务器的 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);
很遗憾,您的 SQL 的书写方式存在问题。我提供了一个具有类似潜在问题的问题的答案,您可以在这里找到它:
sql-服务器会抱怨使用:
Restore Database @dbName
因为它是作为文字字符串传递的。你需要写这样的东西来执行你的代码。
declare @sql varchar(64);
set @sql = 'RESTORE DATABASE ' + @dbName;
exec(@sql);
更新
根据斯科特的评论,使用:
set @sql = 'RESTORE DATABASE ' + QUOTENAME(@dbName);
将转义字符串以创建有效的 SQL 服务器分隔标识符:
@dbName 无效。
传递参数时,您必须遵守 sql 语法中允许使用变量的规则。使用 SELECT 语句时,您可以在列定义或 where 子句中使用变量,但它不能用于引用特定的数据库对象。 ALTER DATABASE、ATLER TABLE、CREATE TABLE 等也是如此.....
所以基本上参数不能代替对象名称,除非您随后使用动态 sql,例如从参数构建 sql 字符串,如其他答案所建议的那样。这种方式的动态 SQL 很容易受到 SQL 注入攻击,因此您应该确保您的来源是可信的。
最后你问是否可能,答案是肯定的,只要你的用户有权限并且你的 sql语句正确就会执行。
您不能参数化 SQL 中的所有内容。
SELECT * FROM myTable WHERE customerID = @custID
有效。
SELECT * FROM @tablename
不是。也不使用带参数的 ALTER DATABASE 或 RESTORE DATABASE。
这是设计使然。解决方法是在将字符串发送到 SQL 之前在字符串中进行替换,但是当然你必须清除传入的参数以防止出现问题。
谢谢大家的回答,指引我选择动态的方向SQL。下面的代码是对我有用的 SQL 解决方案。我会解释发生了什么,因为天哪它变得复杂了(至少对我而言)。
我的 C# 应用程序调用一个函数并传递五个参数:
- 数据库名称
- 完整备份文件路径
- 差异备份文件路径
- 交易备份文件路径
- 恢复到
的时间点
这些值然后用于 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();
}
}
我正在构建一个 C# 控制台应用程序,它使用可以在开关菜单中修改的预定义参数来恢复 SQL 服务器数据库。此应用程序的重点是简单性。它专为任何人设计,只需按一下按钮即可恢复 SQL 服务器数据库。是的,应该有人可以胜任这项任务,但是有很多方法可以完成……不管我已经建立了循环遍历备份目录的逻辑,并根据一个点挑选出正确的备份文件及时。除实际恢复部分外,一切正常。
从我读过的其他问题来看,我担心无法从以这种方式修改服务器的 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);
很遗憾,您的 SQL 的书写方式存在问题。我提供了一个具有类似潜在问题的问题的答案,您可以在这里找到它:
sql-服务器会抱怨使用:
Restore Database @dbName
因为它是作为文字字符串传递的。你需要写这样的东西来执行你的代码。
declare @sql varchar(64);
set @sql = 'RESTORE DATABASE ' + @dbName;
exec(@sql);
更新
根据斯科特的评论,使用:
set @sql = 'RESTORE DATABASE ' + QUOTENAME(@dbName);
将转义字符串以创建有效的 SQL 服务器分隔标识符:
@dbName 无效。
传递参数时,您必须遵守 sql 语法中允许使用变量的规则。使用 SELECT 语句时,您可以在列定义或 where 子句中使用变量,但它不能用于引用特定的数据库对象。 ALTER DATABASE、ATLER TABLE、CREATE TABLE 等也是如此.....
所以基本上参数不能代替对象名称,除非您随后使用动态 sql,例如从参数构建 sql 字符串,如其他答案所建议的那样。这种方式的动态 SQL 很容易受到 SQL 注入攻击,因此您应该确保您的来源是可信的。
最后你问是否可能,答案是肯定的,只要你的用户有权限并且你的 sql语句正确就会执行。
您不能参数化 SQL 中的所有内容。
SELECT * FROM myTable WHERE customerID = @custID
有效。
SELECT * FROM @tablename
不是。也不使用带参数的 ALTER DATABASE 或 RESTORE DATABASE。
这是设计使然。解决方法是在将字符串发送到 SQL 之前在字符串中进行替换,但是当然你必须清除传入的参数以防止出现问题。
谢谢大家的回答,指引我选择动态的方向SQL。下面的代码是对我有用的 SQL 解决方案。我会解释发生了什么,因为天哪它变得复杂了(至少对我而言)。 我的 C# 应用程序调用一个函数并传递五个参数:
- 数据库名称
- 完整备份文件路径
- 差异备份文件路径
- 交易备份文件路径
- 恢复到 的时间点
这些值然后用于 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();
}
}