如何在PowerShell中使用linq显式或在SQL中使用“NOT IN”的模拟

问题描述 投票:3回答:2

我有一个关于在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)
}

作为工作的结果,我想从$table1获取记录,其中FindName1不在$Table2.FindName1中,保留所有标题

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

$ExceptOut = [System.Linq.Enumerable]::Except($Table1.FindName1, $Table2.FindName1)

正如我从article所理解的那样,我需要使用允许我在表中使用LINQ的方法创建自己的类。但我离编程太远了。或许在SQL中还有其他一些"NOT IN"的快速模拟。我希望能得到帮助。谢谢。

linq powershell datatable
2个回答
3
投票

为了(通用).Except() LINQ method工作,作为参数传递的两个枚举(IEnumerable<T>)必须:

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

PowerShell似乎无法使用.Except()[object[]]返回的$Table1.FindName1数组找到$Table2.FindName1的正确重载,尽管这些数组技术上满足上述要求 - 我不知道为什么。

但是,简单地将这些数组转换为已经存在的数据 - [object[]] - 解决了这个问题:

[Linq.Enumerable]::Except([object[]] $Table1.FindName1, [object[]] $Table2.FindName1)

鉴于.FindName1列最终包含字符串,您也可以转换为[string[]],尽管在我的非正式测试中,这样做并没有再提供性能,至少在您的示例数据中。


现在,如果您只想在使用.FindName1列进行比较时返回整行,则事情会变得复杂得多:

  • 您必须实现一个实现IEqualityComparer[T]interface的自定义比较器类。
  • 您必须将.Rows数据表集合强制转换为IEnumerable[DataRow],这需要通过反射调用System.Linq.Enumerable.Cast()方法。 注意:虽然您可以直接转换为[DataRow[]],但这会导致将行集合转换为数组效率低下。

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

# 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()
)

This GitHub issue建议让LINQ成为一流的PowerShell公民。


1
投票

使用本机PowerShell解决方案补充LINQ-based answer

Compare-Object cmdlet允许您比较集合,但请注意,虽然它更简洁,但它也比基于LINQ的解决方案慢得多:

Compare-Object -PassThru -Property FindName1 `
  ([Data.DataRow[]] $Table1.Rows) `
  ([Data.DataRow[]] $Table2.Rows) | Where-Object SideIndicator -eq '<='
  • 铸造[Data.DataRow[]] - 从行集合创建一个新数组 - 似乎需要Compare-Object将行识别为可枚举。 调用.GetEnumerator()或者投掷到Collections.IEnumerable没有帮助,并且投射到Collections.Generic.IEnumerable[Data.DataRow]]失败了。
  • -Property FindName1指定比较属性,即用于比较行的属性。
  • -PassThru需要使Compare-Object按原样输出输入对象,而不是仅包含用-Property指定的属性的自定义对象。 请注意,对象使用.SideIndicator NoteProperty成员进行修饰,但是,使用PowerShell的ETS(扩展类型系统) - 请参阅下文。
  • 鉴于Compare-Object输出对任一集合都是唯一的输入对象,必须使用Where-Object SideIndicator -eq '<='将结果限制为LHS输入集合唯一的差异对象(通过.SideIndicator'<='属性值发出信号 - 箭头指向对象是唯一的)。

This GitHub issue提出了对Compare-Object cmdlet的一些改进,这有助于简化和加快上述解决方案。 也就是说,对make LINQ a first-class PowerShell citizen的提议更有希望。

© www.soinside.com 2019 - 2024. All rights reserved.