Search code examples
powershelllinq

Cannot find an overload for "ToLookup" and the argument count: "2" LINQ


I have a PowerShell script that runs every hour, it essentially updates product info through a drip process. The script is failing in the dev environment, but continues to work in prod and qual. The failure point is at a line that uses LINQ. Here is the script:

$productInfos = [pscustomobject]@{
    product_name = 'Television'
    price = 1000
    product_id = 101
    } | ForEach-Object{$_}

$productIdDelegate = [Func[object,int]] {$args[0].product_id}
$productInfos = [Linq.Enumerable]::ToLookup($productInfos, $productIdDelegate)

Technically, it fails on the last line. but the second to last line is included because while researching this issue, I found a lot of other posts that pointed out if the two arguments in the ToLookup linq function are not of the same type, it can cause this issue. The second to last line was the "supposed" fix for many users, but sadly not myself. I still receive the

Cannot find an overload for "ToLookup" and the argument count: "2"

I checked any dependencies on all 3 environments, and everything is the same. And, the powershell versions are the same on all as well.


Solution

  • Even though the error doesn't give much details, from your currently reproducible code we can tell that the issue is because $productInfos is a single object of the type PSObject and this class does not implement IEnumerable Interface:

    $productInfos = [pscustomobject]@{
        product_name = 'Television'
        price = 1000
        product_id = 101
    }
    
    $productInfos -is [System.Collections.IEnumerable] # => False
    

    To ensure $productInfos is always an enumerable you can use the Array subexpression operator @( ):

    @($productInfos) -is [System.Collections.IEnumerable] # => True
    

    Casting [object[]] or [array] should also be an option:

    [object[]] $productInfos -is [System.Collections.IEnumerable] # => True
    [array] $productInfos -is [System.Collections.IEnumerable]    # => True
    

    However above methods only work as long as $productInfos has at least 1 element, as opposed to @(..) which will always ensure an array:

    The result is always an array of 0 or more objects.

    To summarize:

    $productInfos = [Linq.Enumerable]::ToLookup([object[]] $productInfos, $productIdDelegate)
    $productInfos = [Linq.Enumerable]::ToLookup(@($productInfos), $productIdDelegate)
    $productInfos = [Linq.Enumerable]::ToLookup([array] $productInfos, $productIdDelegate)
    

    Are all valid options having what's explained above in consideration, if we're not sure if the variable will always be populated, the safest alternative will always be @(...):

    # Returns Null instead of an error:
    
    [Linq.Enumerable]::ToLookup(@(), $productIdDelegate)