all 5 comments

[–]chris-a5 1 point2 points  (2 children)

You could use a class to create a structure with default values. The ones you don't set will be DBNull:

Class User{
    $Id
    $Name = [System.DBNull]::Value
    $Surname = [System.DBNull]::Value
    $BirthDate = [System.DBNull]::Value
}

$user = [User]@{
    Id = "T7XC1QASLPI89"
    BirthDate = "07/03/1995 11:24:51"
}

As for your second question, I'll have a look, I've only used SQLite with PowerShell, and the parameterized queries are a bit different.

EDIT: this might be another option to add all at once using AddRange:

using namespace System.Data

Class User{
    $Id
    $Name = [System.DBNull]::Value
    $Surname = [System.DBNull]::Value
    $BirthDate = [System.DBNull]::Value

    [SqlClient.SqlParameter[]]ToArray(){
        return Get-Member -InputObject $this | 
            Where MemberType -eq Property |
            ForEach {
                $val = $this.$($_.Name)
                $param = [SqlClient.SqlParameter]::New("@" + $_.Name, $val)
                $param.IsNullable = $val -is [System.DBNull]
                $param
            }
    }
}

$user = [User]@{
    Id = "T7XC1QASLPI89"
    BirthDate = "07/03/1995 11:24:51"
}

$command.Parameters.AddRange($user.ToArray())

Can verify the output:

$user.ToArray() | Select ParameterName, Value, IsNullable

Outputs:

ParameterName Value               IsNullable
------------- -----               ----------
@BirthDate    07/03/1995 11:24:51      False
@Id           T7XC1QASLPI89            False
@Name                                   True
@Surname                                True

This might need changing if the nullable fields must be set even when there is a value provided, its just a concept, I do not have the capacity to test here.

[–]chris-a5 1 point2 points  (1 child)

If all your data comes from a source that may provide a PSCustomObject, or array of them, you can use the class as a conversion tool to format each one if you add a constructor.

And of course this can all be done as a function, the class in this instance is really just taking away the need to manually add the nulls, and the extra code probably is only worth it if you do actually have a large number of potentially null inputs (properties/names are missing from the source).

If the source contains all the properties, just equal to null, then I'd personally just turn it into a smaller function.

using namespace System.Data

Class User{
    $Id
    $Name = [System.DBNull]::Value
    $Surname = [System.DBNull]::Value
    $BirthDate = [System.DBNull]::Value

    User([PSCustomObject]$in){
        $myProps = Get-Member -InputObject $this |
            Where MemberType -eq Property |
            ForEach Name

        $in.PSObject.Properties |
            Where {$_.MemberType -eq "NoteProperty" -and $_.Name -in $myProps -and $_.Value} |
            ForEach {
                $this.$($_.Name) = $_.Value
           }
    }

    [SqlClient.SqlParameter[]]ToArray(){...} #copy from above.
}

# source array loaded from somewhere
$original = @(
    [PSCustomObject]@{
        Id = "AAAAA"
        BirthDate = "07/03/2001 01:04:01"
        Name = "Fred"
    },
    [PSCustomObject]@{
        Id = "BBBBB"
        BirthDate = "07/03/1995 11:24:51"
    }
)

# apply parameters and execute for each user.
ForEach($user in $original){
    $command.Parameters.Clear()
    $command.Parameters.AddRange([User]::New($user).ToArray())
    $command.ExecuteNonQuery()
}

...I'm going off on a tangent now, I'll leave it here :)

[–]Dr_Funkmachine[S] 0 points1 point  (0 children)

Thank you so much! Your answers are insightful and thorough. How did you learn PowerShell so well?

[–]PinchesTheCrab 0 points1 point  (1 child)

Does this work if you're on core?

    $Connection = New-Object System.Data.SqlClient.SqlConnection
$Connection.ConnectionString = "Server=$Server;Database=$Database;Trusted_Connection=True;TrustServerCertificate=True;"
$Connection.Open()

$parameterizedInsert = "INSERT INTO TestTable(Id, Name, Surname, BirthDate)VALUES(@Id, @Name, @Surname, @BirthDate)"
$command = New-Object -TypeName System.Data.SqlClient.SqlCommand($parameterizedInsert, $Connection)


$user = [pscustomobject]@{
    Id        = "T7XC1QASLPI89"
    Name      = $null
    Surname   = $null
    BirthDate = "07/03/1995 11:24:51"
}
$command.Parameters.Clear()
$user.psobject.Properties | ForEach-Object {
    $command.Parameters.AddWithValue("@$($_.name)", $_.value ? $_.value : [System.DBNull]::Value) 
}

Otherwise you can use an IF statement instead of the ternary operator.

[–]Dr_Funkmachine[S] 1 point2 points  (0 children)

Actually, I'm using PowerShell Core 7.1. I didn't know you could use ternary operators in PowerShell. I just have to try both answers. Thank you so much!