Как я могу использовать PSCustomObject ScriptMethod в конвейере

PS Версия: 5.1


Желанный

What I wished would work
$obj = [PSCustomObject]@{
   PSTypeName = 'MyObject'
   con        = 'local'
}
add-member -MemberType ScriptMethod -InputObject $obj -Name MyMethod -Value {
   param([parameter(ValueFromPipeline=$true)]$x)
   begin  {write-host 'begin'}
   process{write-host "$($this.con): $x"}
   end    {write-host 'end'}
}
1..5 | $obj.MyMethod

Давать

begin
local: 1
local: 2
local: 3
local: 4
local: 5
end

но выдает ошибку

At line:1 char:8
+ 1..5 | $obj.MyMethod
+        ~~~~~~~~~~~~~
Expressions are only allowed as the first element of a pipeline.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ExpressionsMustBeFirstInPipeline

Пытался

what doesn't work because: idk, tl;dr reasons?
1..5 | %{$obj.MyMethod($_)}

что не работает, потому что: начало/конец

$obj = [PSCustomObject]@{
   PSTypeName = 'MyObject'
   con        = 'local'
}
add-member -MemberType ScriptMethod -InputObject $obj -Name MyMethod -Value {
   param($x)
   write-host "$($this.con): $x"
}
1..5 | %{$obj.MyMethod($_)}

что не работает, потому что: $this больше не привязан

$obj = [PSCustomObject]@{
   PSTypeName = 'MyObject'
   con        = 'local'
   MyMethodSB  = {
      param()
      begin  {write-host 'begin'}
      process{write-host "$($obj.con): $_"}
      end    {write-host 'end'}
   }
}
1..5 | &$obj.MyMethodSB

что не работает, потому что: idk, может быть, прицел Stepper умирает преждевременно

$obj = [PSCustomObject]@{
   PSTypeName = 'MyObject'
   con        = 'local'
   Stepper    = $null
}
add-member -MemberType ScriptMethod -InputObject $obj -Name MyMethod -Value {
   param($x)
   if($null -eq $this.Stepper){
      $t = $this
      $sb = {
         param($o)
         begin  {write-host 'begin'}
         process{write-host "$($o.con): $_"}
         end    {write-host 'end'}
      }
      $this.Stepper = {&$sb $t}.GetSteppablePipeline()
      $this.Stepper.Begin($true)
   }
   $this.Stepper.Process($x)
}
1..5 | %{$obj.MyMethod($_)} 
$obj.Stepper.End()
$obj.Stepper = $null

Почти готово

What is kinda working
$Data = @{
   ItemNo1 = @(
      @{loc = 'WH';  qty = 20}
      @{loc = 'DK';  qty = 0}
      @{loc = 'ST1'; qty = 3}
      @{loc = 'ST2'; qty = 2}
   )
   ItemNo2 = @(
      @{loc = 'WH'; qty = 6}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo3 = @(
      @{loc = 'WH'; qty = 100}
      @{loc = 'ST1'; qty = 5}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo4 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 0}
      @{loc = 'ST2'; qty = 15}
   )
   ItemNo5 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 15}
   )
}
$QueryParameter = [PSCustomObject]@{Value = $null}
function MockDataQuery{
   $Item = $QueryParameter.Value
   foreach($iDat in $Data.$Item){
      "   $Item;$($iDat.loc);$($iDat.qty)"
   }
}
$InvObj = [PSCustomObject]@{
   PSTypeName = 'Data.Connection'
   con        = 'item.inventory'
}
add-member -MemberType ScriptMethod -InputObject $InvObj -Name FetchItemData -Value {
   $p = $this
   $qPipe = [PSCustomObject]@{
      PSTypeName = 'Data.Connection.QueryPipe'
      parent  = $p
      qHeader = 'Item;Location;Quantity'
      query   = 'select * from inventory where item = ?'
      param   = $null
   }
   add-member -MemberType ScriptMethod -InputObject $qPipe -Name begin   -Value {
      param([bool]$Header=$true)
      write-host "connect to: $($this.parent.con)"
      write-host "compile command: $($this.query)"
      $this.param = $QueryParameter
      write-host 'begin transaction'
      if($Header){
         write-host "   $($this.qHeader)"
      }
   }
   add-member -MemberType ScriptMethod -InputObject $qPipe -Name process -Value {
      param($itm)
      $this.param.Value = $itm
      MockDataQuery | write-host
   }
   add-member -MemberType ScriptMethod -InputObject $qPipe -Name end     -Value {
      write-host 'end transaction'
      write-host 'dispose command'
      write-host 'close connection'
   }
   return $qPipe
}
1..5 | %{"ItemNo$_"} | &{
   begin  {$stp = $InvObj.FetchItemData();$stp.begin()}
   process{$stp.process($_)}
   end    {$stp.end()}
}

