internal/functions/providers/Connect-SldgRepositorySession.ps1
|
function Connect-SldgRepositorySession { <# .SYNOPSIS Opens a database session through PSSqlRepository and returns the raw connection. .DESCRIPTION The single place where this module talks to a database driver. It registers the module's empty repository context (SqlLabDataGenerator.Data.SldgSchemaContext) for the requested PSSqlRepository provider, calls Connect-PSSqlRepository with the supplied parameters, and pulls the provider-agnostic System.Data.Common.DbConnection out of the resulting EF Core session. Everything downstream - schema discovery, inserts, FK lookups - works against that DbConnection and the SqlDialect resolved for the provider, so any provider PSSqlRepository knows (SqlServer, Sqlite, DuckDB, installed extensions) works here without a driver of its own. Connect-PSSqlRepository publishes the session as PSSqlRepository's ambient session; Disconnect-SldgRepositorySession releases it again. .PARAMETER Provider PSSqlRepository provider name (Get-PSSqlRepositoryProvider). .PARAMETER ConnectParameter Hashtable splatted onto Connect-PSSqlRepository: the provider's own connect parameters (-Server/-Database, -Path, -ConnectionString, ...), -AuthMode, -Credential. .PARAMETER ServerInstance Display value for the connection object (host or 'localhost'). .PARAMETER Database Display value for the connection object (database name or file path). .PARAMETER ConnectionTimeout Timeout in seconds applied while opening the raw DbConnection. #> [OutputType([SqlLabDataGenerator.Connection])] [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$Provider, [Parameter(Mandatory)] [hashtable]$ConnectParameter, [string]$ServerInstance = 'localhost', [string]$Database, [int]$ConnectionTimeout = 30 ) if (-not (Get-Command -Name Connect-PSSqlRepository -ErrorAction SilentlyContinue)) { Stop-PSFFunction -String 'Connect.PSSqlRepositoryMissing' -EnableException $true } $providerInfo = Get-PSSqlRepositoryProvider | Where-Object Name -eq $Provider | Select-Object -First 1 if (-not $providerInfo) { $available = (Get-PSSqlRepositoryProvider | ForEach-Object Name | Sort-Object) -join ', ' Stop-PSFFunction -String 'Provider.NotFound' -StringValues $Provider, $available -EnableException $true } # Database-first: let PSSqlRepository read the catalogue and emit one entity type per table # with a single-column primary key, registered as the provider's repository context. The # generator keeps its own schema readers for the metadata the import does not carry (check # constraints, keyless and composite-key tables), but reads and masking updates go through # the entities, and the caller gets [Customer]-style types for the connected database. # When nothing can be imported (empty database, only keyless tables) the empty context is # registered instead so the session still opens for raw SQL. $schemaImport = $null try { $schemaImport = Import-PSSqlRepositorySchema -ProviderName $providerInfo.Name @ConnectParameter -NoRegister -Confirm:$false -WarningAction SilentlyContinue -ErrorAction Stop } catch { Write-PSFMessage -Level Warning -String 'Connect.SchemaImportFailed' -StringValues $providerInfo.Name, $_.Exception.Message } $entityTypes = New-Object 'System.Collections.Generic.Dictionary[string,type]' ([System.StringComparer]::OrdinalIgnoreCase) if ($schemaImport -and $schemaImport.EntityTypes.Count -gt 0) { # Register the emitted types as the provider's context - the same registration the cmdlet # makes without -NoRegister, minus a second catalogue read. $providerDefinition = [PSSqlRepository.Core.Hosting.PSSqlRepositoryHost]::Current.Providers.GetProvider($providerInfo.Name) [PSSqlRepository.Core.Schema.DatabaseSchemaImporter]::Register($providerDefinition, $schemaImport) foreach ($table in $schemaImport.Imported) { $entityTypes[$table.FullName] = $table.EntityType } $skipped = @($schemaImport.Skipped) Write-PSFMessage -Level Verbose -String 'Connect.SchemaImported' -StringValues $providerInfo.Name, $schemaImport.Imported.Count, $skipped.Count, (($skipped | ForEach-Object { "$($_.FullName): $($_.SkipReason)" }) -join '; ') } else { if ($schemaImport) { Write-PSFMessage -Level Verbose -String 'Connect.SchemaImportSkipped' -StringValues $providerInfo.Name, "$($schemaImport.Tables.Count) table(s) seen, none with a single-column primary key" } Register-PSSqlRepositoryContext -ContextType ([SqlLabDataGenerator.Data.SldgSchemaContext]) -ProviderName $providerInfo.Name } $repository = $null $dbConnection = $null try { $repository = Connect-PSSqlRepository -ProviderName $providerInfo.Name @ConnectParameter -Confirm:$false -ErrorAction Stop if (-not $repository) { throw 'Connect-PSSqlRepository returned no session.' } # Session.Services is the session's DI scope with exactly one DbContext in it - the # imported DynamicEntityDbContext or the empty fallback context. EF Core's relational # facade hands out the driver's DbConnection. $context = $repository.Session.Services.GetService([Microsoft.EntityFrameworkCore.DbContext]) if (-not $context) { throw 'The PSSqlRepository session does not expose a DbContext.' } $dbConnection = [Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions]::GetDbConnection($context.Database) if ($dbConnection.State -ne [System.Data.ConnectionState]::Open) { $openTask = $dbConnection.OpenAsync() if (-not $openTask.Wait([timespan]::FromSeconds([math]::Max(1, $ConnectionTimeout)))) { throw "Opening the connection did not complete within $ConnectionTimeout seconds." } if ($openTask.IsFaulted) { throw $openTask.Exception.GetBaseException() } } } catch { if ($repository) { try { Disconnect-PSSqlRepository -Confirm:$false -ErrorAction SilentlyContinue } catch { $null = $_ } } Stop-PSFFunction -String 'Connect.Failed' -StringValues $providerInfo.Name, $ServerInstance, $Database, $_.Exception.Message -EnableException $true -ErrorRecord $_ return } $dialect = [SqlLabDataGenerator.Data.SqlDialect]::ForProvider($providerInfo.Name) Write-PSFMessage -Level Verbose -String 'Connect.SessionOpened' -StringValues $providerInfo.Name, $dialect.Name, $dbConnection.GetType().Name [SqlLabDataGenerator.Connection]@{ DbConnection = $dbConnection Repository = $repository Dialect = $dialect ServerInstance = $ServerInstance Database = $Database Provider = $providerInfo.Name ConnectedAt = Get-Date SchemaImport = $schemaImport ConnectParameters = $ConnectParameter EntityTypes = $entityTypes } } |