C# 如何使用非常规参数调用存储过程?

C# 如何使用非常规参数调用存储过程?,c#,sql-server,stored-procedures,dapper,C#,Sql Server,Stored Procedures,Dapper,我正试图将RedGate的SQLBackup Pro软件集成到我的内部备份软件中,该软件是用C#编写的。这样做的自然方式是通过他们的。问题是它的调用格式我以前从未见过: master..sqlbackup '-SQL "BACKUP DATABASE pubs TO DISK = [C:\Backups\pubs.sqb]"' 当通过SSMS运行时,这一切正常。我遇到的麻烦是试图从C#调用它(使用.NET4和) 我的第一次尝试无效,因为它将整个cmd字符串解释为存储过程的名称,并抛出错误“找不

我正试图将RedGate的SQLBackup Pro软件集成到我的内部备份软件中,该软件是用C#编写的。这样做的自然方式是通过他们的。问题是它的调用格式我以前从未见过:

master..sqlbackup '-SQL "BACKUP DATABASE pubs TO DISK = [C:\Backups\pubs.sqb]"'
当通过SSMS运行时,这一切正常。我遇到的麻烦是试图从C#调用它(使用.NET4和)

我的第一次尝试无效,因为它将整个
cmd
字符串解释为存储过程的名称,并抛出错误“找不到存储过程“”:

