首页 > 解决方案 > Invoke-SqlCmd 在包含另一个 Invoke-SqlCmd 的循环中使用时会生成异常

问题描述

Invoke-SqlCmd当它包含第二个呼叫时,我遇到Invoke-SqlCmd呼叫问题:

function Get-Foo
{

  $query=`
@"
WITH data AS 
(
  SELECT  1 ID, 'A' NAME
  UNION ALL
  SELECT  2 ID, 'B' NAME
  UNION ALL
  SELECT  3 ID, 'C' NAME
)
SELECT  *
FROM    data
"@

  Invoke-SqlCmd -ServerInstance "SERVER" -Database "DATABASE" -Query $query

}

function Get-Bar
{
  param
  (
    [int]$ParentId
  )

  $query=`
@"
WITH data AS 
(
  SELECT  1 ID, 'A' NAME, 1 PARENT_ID
  UNION ALL
  SELECT  2 ID, 'B' NAME, 1 PARENT_ID
  UNION ALL
  SELECT  3 ID, 'C' NAME, 2 PARENT_ID
)
SELECT  *
FROM    data
WHERE   parent_id = $ParentId
"@

  Invoke-SqlCmd -ServerInstance "SERVER" -Database "DATABASE" -Query $query

}

Get-Foo | ForEach-Object {

  Get-Bar -ParentId $_.ID

}

外循环的第一次迭代工作正常,但是当它尝试第二次迭代时,会生成异常:

Invoke-SqlCmd :无法从 BeginProcessing、ProcessRecord 和 EndProcessing 方法的覆盖范围之外调用 WriteObject 和 WriteError 方法,并且只能从同一线程中调用它们。验证 cmdlet 是否正确进行了这些调用,或联系 Microsoft 客户支持服务。在 Untitled-1.ps1:18 char:3 + Invoke-SqlCmd -ServerInstance "SERVER" -Database "DATABASE ... + ~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidResult: (SERVER:PSObject) [Invoke-Sqlcmd], PSInvalidOperationExceptio n + FullyQualifiedErrorId : ExecutionFailed,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand

但是,此语法有效:

$foo = Get-Foo
$foo | ForEach-Object {

  Get-Bar

}

我猜我需要关闭第一个Invoke-SqlCmd,但也许还有另一种解决方案。

标签: sql-serverpowershell

解决方案


这与 PowerShell 管道的工作方式有关(请参阅了解管道),以及Invoke-SqlCmd它不喜欢并行运行多个查询的限制。

为了说明,让我们重写您的示例,如下所示:

function Invoke-SqlCmd
{
    write-output "aaa";
    write-output "bbb";
    write-output "ccc";
}

function Get-Foo
{
    write-host "Get-Foo started";
    Invoke-SqlCmd;
    write-host "Get-Foo finished";
}

function Get-Bar
{
    param( $value )
    write-host "Get-Bar '$value'";
}

现在运行这个脚本:

write-host "using pipelines"
Get-Foo | foreach-object { Get-Bar $_ }

这给出了这个输出:

using pipelines
Get-Foo started
Get-Bar 'aaa'
Get-Bar 'bbb'
Get-Bar 'ccc'
Get-Foo finished

如果您查看输出,您可以看到“Get-Bar”命令正在被调用,而 Get-Foo 在管道中仍处于“活动状态”。

与此相比:

write-host "using immediate evaluation"
(Get-Foo) | foreach { Get-Bar $_ }

这使:

using immediate evaluation
Get-Foo started
Get-Foo finished
Get-Bar 'aaa'
Get-Bar 'bbb'
Get-Bar 'ccc'

其中 Get-Foo 在值通过管道传输到 Get-Bar 之前执行完成。

您的具体错误是由于您的脚本使用多个并发管道步骤但Invoke-SqlCmd不支持多个并发管道步骤。幸运的是,您可以通过几种不同的方式告诉 PowerShell 使用“立即评估”,包括其他答案中提到的方法:

  • 首先将表达式分配给临时变量:
$foo = Get-Foo;
$foo | ForEach-Object { Get-Bar $_ }

(Get-Foo) | Foreach-Object { Get-Bar $_ }
  • 或将 Invoke-SqlCmd 包装在函数内的子表达式中。(这有点误导,因为 Get-Foo 在管道中仍然处于活动状态,Invoke-SqlCmd 会立即评估)。
function Get-Foo
{
    write-host "Get-Foo started";
    (Invoke-SqlCmd);
    write-host "Get-Foo finished";
}

推荐阅读