Хотя это кажется функциональной моделью, было бы лучше, если бы существовало решение, которое не требовало бы от пользователя объекта подключения кодировать сценарий начала/процесса/конца на своей стороне каждый раз, когда они используют метод объекта. .


person Gregor y    schedule 11.02.2021    source источник
comment
Чего именно вы пытаетесь достичь и почему важно использовать ScriptMethod? ScriptMethods не совсем предназначены для использования в качестве командлетов.   -  person Mathias R. Jessen    schedule 12.02.2021
comment
Последнее ближе всего к моему варианту конечного использования, объект соединения с методом, который запрашивает данные через параметр, который я могу использовать в конвейере. Однако после дальнейшего тестирования даже у последнего возникли проблемы с выводом в конвейере.   -  person Gregor y    schedule 12.02.2021
comment
Является ли db/querystore конкретным или вы пытаетесь создать уровень абстракции для нескольких серверных хранилищ? Многие механизмы баз данных уже имеют SDK, которые заботятся об абстрагировании этих вещей.   -  person Mathias R. Jessen    schedule 12.02.2021
comment
@MathiasR.Jessen Один общий уровень абстракции объектов поверх нескольких SDK механизма баз данных. Было бы неплохо передавать параметры запроса CSV в соединение, которое знает, какой пакет SDK использовать для помещения результатов в выходной файл. Использование конвейера помогает поддерживать накладные расходы в ОЗУ на достаточно низком уровне для получения результатов. Возможно, я смогу преобразовать его в командлет, и, вероятно, мне нужно углубиться в это.   -  person Gregor y    schedule 12.02.2021


Ответы (1)


Через пару дней и слишком много кофе; Я думаю, что я узнал здесь, что если вы можете, попробуйте провести рефакторинг как старую добрую прямую расширенную функцию/командлет

$Data = @{
   ItemNo1 = @(
      @{loc = 'WH';  qty = 20}
      @{loc = 'DK';  qty = 0}
      @{loc = 'ST1'; qty = 3}
      @{loc = 'ST2'; qty = 2}
   )
   ItemNo2 = @(
      @{loc = 'WH'; qty = 6}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo3 = @(
      @{loc = 'WH'; qty = 100}
      @{loc = 'ST1'; qty = 5}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo4 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 0}
      @{loc = 'ST2'; qty = 15}
   )
   ItemNo5 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 15}
   )
}
$QueryParameter = [PSCustomObject]@{Value = $null}
function MockDataQuery{
   $Item = $QueryParameter.Value
   foreach($iDat in $Data.$Item){
      "   $Item;$($iDat.loc);$($iDat.qty)"
   }
}
filter Write-PassThru{
   write-host $_ -ForegroundColor Cyan
   $_
}
$InvObj = [PSCustomObject]@{
   PSTypeName    = 'Data.Connection'
   con           = 'item.inventory'
   FetchItemData = 'Get-ItemData'
}