我的第二次尝试立即返回并(对C#)显示成功,但实际上没有进行备份(这对于参数化也很糟糕):

我的第三次尝试似乎也成功了,但实际上没有进行备份:

var cmd = "master..sqlbackup '-SQL \"BACKUP DATABASE pubs TO DISK = [C:\\Backups\\pubs.sqb]\"'";
connection.Execute(cmd, commandTimeout: 0);
我错过了什么

更新1:

我忽略了RedGate文档,它说存储的过程实际上不会引发SQL错误,它只是在输出表中返回错误。滑溜的这也许可以解释为什么我在上面的第二次和第三次测试中遇到了无声的失败:一些潜在的问题,他们没有收集输出来说明原因

这就是我现在的处境:

var cmd = "master..sqlbackup";
var p = new DynamicParameters();
p.Add("", "'-SQL \"BACKUP DATABASE pubs TO DISK = [C:\\Backups\\pubs.sqb]\"'");
p.Add("@exitcode", DbType.Int32, direction: ParameterDirection.Output);
p.Add("@sqlerrorcode", DbType.Int32, direction: ParameterDirection.Output);
connection.Execute(cmd, p, commandType: CommandType.StoredProcedure, commandTimeout: 0);
当我运行此程序并检查这些输出参数时,我得到退出代码870:

没有向SQL备份传递命令

命令为空

所以它没有看到名为paramater的空文件

更新2:


在跟踪中捕获上述内容表明空参数字符串最终被替换为
@Parameter1=
,这解释了为什么存储过程没有看到它。

您的第一次尝试看起来几乎正确。我注意到的是你没能逃脱反斜杠。对于这种情况,使用@prefix来禁用字符串的转义通常更容易。此外,您还希望使用exec作为前缀,并将其设置为
CommandType.Text

编辑:修复了我自己的转义错误

var cmd = @"exec 'master..sqlbackup -SQL ""BACKUP DATABASE pubs TO DISK = [C:\Backups\pubs.sqb]""'";
connection.Execute(cmd, commandType: CommandType.Text, commandTimeout: 0);

您是否尝试过创建一个调用扩展存储过程的典型存储过程,并从代码中调用它?看起来这里只有几个参数需要处理。

这很糟糕,根本不是我想要的,但这就是我现在要做的:

var cmd = String.Format(@"
DECLARE @exitcode int; 
DECLARE @sqlerrorcode int;
EXEC master..sqlbackup '-SQL \"BACKUP DATABASE [{0}] TO DISK = [{1}])\"', @exitcode OUTPUT, @sqlerrorcode OUTPUT;
IF (@exitcode >= 500) OR (@sqlerrorcode <> 0)
BEGIN
RAISERROR('SQLBackup failed with exitcode %d and sqlerrorcode %d ', 16, 1, @exitcode, @sqlerrorcode)
END
", myDbName, myBkpPath);

connection.Execute(cmd, commandTimeout: 0);
var cmd=String.Format(@)
声明@exitcode int;
声明@sqlerrorcode int;
EXEC master..sqlbackup'-SQL\“将数据库[{0}]备份到磁盘=[{1}])\”,@exitcode输出,@sqlerrorcode输出;
如果(@exitcode>=500)或(@sqlerrorcode 0)
开始
RAISERROR('SQLBackup失败,exitcode为%d,sqlerrorcode为%d',16,1,@exitcode,@sqlerrorcode)
结束
“,myDbName,myBkpPath);
执行(cmd,commandTimeout:0);
这将执行备份并实际返回故障状态,这暴露了导致静默故障的故障部分的根本问题


在运行myDbName之前,将对照已知数据库列表检查它,以确保它存在,并且myBkpPath是由我的代码生成的,所以我不担心注入。只是…嗯,看看这个。太可怕了。

你在测试中遇到了几个问题

在第一个示例中,您设置了
命令类型.storedProcess
。您应该将其设置为
CommandType.Text
,这样它就足够聪明,只需将该字符串传递给执行者即可


在随后的示例中,您实际上没有给参数命名。去看看他们的SqlBackup过程,看看参数名是什么。然后使用它。否则,将不会为其分配任何内容。

抱歉,反斜杠实际上已转义,但在问题中没有转义。我选择转义它们而不是使用
@“
语法,因为引号已经是最容易混淆的部分,并且路径(一旦找到答案)将通过参数或(上帝禁止)字符串格式插入。您是否尝试添加我提到的exec?是的。与#2和#3相同的结果:立即返回,不做任何工作,但似乎没有失败。@sh beta:更重要的是,您是否将CommandType.StoredProcess更改为CommandType.Text?这是主要问题。@chrisly是的。请看第三次尝试。我相信这会起作用,但这不是一个非常可扩展的解决方案,因为所讨论的存储过程必须在我的每台SQL服务器上创建和管理。扩展存储过程不是在每台服务器上都有吗?您所说的“托管”是什么意思?扩展存储过程是软件包的一部分,该软件包具有网络感知安装程序,可让您轻松地将其推送到网络并通过网络进行升级。我并不是说这对于存储过程来说是不可能的,而是说我宁愿让我的代码使用已经存在的东西。如果要对sqlbackup执行sp_帮助,我确信param有一个名称
sp_help sqlbackup
只包括名称、所有者、类型和创建的日期时间列。即使类型是“extended stored proc”,也没有参数顺序(或其他名称)列……我打开了一个带有Red Gate的案例,以了解该参数是否有名称。我们拭目以待。根据红门的说法,这个参数没有名字。它只使用第一个位置的任何东西。这似乎很愚蠢……我的问题中解释了这两个问题。第一个:“我的第一次尝试不起作用,因为它将整个cmd字符串解释为存储过程的名称,并抛出找不到存储过程“”的错误。”。我的第三次尝试就是这样,但是备份失败了。关于第二个问题,请参见我对我的问题的评论。参数没有记录的名称。@sh beta:您可以检查过程以确定参数名称。不需要文件。即使是加密的进程也会告诉您参数的名称。在ManagementStudio中,您可以展开存储的过程定义并查看参数列表。或者您可以右键单击proc并编写脚本,这样您就可以获得参数的名称。这一个没有提供任何有用的信息。整个剧本
var cmd = @"exec 'master..sqlbackup -SQL ""BACKUP DATABASE pubs TO DISK = [C:\Backups\pubs.sqb]""'";
connection.Execute(cmd, commandType: CommandType.Text, commandTimeout: 0);
var cmd = String.Format(@"
DECLARE @exitcode int; 
DECLARE @sqlerrorcode int;
EXEC master..sqlbackup '-SQL \"BACKUP DATABASE [{0}] TO DISK = [{1}])\"', @exitcode OUTPUT, @sqlerrorcode OUTPUT;
IF (@exitcode >= 500) OR (@sqlerrorcode <> 0)
BEGIN
RAISERROR('SQLBackup failed with exitcode %d and sqlerrorcode %d ', 16, 1, @exitcode, @sqlerrorcode)
END
", myDbName, myBkpPath);

connection.Execute(cmd, commandTimeout: 0);