bin/net8.0/PSSqlRepository.psm1
|
# PSSqlRepository module loader # Selects the correct binary for the running .NET runtime version. # PowerShell 7.4 runs on .NET 8, PowerShell 7.6+ runs on .NET 10. $dotnetMajor = [System.Environment]::Version.Major $framework = if ($dotnetMajor -ge 10) { 'net10.0' } else { 'net8.0' } $binRoot = [System.IO.Path]::Combine($PSScriptRoot, 'bin', $framework) if (-not (Test-Path -LiteralPath $binRoot)) { throw "PSSqlRepository: build output for runtime '$framework' not found at '$binRoot'. " + "Detected .NET $($dotnetMajor).x; supported targets are net8.0 (PowerShell 7.4/7.5) " + "and net10.0 (PowerShell 7.6+). The module installation may be corrupted." } foreach ($required in 'PSSqlRepository.Loader.dll', 'PSSqlRepository.Commands.dll') { if (-not (Test-Path -LiteralPath ([System.IO.Path]::Combine($binRoot, $required)))) { throw "PSSqlRepository: could not find $required in '$binRoot'. " + "The module installation may be corrupted." } } # The engine — the cmdlets, EF Core, Microsoft.Extensions.*, the SQL client libraries — runs in an # AssemblyLoadContext of its own (see PSSqlRepository.Loader). Loading it into the process-wide # default context, as Import-Module of the binary path would, decides for every other module in the # session which version of those libraries exists; Az.Resources, for one, then fails to import. # Only the dependency-free loader and the types scripts compile against # (Isystem.Shared.Infrastructure.Core/.Services) go into the default context. # # A loader already loaded by an earlier import is reused: a second file with the same assembly name # cannot be loaded into the default context. Each module directory still gets its own engine. $loaderAssembly = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq 'PSSqlRepository.Loader' -and [System.Runtime.Loader.AssemblyLoadContext]::GetLoadContext($_) -eq [System.Runtime.Loader.AssemblyLoadContext]::Default } | Select-Object -First 1 if (-not $loaderAssembly) { $loaderAssembly = [System.Reflection.Assembly]::LoadFrom([System.IO.Path]::Combine($binRoot, 'PSSqlRepository.Loader.dll')) } $commandsAssembly = $loaderAssembly.GetType('PSSqlRepository.Loader.ModuleEngine', $true)::LoadCommands($binRoot) # Initialization (extension loading, type accelerators) and cleanup are handled inside the binary by # PSSqlRepositoryModuleInitializer (IModuleAssemblyInitializer) and PSSqlRepositoryModuleCleanup # (IModuleAssemblyCleanup). The accelerators make the engine's types nameable from scripts # (class Customer : IEntity[int], class ShopContext : Microsoft.EntityFrameworkCore.DbContext). # # The engine (and with it this assembly) outlives Remove-Module, and so does the binary module an # earlier import created from it: PowerShell keeps it loaded and a second Import-Module -Assembly # from this psm1 then imports none of its cmdlets into the new module. Removing that leftover # first (its IModuleAssemblyCleanup resets the module state) makes every import start clean. Get-Module -All | Where-Object { $_.ModuleType -eq 'Binary' -and $_.ImplementingAssembly -eq $commandsAssembly } | Remove-Module -Force Import-Module -Assembly $commandsAssembly # Update-PSSqlRepositoryEntity: discoverability proxy for Save-PSSqlRepositoryEntity -Mode Update. # Kept as a thin function (not a separate binary cmdlet) so there is exactly one # implementation of the persistence pipeline. The proxy forwards every parameter # (including pipeline input and PassThru) to Save-PSSqlRepositoryEntity but locks # Mode to Update — matching `Get-Command -Verb Update` discoverability without # duplicating the cmdlet surface. function Update-PSSqlRepositoryEntity { [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([object])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [ValidateNotNull()] [psobject] $InputObject, [Parameter(Position = 1)] [ValidateNotNull()] [type] $EntityType, [Parameter()] [switch] $IncludeNavigations, [Parameter()] [PSSqlRepository.Core.OrphanBehavior] $OrphanBehavior = [PSSqlRepository.Core.OrphanBehavior]::FromModel, [Parameter()] [switch] $PassThru, [Parameter()] [switch] $SkipEnumeration, [Parameter()] [ValidateRange(0, [int]::MaxValue)] [int] $CommandTimeout ) process { $forward = @{ InputObject = $InputObject; Mode = 'Update' } if ($PSBoundParameters.ContainsKey('EntityType')) { $forward['EntityType'] = $EntityType } if ($PSBoundParameters.ContainsKey('IncludeNavigations')){ $forward['IncludeNavigations']= $IncludeNavigations } if ($PSBoundParameters.ContainsKey('OrphanBehavior')) { $forward['OrphanBehavior'] = $OrphanBehavior } if ($PSBoundParameters.ContainsKey('PassThru')) { $forward['PassThru'] = $PassThru } if ($PSBoundParameters.ContainsKey('SkipEnumeration')) { $forward['SkipEnumeration'] = $SkipEnumeration } if ($PSBoundParameters.ContainsKey('CommandTimeout')) { $forward['CommandTimeout'] = $CommandTimeout } Save-PSSqlRepositoryEntity @forward } } # NOTE: do NOT call Export-ModuleMember here. # # Calling Export-ModuleMember from a script module replaces the default export set # for THIS module, which by default also exposes cmdlets imported from nested # modules (the binary PSSqlRepository.Commands.dll loaded above). Listing only the # proxy function would silently hide every binary cmdlet from the outer module. # # Exports are controlled exclusively by the manifest: # FunctionsToExport = @('Update-PSSqlRepositoryEntity') # CmdletsToExport = @('Connect-PSSqlRepository', 'Save-PSSqlRepositoryEntity', ...) |