function Get-ItemData{
   param(
      [parameter(mandatory=$true)]
      [PSTypeName('Data.Connection')]
      $t,
      
      [bool]
      $Header=$true,
      
      [Parameter(ValueFromPipeline=$true)]
      $itm
   )
   begin{
      $query   = 'select * from inventory where item = ?'
      $qHeader = 'Item;Location;Quantity'
      $param   = $null

      write-host "connect to: $($t.con)"
      write-host "compile command: $query"
      $param = $QueryParameter
      write-host 'begin transaction'
      if($Header){
         "   $qHeader" | write-passthru
      }
   }process{
      $param.Value = $itm
      MockDataQuery | write-passthru
   }end{
      write-host 'end transaction'
      write-host 'dispose command'
      write-host 'close connection'
   }
}

1..5                        `
   | %{"ItemNo$_"}          `
   | &$InvObj.FetchItemData $InvObj

however if you can't, you could also try to do something terribly hacky like this
$Data = @{
   ItemNo1 = @(
      @{loc = 'WH';  qty = 20}
      @{loc = 'DK';  qty = 0}
      @{loc = 'ST1'; qty = 3}
      @{loc = 'ST2'; qty = 2}
   )
   ItemNo2 = @(
      @{loc = 'WH'; qty = 6}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo3 = @(
      @{loc = 'WH'; qty = 100}
      @{loc = 'ST1'; qty = 5}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo4 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 0}
      @{loc = 'ST2'; qty = 15}
   )
   ItemNo5 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 15}
   )
}
$QueryParameter = [PSCustomObject]@{Value = $null}
function MockDataQuery{
   $Item = $QueryParameter.Value
   foreach($iDat in $Data.$Item){
      "   $Item;$($iDat.loc);$($iDat.qty)"
   }
}
filter Write-PassThru{
   write-host $_ -ForegroundColor Cyan
   $_
}
$InvObj = [PSCustomObject]@{
   PSTypeName    = 'Data.Connection'
   con           = 'item.inventory'
   FetchItemData = {
      param(
         [bool]
         $Header=$true,
         
         [Parameter(ValueFromPipeline=$true)]
         $itm
      )
      begin{
         $pm = $MyInvocation.PositionMessage -split "`n"
         [int]$at = $pm[0] -replace '.*char:(\d+).*','$1'
         $l = ($pm[2] -replace '[^~]','').Length
         $myCall = $pm[1].substring($at+1,$l)
         $thus = get-variable -Name ($myCall -replace '.*?\${?([^}.]+)}?\..*','$1') -ValueOnly

         $query   = 'select * from inventory where item = ?'
         $qHeader = 'Item;Location;Quantity'
         $param   = $null

         write-host "connect to: $($thus.con)"
         write-host "compile command: $query"
         $param = $QueryParameter
         write-host 'begin transaction'
         if($Header){
            "   $qHeader" | write-passthru
         }
      }process{
         $param.Value = $itm
         MockDataQuery | write-passthru
      }end{
         write-host 'end transaction'
         write-host 'dispose command'
         write-host 'close connection'
      }
   }
}

function Annotate-Flow{
   param(
      [string]
      $tag = 'Flow',
      
      [switch]
      $head,
      
      [switch]
      $tail
   )
   begin{
      if($head){
         $Global:FlowSequence = 0
      }else{$Global:FlowSequence++}
      write-host ("${Tag} Seq[{0,2}]: Begin" -f $Global:FlowSequence) -ForegroundColor DarkCyan
   }process{
      $Global:FlowSequence++
      write-host ("${Tag} Seq[{0,2}]: Process($_)" -f $Global:FlowSequence)-ForegroundColor DarkCyan
      $_
   }end{
      $Global:FlowSequence++
      write-host ("${Tag} Seq[{0,2}]: End" -f $Global:FlowSequence) -ForegroundColor DarkCyan
      if($tail){
         remove-variable -Scope Global -Name FlowSequence
      }
   }
}

1..5                        | Annotate-Flow Source -head `
   | %{"ItemNo$_"}          | Annotate-Flow Format       `
   | &$InvObj.FetchItemData | Annotate-Flow Lookup -tail

some stuff you might (not) want to read:

Хотя, если честно, это больше похоже на простой ответ, чем на элегантное решение.

person Gregor y    schedule 19.02.2021