Как использовать linq explict в powershell или аналог «NOT IN» в SQL - PullRequest
0 голосов
/ 03 марта 2019

У меня есть вопрос об использовании Linq в PowerShell.Не могу понять, как правильно использовать метод 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 в таблицах.Но я чрезвычайно далек от программирования.Или, может быть, есть какой-то другой быстрый аналог "NOT IN" в SQL.Я надеюсь на помощь.Спасибо.

Ответы [ 2 ]

0 голосов
/ 03 марта 2019

Для дополнения ответа LINQ с помощью собственного решения PowerShell :

Командлет Compare-Object позволяет сравнивать коллекции, но учтите, что хотя он более краткий , он также намного медленнее, чем решение на основе LINQ :

Compare-Object -PassThru -Property FindName1 `
  ([Data.DataRow[]] $Table1.Rows) `
  ([Data.DataRow[]] $Table2.Rows) | Where-Object SideIndicator -eq '<='
  • Casting[Data.DataRow[]] - который создает новый массив из коллекции строк - по-видимому, необходим для Compare-Object, чтобы распознать строки как перечисляемые.

    • Вызов .GetEnumerator() или приведение к Collections.IEnumerable не делаетt help, и приведение к Collections.Generic.IEnumerable[Data.DataRow]] завершается неудачно.
  • -Property FindName1 определяет свойство сравнения, т. е. свойство сравнения строк по.

  • -PassThru необходимо, чтобы Compare-Object выводил входные объекты как есть, вместо пользовательских объектов, которые содержат только свойства, указанные в -Property.

    • Обратите внимание, что объекты украшены элементом .SideIndicator NoteProperty, однако, с помощью ETS PowerShell (система расширенного типа) -см. ниже.
  • Учитывая, что Compare-Object выводит входные объекты, которые являются уникальными для либо коллекции, Where-Object SideIndicator -eq '<=' необходимо использовать для ограничения результатов доте разностные объекты, которые являются уникальными для входной коллекции LHS (которая сигнализируется через значение свойства .SideIndicator, равное '<=' - стрелка указывает в сторону, для которой объект уникален).

В этом выпуске GitHub предлагается ряд улучшений для командлета Compare-Object, которые могут помочь упростить и ускорить приведенное выше решение.
Тем не менее, предложение сделает LINQ первым-класс PowerShell гражданин имеет гораздо больше обещаний.

0 голосов
/ 03 марта 2019

Чтобы (общий) метод .Except() LINQ работал, два перечислимых значения (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].

  • Необходимо преобразовать коллекцию .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()
)

Эта проблема GitHub предлагает сделать LINQ первоклассным гражданином PowerShell.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...