我有一个关于在 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"
快速模拟。
我希望得到帮助。谢谢。
最佳答案
对于(通用) .Except()
LINQ method要工作,作为参数传递的两个枚举 ( IEnumerable<T>
) 必须:
- 枚举相同类型的实例
T
- 并且,如果该类型是一个引用类型,其实例应该根据实例的内容进行有意义的比较(而不是仅仅通过引用相等,即身份),必须实现
IEquatable<T>
接口(interface)和/或覆盖.Equals()
方法。
PowerShell 似乎无法为 .Except()
找到正确的重载与 [object[]]
$Table1.FindName1
返回的数组和 $Table2.FindName1
,尽管这些阵列在技术上满足上述要求 - 我不知道为什么。
但是,只需将这些数组转换为它们已有的样子 - [object[]]
- 解决问题:
[Linq.Enumerable]::Except([object[]] $Table1.FindName1, [object[]] $Table2.FindName1)
鉴于 .FindName1
列最终包含字符串,您也可以转换为[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()
)
GitHub issue #2226提议使 LINQ 成为一流的 PowerShell 公民。
关于linq - 如何在 powershell 中使用 linq explict 或在 SQL 中使用 "NOT IN"的类似物,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54963252/