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

发布于 2025-01-03 18:02:40 字数 2127 浏览 1 评论 0原文

我正在尝试将 Red Gate 的 SQLBackup Pro 软件集成到我用 C# 编写的内部备份软件中。执行此操作的自然方法是通过他们的扩展存储过程。问题是它以我以前从未见过的格式调用:

master..sqlbackup '-SQL "BACKUP DATABASE pubs TO DISK = [C:\Backups\pubs.sqb]"'

通过 SSMS 运行时效果很好。我遇到麻烦的地方是尝试从 C# 调用它(使用 .NET 4 和 Dapper点网)。

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

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

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

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

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

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

我错过了什么?

更新1:

我忽略了Red Gate文档,该文档说存储过程实际上不会引发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 备份。

该命令为空。

所以它没有看到空命名的参数。

更新 2:

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

I'm attempting to integrate Red Gate's SQLBackup Pro software into my in-house backup software, written in C#. The natural way to do this is via their Extended Stored Procedure. The problems is that it's called in a format I've never seen before:

master..sqlbackup '-SQL "BACKUP DATABASE pubs TO DISK = [C:\Backups\pubs.sqb]"'

This works just fine when run via SSMS. Where I run into trouble is trying to call it from C# (using .NET 4 and Dapper Dot Net).

My first attempt doesn't work because it interprets the entire cmd string as the name of the stored procedure and throws the error "Cannot find stored procedure ''":

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

My second attempt returns immediately and appears (to C#) to succeed, but no backup is actually taken (this also sucks for parameterization):

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

My third attempt also appears to succeed, but no backup is actually taken:

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

What am I missing?

UPDATE 1:

I overlooked the Red Gate documentation that says the stored proc won't actually raise a SQL error, it just returns errors in an output table. Slick. This might explain why I was getting silent failures in the second and third tests above: some underlying problem and they're not collecting the output to show why.

Here's where I am now:

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);

When I run this and check those output parameters, I get Exit Code 870:

No command passed to SQL Backup.

The command is empty.

So it's not seeing the empty-named paramater.

Update 2:

Capturing the above in a trace shows that the empty parameter string ends up replaced with @Parameter1= which explains why the stored procedure doesn't see it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

雾里花 2025-01-10 18:02:40

你的第一次尝试看起来几乎是正确的。我注意到你未能逃脱反斜杠。对于这种事情,使用 @ 前缀来禁用字符串转义通常更容易。另外,您想在前面添加 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);

Your first attempt looks almost right. What I notice is that you failed to escape the backslashes. For this kind of thing, it's often easier to use the @ prefix to disable escaping for the string. Also, you want to prepend with exec and make it CommandType.Text:

EDIT: fixed my own escaping bugs here

var cmd = @"exec 'master..sqlbackup -SQL ""BACKUP DATABASE pubs TO DISK = [C:\Backups\pubs.sqb]""'";
connection.Execute(cmd, commandType: CommandType.Text, commandTimeout: 0);
囚我心虐我身 2025-01-10 18:02:40

这很恶心,根本不是我想要的,但这就是我现在正在工作的:

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);

这会执行备份并实际上返回失败状态,这暴露了导致静默失败的失败部分的潜在问题。

在运行之前,会根据已知数据库列表检查 myDbName 以确保其存在,并且 myBkpPath 是由我的代码生成的,因此我不担心注入。只是……好吧,看看那个。可怕。

It's gross and not at all what I wanted, but this is what I've got working now:

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);

This executes the backup and actually returns failure status, which exposed an underlying issue that caused the failure part of the silent failures.

Before it runs, myDbName is checked against a list of known databases to ensure it exists and myBkpPath is generated by my code, so I'm not worried about injections. It's just...well, look at that. Hideous.

芸娘子的小脾气 2025-01-10 18:02:40

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

Have you tried creating a typical stored procedure that calls the extended stored procedure, and calling that from code? It looks like you have only a few parameters to deal with here.

最终幸福 2025-01-10 18:02:40

您在测试中遇到了几个问题。

在第一个中,您设置CommandType.StoredProcedure。您应该将其设置为 CommandType.Text,以便它足够智能,只需传递该字符串即可执行。

在后续示例中,您实际上并未为参数指定名称。去看看他们的SqlBackup过程,看看参数名称是什么。然后使用它。否则,不会为其分配任何内容。

You had several problems in your tests.

In the first one you set the CommandType.StoredProcedure. You should have set it to CommandType.Text so that it would be smart enough to just pass that string along to be exec'd.

In the subsequent examples, you didn't actually give the parameter a name. Go look at their SqlBackup procedure and see what the parameter names are. Then use it. Otherwise, nothing is going to be assigned to it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文