如何在 powershell 中使用 linq explict 或在 SQL 中类似于“NOT IN”

Vas*_*kin 3 linq datatable powershell

我有一个关于在 PowerShell 中使用 Linq 的问题。我不知道如何正确使用该Except方法

示例表:

$Arr = 1..1000
$Props = ("employeeID","FindName1","FindName2")
$Table1 = New-Object System.Data.DataTable "Table1"
$Props | ForEach-Object { $Table1.Columns.Add( $_ , [String]) | Out-Null }

ForEach ($Record in $Arr ) {
    $Row = $Table1.NewRow()
    $Row.employeeID = $Record.ToString("00000")
    $Row.FindName1 = "UserName_" + $Record.ToString()
    $Row.FindName2 = "String_" + $Record.ToString("00000000")
    $Table1.Rows.Add($Row)
}

$Arr2 = 980..1111
$Props = ("employeeID","FindName1")
$Table2 = New-Object System.Data.DataTable "Table2"
$Props | ForEach-Object { $Table2.Columns.Add( $_ , [String]) | Out-Null }

ForEach ($Record in $Arr2 ) {
    $Row = $Table2.NewRow()
    $Row.employeeID = $Record.ToString("00000")
    $Row.FindName1 = "UserName_" + $Record.ToString()
    $Table2.Rows.Add($Row)
}
Run Code Online (Sandbox Code Playgroud)

作为工作的结果,我想从$table1FindName1 not in 中获取记录$Table2.FindName1保留所有标题

尝试执行不会产生预期的结果。

$ExceptOut = [System.Linq.Enumerable]::Except($Table1.FindName1, $Table2.FindName1)
Run Code Online (Sandbox Code Playgroud)

正如我从文章中了解到的,我需要使用允许我在表中使用 LINQ 的方法创建自己的类。但我离编程还很远。或者也许还有其他一些类似于"NOT IN"SQL 的快速模拟。我希望得到帮助。谢谢。

mkl*_*nt0 5

要使(通用).Except()LINQ 方法工作,IEnumerable<T>作为参数传递的两个可枚举项 ( ) 必须:

  • 枚举相同类型的实例 T
  • 并且该类型必须实现该IEquatable<T>接口。

PowerShell是看似无法找到合适的过载.Except()[object[]]阵列由归国$Table1.FindName1$Table2.FindName1,虽然这些阵列技术上满足上述要求-我不知道为什么。

但是,只需将这些数组转换为已经存在的数组即可[object[]]解决问题:

[Linq.Enumerable]::Except([object[]] $Table1.FindName1, [object[]] $Table2.FindName1)
Run Code Online (Sandbox Code Playgroud)

鉴于该.FindName1列最终包含strings,您也可以强制转换为[string[]],但在我的非正式测试中,这样做并没有再次提供性能,至少对于您的示例数据。


现在,如果您想列用于比较返回整行.FindName1,事情会变得更加复杂:

  • 您必须实现一个实现IEqualityComparer[T]接口的自定义比较器类。

  • 您必须将.Rows数据表的集合转换为IEnumerable[DataRow],这需要通过反射调用 System.Linq.Enumerable.Cast() 方法。

    • 注意:虽然您可以直接[DataRow[]]转换为,但这会涉及将行集合转换为数组的效率低下。

这是将自定义比较器类实现为 PowerShell 类的 PSv5+ 解决方案:

# A custom comparer class that compares two DataRow instances by their
# .FindName1 column.
class CustomTableComparer : Collections.Generic.IEqualityComparer[Data.DataRow] {
  [bool] Equals([Data.DataRow] $x, [Data.DataRow] $y) {
    return [string]::Equals($x.FindName1, $y.FindName1, 'Ordinal')
  }
  [int] GetHashCode([Data.DataRow] $row) {
    # Note: Any two rows for which Equals() returns $true must return the same
    #       hash code. Because *ordinal, case-sensitive* string comparison is
    #       used above, it's sufficient to simply call .GetHashCode() on
    #       the .FindName1 property value, but that would have to be tweaked
    #       for other types of string comparisons.
    return $row.FindName1.GetHashCode();
  }
}


# Use reflection to get a reference to a .Cast() method instantiation 
# that casts to IEnumerable<DataRow>.
$toIEnumerable = [Linq.Enumerable].GetMethod('Cast').MakeGenericMethod([Data.DataRow])

# Call .Except() with the casts and the custom comparer.
# Note the need to wrap the .Rows value in an aux. single-element
# array - (, ...) - for it to be treated as a single argument.
[Linq.Enumerable]::Except(
    $toIEnumerable.Invoke($null, (, $Table1.Rows)), 
    $toIEnumerable.Invoke($null, (, $Table2.Rows)), 
    [CustomTableComparer]::new()
)
Run Code Online (Sandbox Code Playgroud)

这个 GitHub 问题建议让 LINQ 成为一流的 PowerShell 公民。