From 9a73467db2c1534d1f2a279927a29d79b3de583c Mon Sep 17 00:00:00 2001 From: Arius Date: Thu, 14 May 2026 21:14:33 +0200 Subject: [PATCH 1/5] feat: enhance AutoContracts with new manifest handling and SQL parameter normalization --- .github/scripts/ValidatePackage.ps1 | 344 +++++++++++++++--- .github/workflows/benchmark.yml | 12 +- .github/workflows/ci.yml | 13 +- .github/workflows/codeql.yml | 8 +- .github/workflows/integration-tests.yml | 10 +- .github/workflows/nuget-dotnet.yml | 8 +- .github/workflows/release.yml | 34 +- .github/workflows/security.yml | 74 ++-- .../AutoContracts/AutoContractsCSharpNames.cs | 38 ++ .../AutoContractsManifestAnalyzer.cs | 228 ++++++++++-- .../AutoContractsManifestFile.cs | 21 ++ .../AutoContractsManifestModels.cs | 21 +- .../AutoContractsManifestParser.cs | 96 ++++- Benchmark/Data/Simple/SimpleDto.cs | 20 - .../Benchs/Mapping/DtoMappingBench.cs | 8 - Documentations/docs/caeriusnet-logo.webp | Bin 606132 -> 0 bytes .../Abstractions/IUsersRepository.cs | 4 - .../Repositories/UsersRepository.Reads.cs | 11 - .../AutoContractsCacheKeyEmitter.cs | 3 + .../AutoContractsEmissionGuard.cs | 84 +++++ .../AutoContracts/AutoContractsEmitter.cs | 2 +- .../AutoContractsManifestFile.cs | 21 ++ .../AutoContractsManifestParser.cs | 56 ++- .../AutoContracts/AutoContractsModels.cs | 1 + .../AutoContractsSourceGenerator.cs | 10 +- .../AutoContracts/AutoContractsSourceNames.cs | 24 +- .../AutoContracts/AutoContractsSqlEmitter.cs | 4 +- SourceGenerators/Helpers/TypeDetector.cs | 60 --- .../StoredProcedureParametersBuilder.Typed.cs | 12 +- .../StoredProcedureParametersBuilder.cs | 130 ++++++- Src/Caches/CaeriusNetCache.cs | 4 +- Src/Caches/FrozenCacheManager.cs | 26 +- Src/Caches/InMemoryCacheManager.cs | 127 +++++-- .../TransactionReadSqlAsyncCommands.cs | 23 ++ .../TransactionWriteSqlAsyncCommands.cs | 23 ++ Src/Factories/CaeriusNetDbContext.cs | 31 +- Src/Factories/CaeriusNetTransaction.cs | 172 ++++++--- Src/Helpers/MultiResultSetHelper.cs | 52 +-- Src/Helpers/ResultSetMaterializer.cs | 85 +++++ Src/Helpers/SqlCommandHelper.cs | 59 +-- Src/Helpers/SqlCommandHelperTx.cs | 56 +-- Src/Mappers/StoredProcedureParameters.cs | 57 ++- .../AutoContractsDiagnosticDescriptorTests.cs | 20 +- .../AutoContractsManifestAnalyzerTests.cs | 109 +++++- .../Utilities/AnalyzerTestHelper.cs | 32 +- .../AutoContractsSourceGeneratorTests.cs | 116 +++++- .../Helpers/TypeDetectorTests.cs | 24 -- .../Utilities/SourceGeneratorTestHelper.cs | 66 +++- .../AutoContractsPackagingTests.cs | 160 +++++++- .../StoredProcedureParametersBuilderTests.cs | 148 +++++++- ...redProcedureParametersBuilderTypedTests.cs | 48 ++- .../CaeriusNetCacheInvalidationTests.cs | 103 +++++- .../Caches/FrozenCacheManagerTests.cs | 35 ++ .../Caches/FrozenCacheStateCollection.cs | 6 +- .../Caches/InMemoryCacheManagerTests.cs | 101 +++++ .../Factories/CaeriusNetDbContextTests.cs | 22 ++ .../Helpers/ResultSetMaterializerTests.cs | 41 +++ .../Mappers/StoredProcedureParametersTests.cs | 30 +- .../CaeriusNetTransactionTests.cs | 146 ++++++++ .../CaeriusNet.SqlServer.Contracts.csproj | 4 +- .../CaeriusNet.SqlServer.Contracts/Program.cs | 162 +++++++-- .../SqlServerContractDiscoverer.cs | 65 ++-- .../CaeriusNet.SqlServer.Contracts.props | 7 +- .../CaeriusNet.SqlServer.Contracts.targets | 51 ++- 64 files changed, 2893 insertions(+), 675 deletions(-) create mode 100644 Analyzer/AutoContracts/AutoContractsCSharpNames.cs create mode 100644 Analyzer/AutoContracts/AutoContractsManifestFile.cs delete mode 100644 Benchmark/Data/Simple/SimpleDto.cs delete mode 100644 Documentations/docs/caeriusnet-logo.webp create mode 100644 SourceGenerators/AutoContracts/AutoContractsManifestFile.cs create mode 100644 Src/Helpers/ResultSetMaterializer.cs create mode 100644 Tests/CaeriusNet.Tests/Helpers/ResultSetMaterializerTests.cs diff --git a/.github/scripts/ValidatePackage.ps1 b/.github/scripts/ValidatePackage.ps1 index fa69bb1..db60cd5 100644 --- a/.github/scripts/ValidatePackage.ps1 +++ b/.github/scripts/ValidatePackage.ps1 @@ -1,93 +1,332 @@ [CmdletBinding()] param( [string]$Configuration = "Release", - [string]$OutputDirectory = ".work\package-validation" + [string]$OutputDirectory = ".work\package-validation", + [string]$PackageDirectory = "" ) $ErrorActionPreference = "Stop" $repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -$outputRoot = if ([System.IO.Path]::IsPathRooted($OutputDirectory)) { - $OutputDirectory + +function Resolve-RepositoryPath { + param([Parameter(Mandatory = $true)][string]$Path) + + if ([System.IO.Path]::IsPathRooted($Path)) { + return [System.IO.Path]::GetFullPath($Path) + } + + return [System.IO.Path]::GetFullPath((Join-Path $repoRoot $Path)) } -else { - Join-Path $repoRoot $OutputDirectory + +function Normalize-DirectoryPath { + param([Parameter(Mandatory = $true)][string]$Path) + + return ([System.IO.Path]::GetFullPath($Path)).TrimEnd( + [System.IO.Path]::DirectorySeparatorChar, + [System.IO.Path]::AltDirectorySeparatorChar) } -$packageDirectory = Join-Path $outputRoot "packages" -$consumerDirectory = Join-Path $outputRoot "consumer" +function Assert-SafeOutputDirectory { + param([Parameter(Mandatory = $true)][string]$Path) -Push-Location $repoRoot -try { - if (Test-Path $outputRoot) { - Remove-Item $outputRoot -Recurse -Force + $normalized = Normalize-DirectoryPath $Path + $root = (Normalize-DirectoryPath ([System.IO.Path]::GetPathRoot($normalized))) + $repo = Normalize-DirectoryPath $repoRoot + + if ([string]::IsNullOrWhiteSpace($normalized) -or $normalized -eq $root -or $normalized -eq $repo) { + throw "Refusing to clear unsafe output directory '$Path'." } +} - New-Item -ItemType Directory -Path $packageDirectory -Force | Out-Null +function Invoke-DotNet { + param( + [Parameter(Mandatory = $true)][string[]]$Arguments, + [Parameter(Mandatory = $true)][string]$FailureMessage + ) - dotnet build "Src\CaeriusNet.csproj" --configuration $Configuration --no-incremental + & dotnet @Arguments if ($LASTEXITCODE -ne 0) { - throw "dotnet build failed with exit code $LASTEXITCODE." + throw "$FailureMessage Exit code: $LASTEXITCODE." } +} - dotnet pack "Src\CaeriusNet.csproj" --configuration $Configuration --no-build --output $packageDirectory - if ($LASTEXITCODE -ne 0) { - throw "dotnet pack failed with exit code $LASTEXITCODE." +function Get-RequiredPackage { + param( + [Parameter(Mandatory = $true)][string]$Directory, + [Parameter(Mandatory = $true)][string]$Pattern, + [Parameter(Mandatory = $true)][scriptblock]$Filter, + [Parameter(Mandatory = $true)][string]$Description + ) + + $packages = @(Get-ChildItem $Directory -Filter $Pattern -File | + Where-Object $Filter | + Sort-Object LastWriteTime -Descending) + + if ($packages.Count -eq 0) { + throw "No $Description was found in '$Directory'." } - $package = Get-ChildItem $packageDirectory -Filter "CaeriusNet.*.nupkg" | - Where-Object { $_.Name -notlike "*.symbols.nupkg" } | - Sort-Object LastWriteTime -Descending | - Select-Object -First 1 + if ($packages.Count -gt 1) { + Write-Warning "Multiple $Description files were found in '$Directory'. Validating newest: $($packages[0].Name)." + } + + return $packages[0] +} - if (-not $package) { - throw "No CaeriusNet .nupkg was produced in '$packageDirectory'." +function Get-PackageVersion { + param( + [Parameter(Mandatory = $true)][System.IO.FileInfo]$Package, + [Parameter(Mandatory = $true)][string]$PackageId + ) + + $escapedId = [regex]::Escape($PackageId) + $match = [regex]::Match($Package.Name, "^$escapedId\.(?.+)\.nupkg$") + if (-not $match.Success -or [string]::IsNullOrWhiteSpace($match.Groups["version"].Value)) { + throw "Could not infer package version from '$($Package.Name)'." } - Add-Type -AssemblyName System.IO.Compression.FileSystem - $archive = [System.IO.Compression.ZipFile]::OpenRead($package.FullName) + return $match.Groups["version"].Value +} + +function Assert-PackageEntries { + param( + [Parameter(Mandatory = $true)][System.IO.FileInfo]$Package, + [Parameter(Mandatory = $true)][string[]]$RequiredEntries + ) + + $archive = [System.IO.Compression.ZipFile]::OpenRead($Package.FullName) try { $entries = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($entry in $archive.Entries) { + if ($entry.FullName.Contains('\')) { + throw "Package '$($Package.Name)' contains a non-portable ZIP entry '$($entry.FullName)'." + } + [void]$entries.Add($entry.FullName) } - $requiredEntries = @( - "lib/net10.0/CaeriusNet.dll", - "lib/net10.0/CaeriusNet.xml", - "analyzers/dotnet/cs/CaeriusNet.Generator.dll", - "analyzers/dotnet/cs/CaeriusNet.Analyzer.dll" - ) - - foreach ($requiredEntry in $requiredEntries) { + foreach ($requiredEntry in $RequiredEntries) { if (-not $entries.Contains($requiredEntry)) { - throw "Package '$($package.Name)' is missing required entry '$requiredEntry'." + throw "Package '$($Package.Name)' is missing required entry '$requiredEntry'." } } } finally { $archive.Dispose() } +} - $version = [regex]::Match($package.Name, "^CaeriusNet\.(?.+)\.nupkg$").Groups["version"].Value - if ([string]::IsNullOrWhiteSpace($version)) { - throw "Could not infer package version from '$($package.Name)'." +function Get-PackageEntryText { + param( + [Parameter(Mandatory = $true)][System.IO.FileInfo]$Package, + [Parameter(Mandatory = $true)][string]$EntryName + ) + + $archive = [System.IO.Compression.ZipFile]::OpenRead($Package.FullName) + try { + $entry = $archive.GetEntry($EntryName) + if (-not $entry) { + throw "Package '$($Package.Name)' is missing required entry '$EntryName'." + } + + $stream = $entry.Open() + $reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::UTF8, $true) + try { + return $reader.ReadToEnd() + } + finally { + $reader.Dispose() + $stream.Dispose() + } } + finally { + $archive.Dispose() + } +} - $globalPackagesLine = dotnet nuget locals global-packages --list - if ($LASTEXITCODE -ne 0) { - throw "Could not locate NuGet global packages cache." +function Assert-PackageTextContains { + param( + [Parameter(Mandatory = $true)][System.IO.FileInfo]$Package, + [Parameter(Mandatory = $true)][string]$EntryName, + [Parameter(Mandatory = $true)][string[]]$ExpectedText + ) + + $text = Get-PackageEntryText -Package $Package -EntryName $EntryName + foreach ($expected in $ExpectedText) { + if (-not $text.Contains($expected)) { + throw "Package '$($Package.Name)' entry '$EntryName' does not contain expected text '$expected'." + } } +} - $globalPackagesRoot = (($globalPackagesLine | Select-Object -First 1) -replace '^global-packages:\s*', '').Trim() - $cachedPackageDirectory = Join-Path $globalPackagesRoot (Join-Path "caeriusnet" $version) - if (Test-Path $cachedPackageDirectory) { - Remove-Item $cachedPackageDirectory -Recurse -Force +function Assert-PackageTextDoesNotContain { + param( + [Parameter(Mandatory = $true)][System.IO.FileInfo]$Package, + [Parameter(Mandatory = $true)][string]$EntryName, + [Parameter(Mandatory = $true)][string[]]$DisallowedText + ) + + $text = Get-PackageEntryText -Package $Package -EntryName $EntryName + foreach ($disallowed in $DisallowedText) { + if ($text.Contains($disallowed)) { + throw "Package '$($Package.Name)' entry '$EntryName' contains disallowed text '$disallowed'." + } } +} + +function Write-PackageHashes { + param([Parameter(Mandatory = $true)][string]$Directory) + + $packages = @(Get-ChildItem $Directory -File | + Where-Object { $_.Extension -in ".nupkg", ".snupkg" } | + Sort-Object Name) + + if ($packages.Count -eq 0) { + return + } + + Write-Host "Package SHA256 hashes:" + foreach ($package in $packages) { + $hash = Get-FileHash -Algorithm SHA256 -Path $package.FullName + Write-Host (" {0} {1}" -f $hash.Hash.ToLowerInvariant(), $package.Name) + } +} + +function Validate-CaeriusNetPackage { + param([Parameter(Mandatory = $true)][System.IO.FileInfo]$Package) + + Assert-PackageEntries -Package $Package -RequiredEntries @( + "CaeriusNet.nuspec", + "README.md", + "LICENSE", + "lib/net10.0/CaeriusNet.dll", + "lib/net10.0/CaeriusNet.xml", + "analyzers/dotnet/cs/CaeriusNet.Generator.dll", + "analyzers/dotnet/cs/CaeriusNet.Analyzer.dll" + ) + + Assert-PackageTextContains -Package $Package -EntryName "CaeriusNet.nuspec" -ExpectedText @( + "CaeriusNet", + "MIT", + "README.md", + "CaeriusNet.SqlServer.Contracts", + "MIT", + "README.md", + " @@ -233,13 +472,18 @@ internal static class Program } "@ | Set-Content -Path (Join-Path $consumerDirectory "Program.cs") -Encoding UTF8 - dotnet run --project (Join-Path $consumerDirectory "PackageSmoke.Consumer.csproj") --configuration Release - if ($LASTEXITCODE -ne 0) { - throw "Package smoke consumer failed with exit code $LASTEXITCODE." - } + Invoke-DotNet -Arguments @("run", "--project", (Join-Path $consumerDirectory "PackageSmoke.Consumer.csproj"), "--configuration", "Release") ` + -FailureMessage "Package smoke consumer failed." - Write-Host "Package validation succeeded for $($package.Name)." + Write-Host "Package validation succeeded for $($mainPackage.Name) and $($contractsPackage.Name)." } finally { + if ($null -eq $previousNuGetPackages) { + Remove-Item Env:NUGET_PACKAGES -ErrorAction SilentlyContinue + } + else { + $env:NUGET_PACKAGES = $previousNuGetPackages + } + Pop-Location } diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d25fac0..e6b307a 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -34,7 +34,6 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_NOLOGO: true - DOTNET_VERSION: '10.0.x' FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' SA_PASSWORD: 'BenchmarkP@ss2026!' @@ -70,16 +69,19 @@ jobs: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - - name: Setup .NET ${{ env.DOTNET_VERSION }} + - name: Setup .NET from global.json uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ env.DOTNET_VERSION }} + global-json-file: global.json + + - name: Display .NET SDK info + run: dotnet --info - name: Cache NuGet packages uses: actions/cache@v5 with: path: ~/.nuget/packages - key: nuget-bench-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.slnx') }} + key: nuget-bench-${{ runner.os }}-${{ hashFiles('global.json', '**/*.csproj', '**/*.slnx') }} restore-keys: nuget-bench-${{ runner.os }}- - name: Restore dependencies @@ -205,7 +207,7 @@ jobs: if: always() uses: actions/upload-artifact@v5 with: - name: benchmark-reports-${{ github.run_number }} + name: "benchmark-reports-${{ github.run_number }}" path: | ${{ github.workspace }}/BenchmarkDotNet.Artifacts/results/*-report-github.md ${{ github.workspace }}/BenchmarkDotNet.Artifacts/results/*-report-full.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd12fac..9ce59e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,6 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_NOLOGO: true - DOTNET_VERSION: '10.0.x' FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' jobs: @@ -37,16 +36,19 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup .NET ${{ env.DOTNET_VERSION }} + - name: Setup .NET from global.json uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ env.DOTNET_VERSION }} + global-json-file: global.json + + - name: Display .NET SDK info + run: dotnet --info - name: Cache NuGet packages uses: actions/cache@v5 with: path: ~/.nuget/packages - key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.slnx') }} + key: nuget-${{ runner.os }}-${{ hashFiles('global.json', '**/*.csproj', '**/*.slnx') }} restore-keys: nuget-${{ runner.os }}- - name: Restore dependencies @@ -57,8 +59,7 @@ jobs: dotnet build CaeriusNet.slnx \ --configuration Release \ --no-restore \ - -p:TreatWarningsAsErrors=true \ - -p:WarningLevel=4 + -p:TreatWarningsAsErrors=true - name: Validate NuGet package shell: pwsh diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1d72cf1..8c383e1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,6 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_NOLOGO: true - DOTNET_VERSION: '10.0.x' FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' jobs: @@ -47,10 +46,13 @@ jobs: - Exemples/** - Documentations/** - - name: Setup .NET ${{ env.DOTNET_VERSION }} + - name: Setup .NET from global.json uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ env.DOTNET_VERSION }} + global-json-file: global.json + + - name: Display .NET SDK info + run: dotnet --info - name: Restore dependencies run: dotnet restore CaeriusNet.slnx diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 028b2ba..d7a83e3 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -23,7 +23,6 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_NOLOGO: true - DOTNET_VERSION: '10.0.x' # Testcontainers' Ryuk reaper must remain enabled in CI to guarantee container cleanup # if the runner is killed (job timeout, cancellation, OOM). Reuse is not used in CI. TESTCONTAINERS_RYUK_DISABLED: 'false' @@ -38,16 +37,19 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup .NET ${{ env.DOTNET_VERSION }} + - name: Setup .NET from global.json uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ env.DOTNET_VERSION }} + global-json-file: global.json + + - name: Display .NET SDK info + run: dotnet --info - name: Cache NuGet packages uses: actions/cache@v5 with: path: ~/.nuget/packages - key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.slnx') }} + key: nuget-${{ runner.os }}-${{ hashFiles('global.json', '**/*.csproj', '**/*.slnx') }} restore-keys: nuget-${{ runner.os }}- - name: Pre-pull SQL Server image diff --git a/.github/workflows/nuget-dotnet.yml b/.github/workflows/nuget-dotnet.yml index 378b3ea..7d18e1c 100644 --- a/.github/workflows/nuget-dotnet.yml +++ b/.github/workflows/nuget-dotnet.yml @@ -38,11 +38,17 @@ jobs: id: bump run: | VERSION=$(grep -oP '(?<=)\d+\.\d+\.\d+(?=)' Src/CaeriusNet.csproj) + TOOL_VERSION=$(grep -oP '(?<=)\d+\.\d+\.\d+(?=)' Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj) + if [ "$TOOL_VERSION" != "$VERSION" ]; then + echo "❌ Package versions are already out of sync. Src=${VERSION}; Tools=${TOOL_VERSION}." + exit 1 + fi MAJOR=$(echo "$VERSION" | cut -d. -f1) MINOR=$(echo "$VERSION" | cut -d. -f2) PATCH=$(echo "$VERSION" | cut -d. -f3) NEW="${MAJOR}.${MINOR}.$((PATCH+1))" sed -i "s|${VERSION}|${NEW}|g" Src/CaeriusNet.csproj + sed -i "s|${VERSION}|${NEW}|g" Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj echo "new_version=$NEW" >> "$GITHUB_OUTPUT" echo "🔖 Bumped: ${VERSION} → ${NEW}" @@ -50,6 +56,6 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add Src/CaeriusNet.csproj + git add Src/CaeriusNet.csproj Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj git commit -m "chore: bump version to ${{ steps.bump.outputs.new_version }}" git push diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c72b540..200b377 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,6 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_NOLOGO: true - DOTNET_VERSION: '10.0.x' FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' jobs: @@ -56,10 +55,13 @@ jobs: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - - name: Setup .NET ${{ env.DOTNET_VERSION }} + - name: Setup .NET from global.json uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ env.DOTNET_VERSION }} + global-json-file: global.json + + - name: Display .NET SDK info + run: dotnet --info - name: Setup Node if: hashFiles('Documentations/package.json') != '' @@ -73,7 +75,7 @@ jobs: uses: actions/cache@v5 with: path: ~/.nuget/packages - key: nuget-release-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.slnx') }} + key: nuget-release-${{ runner.os }}-${{ hashFiles('global.json', '**/*.csproj', '**/*.slnx') }} restore-keys: nuget-release-${{ runner.os }}- # ── Bump package versions ─────────────────────────────────────────────────── @@ -91,8 +93,15 @@ jobs: patch) NEW="${MAJOR}.${MINOR}.$((PATCH+1))" ;; esac - sed -i "s|${CURRENT}|${NEW}|g" Src/CaeriusNet.csproj - sed -i "s|${CURRENT}|${NEW}|g" Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj + sed -i -E "s|[0-9]+\.[0-9]+\.[0-9]+|${NEW}|" Src/CaeriusNet.csproj + sed -i -E "s|[0-9]+\.[0-9]+\.[0-9]+|${NEW}|" Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj + + SRC_VERSION=$(grep -oP '(?<=)\d+\.\d+\.\d+(?=)' Src/CaeriusNet.csproj) + TOOLS_VERSION=$(grep -oP '(?<=)\d+\.\d+\.\d+(?=)' Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj) + if [ "$SRC_VERSION" != "$NEW" ] || [ "$TOOLS_VERSION" != "$NEW" ]; then + echo "❌ Version bump did not update all published packages. Src=${SRC_VERSION}; Tools=${TOOLS_VERSION}; expected=${NEW}." + exit 1 + fi echo "current=$CURRENT" >> "$GITHUB_OUTPUT" echo "new=$NEW" >> "$GITHUB_OUTPUT" @@ -122,8 +131,7 @@ jobs: dotnet build CaeriusNet.slnx \ --configuration Release \ --no-restore \ - -p:TreatWarningsAsErrors=true \ - -p:WarningLevel=4 + -p:TreatWarningsAsErrors=true - name: Test (non-integration) run: | @@ -176,7 +184,7 @@ jobs: - name: Validate package contents shell: pwsh - run: ./.github/scripts/ValidatePackage.ps1 -Configuration Release -OutputDirectory "${{ runner.temp }}/caerius-package-validation" + run: ./.github/scripts/ValidatePackage.ps1 -Configuration Release -OutputDirectory "${{ runner.temp }}/caerius-package-validation" -PackageDirectory ./packages - name: List packages run: ls -la ./packages/ @@ -219,20 +227,22 @@ jobs: - name: Create GitHub Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_NOTES: ${{ github.event.inputs.release_notes }} run: | + set -euo pipefail TAG="${{ steps.bump.outputs.tag }}" VERSION="${{ steps.bump.outputs.new }}" - NOTES="${{ github.event.inputs.release_notes }}" ASSETS=$(ls ./packages/*.nupkg ./packages/*.snupkg 2>/dev/null | tr '\n' ' ') if gh release view "${TAG}" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then echo "â„šī¸ Release ${TAG} already exists — skipping." else - if [ -n "$NOTES" ]; then + if [ -n "$RELEASE_NOTES" ]; then + printf '%s' "$RELEASE_NOTES" > release-notes.md # shellcheck disable=SC2086 gh release create "${TAG}" \ --title "CaeriusNet ${VERSION}" \ - --notes "${NOTES}" \ + --notes-file release-notes.md \ --repo "$GITHUB_REPOSITORY" \ ${ASSETS} else diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index ed377a8..5b59083 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -19,7 +19,6 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_NOLOGO: true - DOTNET_VERSION: '10.0.x' FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' jobs: @@ -50,57 +49,56 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup .NET ${{ env.DOTNET_VERSION }} + - name: Setup .NET from global.json uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ env.DOTNET_VERSION }} + global-json-file: global.json + + - name: Display .NET SDK info + run: dotnet --info - name: Restore dependencies run: dotnet restore CaeriusNet.slnx - - name: Scan for vulnerable packages (main library) + - name: Scan for vulnerable packages (distributed projects) run: | - echo "=== Scanning Src/CaeriusNet.csproj for vulnerabilities ===" - RESULT=$(dotnet list Src/CaeriusNet.csproj package --vulnerable --include-transitive --format json 2>&1) - echo "$RESULT" - VULN_COUNT=$(echo "$RESULT" | jq '[.projects[].frameworks[] | ((.topLevelPackages // [])[], (.transitivePackages // [])[]) | select((.vulnerabilities // []) | length > 0)] | length' || echo 0) - if [ "$VULN_COUNT" -gt 0 ]; then - echo "::error title=Vulnerable Packages::Vulnerable NuGet packages detected in CaeriusNet! See log for details." - exit 1 - fi - echo "✅ No vulnerable packages found in main library." + set -euo pipefail - - name: Scan for vulnerable packages (source generator) - run: | - echo "=== Scanning SourceGenerators/CaeriusNet.Generator.csproj for vulnerabilities ===" - RESULT=$(dotnet list SourceGenerators/CaeriusNet.Generator.csproj package --vulnerable --include-transitive --format json 2>&1) - echo "$RESULT" - VULN_COUNT=$(echo "$RESULT" | jq '[.projects[].frameworks[] | ((.topLevelPackages // [])[], (.transitivePackages // [])[]) | select((.vulnerabilities // []) | length > 0)] | length' || echo 0) - if [ "$VULN_COUNT" -gt 0 ]; then - echo "::error title=Vulnerable Packages::Vulnerable NuGet packages detected in CaeriusNet.Generator! See log for details." + projects=( + "Src/CaeriusNet.csproj|CaeriusNet" + "Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj|CaeriusNet.SqlServer.Contracts" + "Analyzer/CaeriusNet.Analyzer.csproj|CaeriusNet.Analyzer" + "SourceGenerators/CaeriusNet.Generator.csproj|CaeriusNet.Generator" + ) + + total_vulnerabilities=0 + for item in "${projects[@]}"; do + IFS='|' read -r project label <<< "$item" + echo "=== Scanning ${project} for vulnerabilities ===" + result=$(dotnet list "$project" package --vulnerable --include-transitive --format json 2>&1) + echo "$result" + count=$(echo "$result" | jq '[.projects[].frameworks[] | ((.topLevelPackages // [])[], (.transitivePackages // [])[]) | select((.vulnerabilities // []) | length > 0)] | length') + if [ "$count" -gt 0 ]; then + echo "::error title=Vulnerable Packages::${count} vulnerable NuGet package(s) detected in ${label}. See log for details." + total_vulnerabilities=$((total_vulnerabilities + count)) + else + echo "No vulnerable packages found in ${label}." + fi + done + + if [ "$total_vulnerabilities" -gt 0 ]; then exit 1 fi - echo "✅ No vulnerable packages found in source generator." - - name: Scan for vulnerable packages (test projects, informational) + - name: Scan for vulnerable packages (full solution, informational) run: | - echo "=== Scanning test projects for vulnerabilities (non-blocking) ===" - RESULT_TESTS=$(dotnet list Tests/CaeriusNet.Tests/CaeriusNet.Tests.csproj package --vulnerable --include-transitive --format json 2>&1 || true) - RESULT_GEN=$(dotnet list Tests/CaeriusNet.Generator.Tests/CaeriusNet.Generator.Tests.csproj package --vulnerable --include-transitive --format json 2>&1 || true) - echo "$RESULT_TESTS" - echo "$RESULT_GEN" - VULN_COUNT=$(( \ - $(echo "$RESULT_TESTS" | jq '[.projects[].frameworks[] | ((.topLevelPackages // [])[], (.transitivePackages // [])[]) | select((.vulnerabilities // []) | length > 0)] | length' || echo 0) + \ - $(echo "$RESULT_GEN" | jq '[.projects[].frameworks[] | ((.topLevelPackages // [])[], (.transitivePackages // [])[]) | select((.vulnerabilities // []) | length > 0)] | length' || echo 0) \ - )) - if [ "$VULN_COUNT" -gt 0 ]; then - echo "::warning title=Vulnerable Packages::Vulnerable packages found in test projects (non-blocking)." - else - echo "✅ No vulnerable packages found in test projects." - fi + echo "=== Scanning all solution projects for vulnerabilities (non-blocking) ===" + dotnet list CaeriusNet.slnx package --vulnerable --include-transitive --format json || true - name: Report outdated packages (informational) run: | echo "=== Outdated packages (informational, non-blocking) ===" - dotnet list Src/CaeriusNet.csproj package --outdated || true + dotnet list CaeriusNet.slnx package --outdated || true + echo "=== Deprecated packages (informational, non-blocking) ===" + dotnet list CaeriusNet.slnx package --deprecated --include-transitive || true continue-on-error: true diff --git a/Analyzer/AutoContracts/AutoContractsCSharpNames.cs b/Analyzer/AutoContracts/AutoContractsCSharpNames.cs new file mode 100644 index 0000000..be3bb5a --- /dev/null +++ b/Analyzer/AutoContracts/AutoContractsCSharpNames.cs @@ -0,0 +1,38 @@ +using System.Text; + +namespace CaeriusNet.Analyzer.AutoContracts; + +internal static class AutoContractsCSharpNames +{ + internal static string ToIdentifier(string name) + { + if (string.IsNullOrWhiteSpace(name)) + return "Value"; + + var sb = new StringBuilder(name.Length + 1); + if (!IsIdentifierStart(name[0])) + sb.Append('_'); + + foreach (var ch in name) + sb.Append(IsIdentifierPart(ch) ? ch : '_'); + + var identifier = sb.ToString(); + return IsKeyword(identifier) ? "@" + identifier : identifier; + } + + private static bool IsIdentifierStart(char ch) + { + return ch == '_' || char.IsLetter(ch); + } + + private static bool IsIdentifierPart(char ch) + { + return ch == '_' || char.IsLetterOrDigit(ch); + } + + private static bool IsKeyword(string identifier) + { + return SyntaxFacts.GetKeywordKind(identifier) != SyntaxKind.None || + SyntaxFacts.GetContextualKeywordKind(identifier) != SyntaxKind.None; + } +} diff --git a/Analyzer/AutoContracts/AutoContractsManifestAnalyzer.cs b/Analyzer/AutoContracts/AutoContractsManifestAnalyzer.cs index bc3cc65..5deb5bd 100644 --- a/Analyzer/AutoContracts/AutoContractsManifestAnalyzer.cs +++ b/Analyzer/AutoContracts/AutoContractsManifestAnalyzer.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.IO; +using Microsoft.CodeAnalysis.Text; namespace CaeriusNet.Analyzer.AutoContracts; @@ -9,12 +9,9 @@ namespace CaeriusNet.Analyzer.AutoContracts; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AutoContractsManifestAnalyzer : DiagnosticAnalyzer { - private const string ManifestFileName = "caerius.contracts.json"; - public override ImmutableArray SupportedDiagnostics { get; } = [ DiagnosticDescriptors.ContractManifestMissing, - DiagnosticDescriptors.ContractProcedureMissing, DiagnosticDescriptors.ContractTableTypeMissing, DiagnosticDescriptors.ContractUnsupportedTableTypeSqlType, DiagnosticDescriptors.ContractUndeterminedResultSet, @@ -22,7 +19,6 @@ public sealed class AutoContractsManifestAnalyzer : DiagnosticAnalyzer DiagnosticDescriptors.ContractOutputParameterUnsupported, DiagnosticDescriptors.ContractUnsupportedSqlType, DiagnosticDescriptors.ContractNullableResultColumn, - DiagnosticDescriptors.ContractHashMismatch, DiagnosticDescriptors.ContractPossibleIncompatibility ]; @@ -38,10 +34,9 @@ private static void AnalyzeCompilation(CompilationAnalysisContext context) var manifestFound = false; foreach (var additionalFile in context.Options.AdditionalFiles) { - if (!string.Equals( - Path.GetFileName(additionalFile.Path), - ManifestFileName, - StringComparison.OrdinalIgnoreCase)) + if (!AutoContractsManifestFile.IsManifest( + additionalFile, + context.Options.AnalyzerConfigOptionsProvider)) continue; manifestFound = true; @@ -52,7 +47,7 @@ private static void AnalyzeCompilation(CompilationAnalysisContext context) context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractManifestMissing, Location.None, - ManifestFileName)); + AutoContractsManifestFile.ManifestFileName)); } private static bool RequiresManifest(CompilationAnalysisContext context) @@ -62,6 +57,7 @@ private static bool RequiresManifest(CompilationAnalysisContext context) out var mode)) return false; + mode = mode.Trim(); return mode.Equals("Pull", StringComparison.OrdinalIgnoreCase) || mode.Equals("Verify", StringComparison.OrdinalIgnoreCase); } @@ -70,12 +66,13 @@ private static void AnalyzeManifest(CompilationAnalysisContext context, Addition { var sourceText = manifestFile.GetText(context.CancellationToken); var source = sourceText?.ToString(); + var location = CreateManifestLocation(manifestFile, sourceText); if (string.IsNullOrWhiteSpace(source)) { context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractManifestMissing, - Location.None, - ManifestFileName)); + location, + AutoContractsManifestFile.ManifestFileName)); return; } @@ -88,24 +85,43 @@ private static void AnalyzeManifest(CompilationAnalysisContext context, Addition { context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractPossibleIncompatibility, - Location.None, - ManifestFileName, + location, + manifestFile.Path, ex.Message)); return; } - ValidateManifest(context, manifest); + ValidateManifest(context, manifest, manifestFile.Path, location); } - private static void ValidateManifest(CompilationAnalysisContext context, AutoContractsManifest manifest) + private static void ValidateManifest( + CompilationAnalysisContext context, + AutoContractsManifest manifest, + string manifestPath, + Location location) { + if (manifest.Version != AutoContractsManifestParser.SupportedManifestVersion) + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.ContractPossibleIncompatibility, + location, + manifestPath, + $"Unsupported manifest version '{manifest.Version}'. Expected version '{AutoContractsManifestParser.SupportedManifestVersion}'.")); + + if (!IsValidNamespace(manifest.Namespace)) + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.ContractPossibleIncompatibility, + location, + manifestPath, + $"Manifest namespace '{manifest.Namespace}' is not a valid C# namespace.")); + var tableTypes = BuildTableTypeLookup(manifest); + ValidateGenerationNames(context, manifest, manifestPath, location); foreach (var tableType in manifest.TableTypes.Items) - ValidateTableType(context, tableType); + ValidateTableType(context, tableType, location); foreach (var procedure in manifest.Procedures.Items) - ValidateProcedure(context, procedure, tableTypes); + ValidateProcedure(context, procedure, tableTypes, location); } private static HashSet BuildTableTypeLookup(AutoContractsManifest manifest) @@ -117,7 +133,10 @@ private static HashSet BuildTableTypeLookup(AutoContractsManifest manife return tableTypes; } - private static void ValidateTableType(CompilationAnalysisContext context, AutoContractsTableType tableType) + private static void ValidateTableType( + CompilationAnalysisContext context, + AutoContractsTableType tableType, + Location location) { foreach (var column in tableType.Columns.Items) { @@ -126,7 +145,7 @@ private static void ValidateTableType(CompilationAnalysisContext context, AutoCo context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractUnsupportedTableTypeSqlType, - Location.None, + location, BuildFullName(tableType.Schema, tableType.Name, column.Name), column.SqlType)); } @@ -135,27 +154,29 @@ private static void ValidateTableType(CompilationAnalysisContext context, AutoCo private static void ValidateProcedure( CompilationAnalysisContext context, AutoContractsProcedure procedure, - HashSet tableTypes) + HashSet tableTypes, + Location location) { foreach (var parameter in procedure.Parameters.Items) - ValidateParameter(context, procedure, parameter, tableTypes); + ValidateParameter(context, procedure, parameter, tableTypes, location); foreach (var column in procedure.ResultSet.Columns.Items) - ValidateResultColumn(context, procedure, column); + ValidateResultColumn(context, procedure, column, location); - ValidateResultSetStatus(context, procedure); + ValidateResultSetStatus(context, procedure, location); } private static void ValidateParameter( CompilationAnalysisContext context, AutoContractsProcedure procedure, AutoContractsParameter parameter, - HashSet tableTypes) + HashSet tableTypes, + Location location) { if (parameter.IsOutput) context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractOutputParameterUnsupported, - Location.None, + location, BuildFullName(procedure.Schema, procedure.Name), parameter.Name)); @@ -164,7 +185,7 @@ private static void ValidateParameter( if (!tableTypes.Contains(parameter.SqlType)) context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractTableTypeMissing, - Location.None, + location, parameter.SqlType)); return; @@ -173,35 +194,39 @@ private static void ValidateParameter( if (!AutoContractsSqlTypes.IsSupported(parameter.SqlType)) context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractUnsupportedSqlType, - Location.None, + location, parameter.SqlType)); } private static void ValidateResultColumn( CompilationAnalysisContext context, AutoContractsProcedure procedure, - AutoContractsColumn column) + AutoContractsColumn column, + Location location) { if (!AutoContractsSqlTypes.IsSupported(column.SqlType)) context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractUnsupportedSqlType, - Location.None, + location, column.SqlType)); if (column.Nullable) context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractNullableResultColumn, - Location.None, + location, BuildFullName(procedure.Schema, procedure.Name, column.Name))); } - private static void ValidateResultSetStatus(CompilationAnalysisContext context, AutoContractsProcedure procedure) + private static void ValidateResultSetStatus( + CompilationAnalysisContext context, + AutoContractsProcedure procedure, + Location location) { if (string.Equals(procedure.ResultSet.Status, "Undetermined", StringComparison.OrdinalIgnoreCase)) { context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractUndeterminedResultSet, - Location.None, + location, BuildFullName(procedure.Schema, procedure.Name))); return; } @@ -209,10 +234,145 @@ private static void ValidateResultSetStatus(CompilationAnalysisContext context, if (string.Equals(procedure.ResultSet.Status, "None", StringComparison.OrdinalIgnoreCase)) context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ContractNoResultSet, - Location.None, + location, BuildFullName(procedure.Schema, procedure.Name))); } + private static void ValidateGenerationNames( + CompilationAnalysisContext context, + AutoContractsManifest manifest, + string manifestPath, + Location location) + { + var generatedTypes = new HashSet(StringComparer.Ordinal); + var tableSqlNames = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var tableType in manifest.TableTypes.Items) + { + ReportIfInvalidIdentifier(context, manifestPath, location, tableType.ClrName, "table type CLR name"); + ReportIfDuplicate(context, manifestPath, location, tableSqlNames, BuildFullName(tableType.Schema, tableType.Name)); + ReportIfDuplicate(context, manifestPath, location, generatedTypes, tableType.ClrName); + ValidateUniqueMembers(context, manifestPath, location, tableType.ClrName, tableType.Columns.Items); + } + + var procedureSqlNames = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var procedure in manifest.Procedures.Items) + { + ReportIfInvalidIdentifier(context, manifestPath, location, procedure.ClrName, "procedure CLR name"); + ReportIfInvalidIdentifier(context, manifestPath, location, procedure.ParametersClrName, "parameters CLR name"); + ReportIfDuplicate(context, manifestPath, location, procedureSqlNames, BuildFullName(procedure.Schema, procedure.Name)); + ReportIfDuplicate(context, manifestPath, location, generatedTypes, procedure.ClrName); + ReportIfDuplicate(context, manifestPath, location, generatedTypes, procedure.ParametersClrName); + ValidateUniqueMembers(context, manifestPath, location, procedure.ParametersClrName, procedure.Parameters.Items); + + if (procedure.ResultClrName is { Length: > 0 } resultClrName) + { + ReportIfInvalidIdentifier(context, manifestPath, location, resultClrName, "result CLR name"); + ReportIfDuplicate(context, manifestPath, location, generatedTypes, resultClrName); + ValidateUniqueMembers(context, manifestPath, location, resultClrName, procedure.ResultSet.Columns.Items); + } + } + } + + private static void ValidateUniqueMembers( + CompilationAnalysisContext context, + string manifestPath, + Location location, + string containerName, + ImmutableArray columns) + { + var identifiers = new HashSet(StringComparer.Ordinal); + foreach (var column in columns) + ReportIfDuplicate(context, manifestPath, location, identifiers, NormalizeIdentifier(column.Name), containerName); + } + + private static void ValidateUniqueMembers( + CompilationAnalysisContext context, + string manifestPath, + Location location, + string containerName, + ImmutableArray parameters) + { + var identifiers = new HashSet(StringComparer.Ordinal); + foreach (var parameter in parameters) + ReportIfDuplicate(context, manifestPath, location, identifiers, NormalizeIdentifier(parameter.Name), containerName); + } + + private static void ReportIfInvalidIdentifier( + CompilationAnalysisContext context, + string manifestPath, + Location location, + string identifier, + string label) + { + if (IsValidIdentifier(identifier)) + return; + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.ContractPossibleIncompatibility, + location, + manifestPath, + $"Manifest {label} '{identifier}' is not a valid C# identifier.")); + } + + private static void ReportIfDuplicate( + CompilationAnalysisContext context, + string manifestPath, + Location location, + HashSet values, + string value, + string? containerName = null) + { + if (values.Add(value)) + return; + + var message = containerName is null + ? $"Manifest contains duplicate generated name or SQL name '{value}'." + : $"Manifest members in '{containerName}' collide after C# identifier normalization as '{value}'."; + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.ContractPossibleIncompatibility, + location, + manifestPath, + message)); + } + + private static bool IsValidNamespace(string @namespace) + { + if (string.IsNullOrWhiteSpace(@namespace)) + return false; + + var parts = @namespace.Split('.'); + foreach (var part in parts) + if (!IsValidIdentifier(part)) + return false; + + return true; + } + + private static bool IsValidIdentifier(string identifier) + { + return SyntaxFacts.IsValidIdentifier(identifier) && + SyntaxFacts.GetKeywordKind(identifier) == SyntaxKind.None && + SyntaxFacts.GetContextualKeywordKind(identifier) == SyntaxKind.None; + } + + private static string NormalizeIdentifier(string name) + { + return AutoContractsCSharpNames.ToIdentifier(name).TrimStart('@'); + } + + private static Location CreateManifestLocation(AdditionalText manifestFile, SourceText? sourceText) + { + if (sourceText is null) + return Location.None; + + var span = new TextSpan(0, sourceText.Length); + return Location.Create( + manifestFile.Path, + span, + sourceText.Lines.GetLinePositionSpan(span)); + } + private static string BuildFullName(string schema, string name) { return schema + "." + name; diff --git a/Analyzer/AutoContracts/AutoContractsManifestFile.cs b/Analyzer/AutoContracts/AutoContractsManifestFile.cs new file mode 100644 index 0000000..73805c8 --- /dev/null +++ b/Analyzer/AutoContracts/AutoContractsManifestFile.cs @@ -0,0 +1,21 @@ +using System.IO; + +namespace CaeriusNet.Analyzer.AutoContracts; + +internal static class AutoContractsManifestFile +{ + internal const string ManifestFileName = "caerius.contracts.json"; + private const string ManifestMetadataName = "build_metadata.AdditionalFiles.CaeriusContractManifest"; + + internal static bool IsManifest(AdditionalText additionalFile, AnalyzerConfigOptionsProvider optionsProvider) + { + var options = optionsProvider.GetOptions(additionalFile); + if (options.TryGetValue(ManifestMetadataName, out var metadataValue)) + return string.Equals(metadataValue, "true", StringComparison.OrdinalIgnoreCase); + + return string.Equals( + Path.GetFileName(additionalFile.Path), + ManifestFileName, + StringComparison.OrdinalIgnoreCase); + } +} diff --git a/Analyzer/AutoContracts/AutoContractsManifestModels.cs b/Analyzer/AutoContracts/AutoContractsManifestModels.cs index 9a1441f..02caf2f 100644 --- a/Analyzer/AutoContracts/AutoContractsManifestModels.cs +++ b/Analyzer/AutoContracts/AutoContractsManifestModels.cs @@ -3,34 +3,51 @@ namespace CaeriusNet.Analyzer.AutoContracts; internal sealed record AutoContractsManifest( + int Version, + string Namespace, EquatableArray TableTypes, EquatableArray Procedures); internal sealed record AutoContractsTableType( string Schema, string Name, + string ClrName, EquatableArray Columns); internal sealed record AutoContractsProcedure( string Schema, string Name, + string ClrName, + string ParametersClrName, + string? ResultClrName, EquatableArray Parameters, AutoContractsResultSet ResultSet); internal sealed record AutoContractsParameter( + int Ordinal, string Name, string SqlType, + string ClrType, bool IsTableType, - bool IsOutput); + bool IsOutput, + bool Nullable, + int? MaxLength, + byte? Precision, + byte? Scale); internal sealed record AutoContractsResultSet( string Status, EquatableArray Columns); internal sealed record AutoContractsColumn( + int Ordinal, string Name, string SqlType, - bool Nullable); + string ClrType, + bool Nullable, + int? MaxLength, + byte? Precision, + byte? Scale); internal readonly struct EquatableArray(ImmutableArray items) : IEquatable> where T : IEquatable diff --git a/Analyzer/AutoContracts/AutoContractsManifestParser.cs b/Analyzer/AutoContracts/AutoContractsManifestParser.cs index 83ebd06..c9187d3 100644 --- a/Analyzer/AutoContracts/AutoContractsManifestParser.cs +++ b/Analyzer/AutoContracts/AutoContractsManifestParser.cs @@ -6,12 +6,16 @@ namespace CaeriusNet.Analyzer.AutoContracts; internal static class AutoContractsManifestParser { + internal const int SupportedManifestVersion = 1; + internal static AutoContractsManifest Parse(string source) { var root = JsonParser.Parse(source).AsObject(); return new AutoContractsManifest( - ParseTableTypes(root.GetArray("tableTypes")), - ParseProcedures(root.GetArray("procedures"))); + root.GetRequiredInt32("version"), + root.GetRequiredString("namespace"), + ParseTableTypes(root.GetRequiredArray("tableTypes")), + ParseProcedures(root.GetRequiredArray("procedures"))); } private static EquatableArray ParseTableTypes(IReadOnlyList values) @@ -23,7 +27,8 @@ private static EquatableArray ParseTableTypes(IReadOnlyL tableTypes.Add(new AutoContractsTableType( obj.GetRequiredString("schema"), obj.GetRequiredString("name"), - ParseColumns(obj.GetArray("columns")))); + obj.GetRequiredString("clrName"), + ParseColumns(obj.GetRequiredArray("columns")))); } return new EquatableArray(tableTypes.MoveToImmutable()); @@ -38,7 +43,10 @@ private static EquatableArray ParseProcedures(IReadOnlyL procedures.Add(new AutoContractsProcedure( obj.GetRequiredString("schema"), obj.GetRequiredString("name"), - ParseParameters(obj.GetArray("parameters")), + obj.GetRequiredString("clrName"), + obj.GetRequiredString("parametersClrName"), + obj.GetString("resultClrName", null), + ParseParameters(obj.GetRequiredArray("parameters")), obj.TryGetObject("resultSet", out var resultSet) ? ParseResultSet(resultSet) : new AutoContractsResultSet("None", EquatableArray.Empty))); @@ -51,7 +59,7 @@ private static AutoContractsResultSet ParseResultSet(JsonObject value) { return new AutoContractsResultSet( value.GetString("status", "None"), - ParseColumns(value.GetArray("columns"))); + ParseColumns(value.GetRequiredArray("columns"))); } private static EquatableArray ParseParameters(IReadOnlyList values) @@ -61,10 +69,16 @@ private static EquatableArray ParseParameters(IReadOnlyL { var obj = value.AsObject(); parameters.Add(new AutoContractsParameter( + obj.GetInt32("ordinal", parameters.Count + 1), obj.GetRequiredString("name"), obj.GetRequiredString("sqlType"), + obj.GetRequiredString("clrType"), obj.GetBoolean("isTableType", false), - obj.GetBoolean("isOutput", false))); + obj.GetBoolean("isOutput", false), + obj.GetBoolean("nullable", false), + obj.GetNullableInt32("maxLength"), + obj.GetNullableByte("precision"), + obj.GetNullableByte("scale"))); } return new EquatableArray(parameters.MoveToImmutable()); @@ -77,9 +91,14 @@ private static EquatableArray ParseColumns(IReadOnlyList(columns.MoveToImmutable()); @@ -341,6 +360,26 @@ internal string AsString() ?? throw new FormatException("Expected JSON string."); } + internal int AsInt32() + { + return _value is double value && + value >= int.MinValue && + value <= int.MaxValue && + Math.Truncate(value) == value + ? (int)value + : throw new FormatException("Expected JSON integer."); + } + + internal byte AsByte() + { + return _value is double value && + value >= byte.MinValue && + value <= byte.MaxValue && + Math.Truncate(value) == value + ? (byte)value + : throw new FormatException("Expected JSON byte."); + } + internal bool AsBoolean() { return _value is bool value @@ -352,19 +391,51 @@ internal bool AsBoolean() private sealed class JsonObject(Dictionary values) { internal string GetRequiredString(string name) + { + if (!values.TryGetValue(name, out var value) || value.IsNull) + throw new FormatException($"Required manifest property '{name}' is missing."); + + var text = value.AsString(); + return string.IsNullOrWhiteSpace(text) + ? throw new FormatException($"Required manifest property '{name}' is empty.") + : text; + } + + internal string GetString(string name, string? defaultValue) { return values.TryGetValue(name, out var value) && !value.IsNull ? value.AsString() + : defaultValue ?? string.Empty; + } + + internal int GetRequiredInt32(string name) + { + return values.TryGetValue(name, out var value) && !value.IsNull + ? value.AsInt32() : throw new FormatException($"Required manifest property '{name}' is missing."); } - internal string GetString(string name, string defaultValue) + internal int GetInt32(string name, int defaultValue) { return values.TryGetValue(name, out var value) && !value.IsNull - ? value.AsString() + ? value.AsInt32() : defaultValue; } + internal int? GetNullableInt32(string name) + { + return values.TryGetValue(name, out var value) && !value.IsNull + ? value.AsInt32() + : null; + } + + internal byte? GetNullableByte(string name) + { + return values.TryGetValue(name, out var value) && !value.IsNull + ? value.AsByte() + : null; + } + internal bool GetBoolean(string name, bool defaultValue) { return values.TryGetValue(name, out var value) && !value.IsNull @@ -379,6 +450,13 @@ internal IReadOnlyList GetArray(string name) : []; } + internal IReadOnlyList GetRequiredArray(string name) + { + return values.TryGetValue(name, out var value) && !value.IsNull + ? value.AsArray() + : throw new FormatException($"Required manifest property '{name}' is missing."); + } + internal bool TryGetObject(string name, out JsonObject obj) { if (values.TryGetValue(name, out var value) && !value.IsNull) diff --git a/Benchmark/Data/Simple/SimpleDto.cs b/Benchmark/Data/Simple/SimpleDto.cs deleted file mode 100644 index ab7d9c6..0000000 --- a/Benchmark/Data/Simple/SimpleDto.cs +++ /dev/null @@ -1,20 +0,0 @@ -īģŋnamespace CaeriusNet.Benchmark.Data.Simple; - -/// Minimal DTO used for collection read/create benchmarks (no source generator). -public sealed class SimpleDto -{ - public SimpleDto(int id, Guid guid, string name) - { - Id = id; - Guid = guid; - Name = name; - } - - public SimpleDto() - { - } - - public int Id { get; set; } - public Guid Guid { get; set; } - public string Name { get; set; } = null!; -} diff --git a/Benchmark/Workshops/Benchs/Mapping/DtoMappingBench.cs b/Benchmark/Workshops/Benchs/Mapping/DtoMappingBench.cs index 35cc9c9..2fba5f8 100644 --- a/Benchmark/Workshops/Benchs/Mapping/DtoMappingBench.cs +++ b/Benchmark/Workshops/Benchs/Mapping/DtoMappingBench.cs @@ -12,14 +12,6 @@ namespace CaeriusNet.Benchmark.Workshops.Benchs.Mapping; [HardwareCounters(HardwareCounter.BranchMispredictions, HardwareCounter.CacheMisses)] public class DtoMappingBench { - private static readonly Faker Faker = new Faker() - .CustomInstantiator(f => new BenchmarkItemDto( - f.Random.Int(1, 100_000), - f.Random.Guid(), - f.Internet.UserName(), - Math.Round((decimal)f.Random.Double(0.01, 9999.99), 2), - f.Random.Bool())); - private int[] _ids = null!; private bool[] _isActives = null!; private string[] _names = null!; diff --git a/Documentations/docs/caeriusnet-logo.webp b/Documentations/docs/caeriusnet-logo.webp deleted file mode 100644 index a41d8e739268871e731616bb90f575cd56d954a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 606132 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

00~8bj=I2lNp4Je|NH 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⋙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 diff --git a/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Abstractions/IUsersRepository.cs b/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Abstractions/IUsersRepository.cs index 9fbbcee..317d3e4 100644 --- a/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Abstractions/IUsersRepository.cs +++ b/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Abstractions/IUsersRepository.cs @@ -10,10 +10,6 @@ public interface IUsersRepository Task> GetUsersByTvpIntGuid(CancellationToken cancellationToken = default); Task> GetUsersByTvpInt(CancellationToken cancellationToken = default); Task> GetUsersByTvpGuid(CancellationToken cancellationToken = default); - - // Writes -------------------------------------------------------------- - Task CreateNewUser(CancellationToken cancellationToken = default); - // Multi-result sets --------------------------------------------------- Task GetDashboardAsync(CancellationToken cancellationToken = default); diff --git a/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Repositories/UsersRepository.Reads.cs b/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Repositories/UsersRepository.Reads.cs index 713eba7..ec64b86 100644 --- a/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Repositories/UsersRepository.Reads.cs +++ b/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Repositories/UsersRepository.Reads.cs @@ -49,15 +49,4 @@ public async Task> GetAllUsersWithRedisCache(CancellationTo return await dbContext.QueryAsIEnumerableAsync(sp, cancellationToken); } - - // ─── Write ─────────────────────────────────────────────────────────────── - - public async Task CreateNewUser(CancellationToken cancellationToken = default) - { - var sp = new StoredProcedureParametersBuilder("Users", "usp_Create_User") - .AddParameter("UserName", $"demo-{Guid.NewGuid():N}"[..32], SqlDbType.NVarChar) - .Build(); - - await dbContext.ExecuteAsync(sp, cancellationToken); - } } diff --git a/SourceGenerators/AutoContracts/AutoContractsCacheKeyEmitter.cs b/SourceGenerators/AutoContracts/AutoContractsCacheKeyEmitter.cs index b387b16..4c86f66 100644 --- a/SourceGenerators/AutoContracts/AutoContractsCacheKeyEmitter.cs +++ b/SourceGenerators/AutoContracts/AutoContractsCacheKeyEmitter.cs @@ -54,6 +54,9 @@ private static void AppendParameter( private static void AppendTvpValue(StringBuilder sb, string property, AutoContractsTableType tableType) { + sb.Append(" AppendString(hash, ") + .Append(AutoContractsSqlEmitter.ToStringLiteral(tableType.ContractHash)) + .AppendLine(");"); sb.Append(" AppendInt32(hash, ") .Append(property) .AppendLine(".Length);"); diff --git a/SourceGenerators/AutoContracts/AutoContractsEmissionGuard.cs b/SourceGenerators/AutoContracts/AutoContractsEmissionGuard.cs index 680c7e3..5886861 100644 --- a/SourceGenerators/AutoContracts/AutoContractsEmissionGuard.cs +++ b/SourceGenerators/AutoContracts/AutoContractsEmissionGuard.cs @@ -7,6 +7,10 @@ internal static bool CanEmit(AutoContractsManifest manifest) var valid = true; var tableTypes = BuildTableTypeLookup(manifest); + valid &= manifest.Version == AutoContractsManifestParser.SupportedManifestVersion; + valid &= IsValidNamespace(manifest.Namespace); + valid &= HasNoGenerationNameCollisions(manifest); + foreach (var tableType in manifest.TableTypes) valid &= CanEmitTableType(tableType); @@ -28,6 +32,7 @@ private static HashSet BuildTableTypeLookup(AutoContractsManifest manife private static bool CanEmitTableType(AutoContractsTableType tableType) { var valid = true; + valid &= IsValidIdentifier(tableType.ClrName); foreach (var column in tableType.Columns) { if (AutoContractsSqlEmitter.IsSupportedSqlType(column.SqlType)) @@ -44,6 +49,10 @@ private static bool CanEmitProcedure( HashSet tableTypes) { var valid = true; + valid &= IsValidIdentifier(procedure.ClrName); + valid &= IsValidIdentifier(procedure.ParametersClrName); + valid &= procedure.ResultClrName is not { Length: > 0 } resultClrName || + IsValidIdentifier(resultClrName); foreach (var parameter in procedure.Parameters) valid &= CanEmitParameter(parameter, tableTypes); @@ -72,4 +81,79 @@ private static string BuildFullName(string schema, string name) { return schema + "." + name; } + + private static bool HasNoGenerationNameCollisions(AutoContractsManifest manifest) + { + var valid = true; + var generatedTypes = new HashSet(StringComparer.Ordinal); + var tableSqlNames = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var tableType in manifest.TableTypes) + { + valid &= tableSqlNames.Add(BuildFullName(tableType.Schema, tableType.Name)); + valid &= generatedTypes.Add(tableType.ClrName); + valid &= HasNoMemberCollisions(tableType.Columns); + } + + var procedureSqlNames = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var procedure in manifest.Procedures) + { + valid &= procedureSqlNames.Add(BuildFullName(procedure.Schema, procedure.Name)); + valid &= generatedTypes.Add(procedure.ClrName); + valid &= generatedTypes.Add(procedure.ParametersClrName); + valid &= HasNoMemberCollisions(procedure.Parameters); + + if (procedure.ResultClrName is { Length: > 0 } resultClrName) + { + valid &= generatedTypes.Add(resultClrName); + valid &= HasNoMemberCollisions(procedure.ResultSet.Columns); + } + } + + return valid; + } + + private static bool HasNoMemberCollisions(EquatableArray columns) + { + var identifiers = new HashSet(StringComparer.Ordinal); + foreach (var column in columns) + if (!identifiers.Add(NormalizeIdentifier(column.Name))) + return false; + + return true; + } + + private static bool HasNoMemberCollisions(EquatableArray parameters) + { + var identifiers = new HashSet(StringComparer.Ordinal); + foreach (var parameter in parameters) + if (!identifiers.Add(NormalizeIdentifier(parameter.Name))) + return false; + + return true; + } + + private static bool IsValidNamespace(string @namespace) + { + if (string.IsNullOrWhiteSpace(@namespace)) + return false; + + var parts = @namespace.Split('.'); + foreach (var part in parts) + if (!IsValidIdentifier(part)) + return false; + + return true; + } + + private static bool IsValidIdentifier(string identifier) + { + return SyntaxFacts.IsValidIdentifier(identifier) && + SyntaxFacts.GetKeywordKind(identifier) == SyntaxKind.None && + SyntaxFacts.GetContextualKeywordKind(identifier) == SyntaxKind.None; + } + + private static string NormalizeIdentifier(string name) + { + return AutoContractsSqlEmitter.ToIdentifier(name).TrimStart('@'); + } } diff --git a/SourceGenerators/AutoContracts/AutoContractsEmitter.cs b/SourceGenerators/AutoContracts/AutoContractsEmitter.cs index 25ce336..6dc0868 100644 --- a/SourceGenerators/AutoContracts/AutoContractsEmitter.cs +++ b/SourceGenerators/AutoContracts/AutoContractsEmitter.cs @@ -76,7 +76,7 @@ private static void AppendTableType(StringBuilder sb, AutoContractsTableType tab sb.AppendLine(" ];"); sb.AppendLine(); - sb.AppendLine(" public static SqlMetaData[] Metadata => _metadata;"); + sb.AppendLine(" public static SqlMetaData[] Metadata => (SqlMetaData[])_metadata.Clone();"); sb.AppendLine(); sb.AppendLine(" [MethodImpl(MethodImplOptions.AggressiveInlining)]"); diff --git a/SourceGenerators/AutoContracts/AutoContractsManifestFile.cs b/SourceGenerators/AutoContracts/AutoContractsManifestFile.cs new file mode 100644 index 0000000..b545c8b --- /dev/null +++ b/SourceGenerators/AutoContracts/AutoContractsManifestFile.cs @@ -0,0 +1,21 @@ +using System.IO; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CaeriusNet.Generator.AutoContracts; + +internal static class AutoContractsManifestFile +{ + private const string ManifestMetadataName = "build_metadata.AdditionalFiles.CaeriusContractManifest"; + + internal static bool IsManifest(AdditionalText additionalFile, AnalyzerConfigOptionsProvider optionsProvider) + { + var options = optionsProvider.GetOptions(additionalFile); + if (options.TryGetValue(ManifestMetadataName, out var metadataValue)) + return string.Equals(metadataValue, "true", StringComparison.OrdinalIgnoreCase); + + return string.Equals( + Path.GetFileName(additionalFile.Path), + AutoContractsSourceNames.ManifestFileName, + StringComparison.OrdinalIgnoreCase); + } +} diff --git a/SourceGenerators/AutoContracts/AutoContractsManifestParser.cs b/SourceGenerators/AutoContracts/AutoContractsManifestParser.cs index 5cc960f..76855a8 100644 --- a/SourceGenerators/AutoContracts/AutoContractsManifestParser.cs +++ b/SourceGenerators/AutoContracts/AutoContractsManifestParser.cs @@ -4,7 +4,7 @@ namespace CaeriusNet.Generator.AutoContracts; internal static class AutoContractsManifestParser { - private const string DefaultNamespace = "CaeriusNet.Generated"; + internal const int SupportedManifestVersion = 1; internal static AutoContractsManifest? ParseOrDefault(AdditionalText text, CancellationToken cancellationToken) { @@ -16,12 +16,12 @@ internal static class AutoContractsManifestParser { var json = SimpleJsonParser.Parse(source!); var root = json.AsObject(); - var version = root.GetInt32("version", 1); - var ns = root.GetString("namespace", DefaultNamespace); - var tableTypes = ParseTableTypes(root.GetArray("tableTypes")); - var procedures = ParseProcedures(root.GetArray("procedures")); + var version = root.GetRequiredInt32("version"); + var ns = root.GetRequiredString("namespace"); + var tableTypes = ParseTableTypes(root.GetRequiredArray("tableTypes")); + var procedures = ParseProcedures(root.GetRequiredArray("procedures")); - return new AutoContractsManifest(version, ns, tableTypes, procedures); + return new AutoContractsManifest(text.Path, version, ns, tableTypes, procedures); } catch { @@ -39,7 +39,7 @@ private static EquatableArray ParseTableTypes(IReadOnlyL value.GetRequiredString("schema"), value.GetRequiredString("name"), value.GetRequiredString("clrName"), - ParseColumns(value.GetArray("columns")), + ParseColumns(value.GetRequiredArray("columns")), value.GetString("contractHash", string.Empty)); } @@ -65,7 +65,7 @@ private static EquatableArray ParseProcedures(IReadOnlyL value.GetRequiredString("clrName"), value.GetRequiredString("parametersClrName"), value.GetString("resultClrName", null), - ParseParameters(value.GetArray("parameters")), + ParseParameters(value.GetRequiredArray("parameters")), resultSet, value.GetString("contractHash", string.Empty)); } @@ -77,7 +77,7 @@ private static AutoContractsResultSet ParseResultSet(JsonObject value) { return new AutoContractsResultSet( value.GetString("status", "None"), - ParseColumns(value.GetArray("columns")), + ParseColumns(value.GetRequiredArray("columns")), value.GetString("errorMessage", null)); } @@ -384,12 +384,22 @@ internal string AsString() internal int AsInt32() { - return Convert.ToInt32(_value, CultureInfo.InvariantCulture); + return _value is double value && + value >= int.MinValue && + value <= int.MaxValue && + Math.Truncate(value) == value + ? (int)value + : throw new FormatException("Expected JSON integer."); } internal byte AsByte() { - return Convert.ToByte(_value, CultureInfo.InvariantCulture); + return _value is double value && + value >= byte.MinValue && + value <= byte.MaxValue && + Math.Truncate(value) == value + ? (byte)value + : throw new FormatException("Expected JSON byte."); } internal bool AsBoolean() @@ -404,9 +414,13 @@ private sealed class JsonObject(Dictionary values) { internal string GetRequiredString(string name) { - return values.TryGetValue(name, out var value) && !value.IsNull - ? value.AsString() - : throw new FormatException($"Required manifest property '{name}' is missing."); + if (!values.TryGetValue(name, out var value) || value.IsNull) + throw new FormatException($"Required manifest property '{name}' is missing."); + + var text = value.AsString(); + return string.IsNullOrWhiteSpace(text) + ? throw new FormatException($"Required manifest property '{name}' is empty.") + : text; } internal string GetString(string name, string? defaultValue) @@ -423,6 +437,13 @@ internal int GetInt32(string name, int defaultValue) : defaultValue; } + internal int GetRequiredInt32(string name) + { + return values.TryGetValue(name, out var value) && !value.IsNull + ? value.AsInt32() + : throw new FormatException($"Required manifest property '{name}' is missing."); + } + internal int? GetNullableInt32(string name) { return values.TryGetValue(name, out var value) && !value.IsNull @@ -451,6 +472,13 @@ internal IReadOnlyList GetArray(string name) : []; } + internal IReadOnlyList GetRequiredArray(string name) + { + return values.TryGetValue(name, out var value) && !value.IsNull + ? value.AsArray() + : throw new FormatException($"Required manifest property '{name}' is missing."); + } + internal bool TryGetObject(string name, out JsonObject obj) { if (values.TryGetValue(name, out var value) && !value.IsNull) diff --git a/SourceGenerators/AutoContracts/AutoContractsModels.cs b/SourceGenerators/AutoContracts/AutoContractsModels.cs index d20250a..d97ac70 100644 --- a/SourceGenerators/AutoContracts/AutoContractsModels.cs +++ b/SourceGenerators/AutoContracts/AutoContractsModels.cs @@ -1,6 +1,7 @@ namespace CaeriusNet.Generator.AutoContracts; internal sealed record AutoContractsManifest( + string SourcePath, int Version, string Namespace, EquatableArray TableTypes, diff --git a/SourceGenerators/AutoContracts/AutoContractsSourceGenerator.cs b/SourceGenerators/AutoContracts/AutoContractsSourceGenerator.cs index 564252b..2eff9c0 100644 --- a/SourceGenerators/AutoContracts/AutoContractsSourceGenerator.cs +++ b/SourceGenerators/AutoContracts/AutoContractsSourceGenerator.cs @@ -1,5 +1,3 @@ -using System.IO; - namespace CaeriusNet.Generator.AutoContracts; ///

@@ -12,11 +10,9 @@ public sealed class AutoContractsSourceGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { var manifests = context.AdditionalTextsProvider - .Where(static text => string.Equals( - Path.GetFileName(text.Path), - AutoContractsSourceNames.ManifestFileName, - StringComparison.OrdinalIgnoreCase)) - .Select(static (text, ct) => AutoContractsManifestParser.ParseOrDefault(text, ct)) + .Combine(context.AnalyzerConfigOptionsProvider) + .Where(static pair => AutoContractsManifestFile.IsManifest(pair.Left, pair.Right)) + .Select(static (pair, ct) => AutoContractsManifestParser.ParseOrDefault(pair.Left, ct)) .Where(static manifest => manifest is not null) .Select(static (manifest, _) => manifest!); diff --git a/SourceGenerators/AutoContracts/AutoContractsSourceNames.cs b/SourceGenerators/AutoContracts/AutoContractsSourceNames.cs index 0a3ded4..1965b8d 100644 --- a/SourceGenerators/AutoContracts/AutoContractsSourceNames.cs +++ b/SourceGenerators/AutoContracts/AutoContractsSourceNames.cs @@ -6,6 +6,28 @@ internal static class AutoContractsSourceNames internal static string BuildHintName(AutoContractsManifest manifest) { - return HintNameBuilder.Build(manifest.Namespace, "CaeriusContracts", "AutoContracts"); + return HintNameBuilder.Build( + manifest.Namespace, + "CaeriusContracts", + "AutoContracts." + BuildStablePathHash(manifest.SourcePath)); + } + + private static string BuildStablePathHash(string path) + { + unchecked + { + const uint offset = 2166136261; + const uint prime = 16777619; + var hash = offset; + + for (var i = 0; i < path.Length; i++) + { + var ch = path[i] == '\\' ? '/' : path[i]; + hash ^= ch; + hash *= prime; + } + + return hash.ToString("x8"); + } } } diff --git a/SourceGenerators/AutoContracts/AutoContractsSqlEmitter.cs b/SourceGenerators/AutoContracts/AutoContractsSqlEmitter.cs index c1f2235..eeb832d 100644 --- a/SourceGenerators/AutoContracts/AutoContractsSqlEmitter.cs +++ b/SourceGenerators/AutoContracts/AutoContractsSqlEmitter.cs @@ -238,7 +238,7 @@ private static bool IsIdentifierPart(char ch) private static bool IsKeyword(string identifier) { - return identifier is "class" or "struct" or "record" or "namespace" or "public" or "private" or - "internal" or "readonly" or "string" or "int" or "bool" or "decimal" or "object"; + return SyntaxFacts.GetKeywordKind(identifier) != SyntaxKind.None || + SyntaxFacts.GetContextualKeywordKind(identifier) != SyntaxKind.None; } } diff --git a/SourceGenerators/Helpers/TypeDetector.cs b/SourceGenerators/Helpers/TypeDetector.cs index d29311f..73073cf 100644 --- a/SourceGenerators/Helpers/TypeDetector.cs +++ b/SourceGenerators/Helpers/TypeDetector.cs @@ -212,64 +212,4 @@ public static string GetReaderMethodForSqlType(string sqlType) }; } - /// - /// Determines whether a type requires special conversion logic beyond direct SqlDataReader calls. - /// - /// The fully qualified type name to examine. - /// - /// if the type requires custom conversion (e.g., , - /// , byte arrays); - /// otherwise, . - /// - /// - /// Special conversion is needed for types like: - /// - /// - /// - converted from DateTime - /// - /// - /// - converted from DateTime - /// - /// - /// byte[] - requires casting from object - /// - /// - /// - public static bool RequiresSpecialConversion(string typeName) - { - var baseTypeName = ExtractBaseTypeFromNullable(typeName.AsSpan()); - - return baseTypeName switch - { - "System.DateOnly" or "DateOnly" => true, - "System.TimeOnly" or "TimeOnly" => true, - "System.Half" or "Half" => true, - "byte[]" or "System.Byte[]" => true, - _ => false - }; - } - - /// - /// Extracts the base type name from a potentially nullable type representation. - /// - /// The type name span to process. - /// - /// A span representing the base type name without the wrapper. - /// - /// - /// This method efficiently extracts the inner type from "System.Nullable<T>" syntax - /// using span operations to avoid string allocations. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ReadOnlySpan ExtractBaseTypeFromNullable(ReadOnlySpan typeName) - { - const string nullablePrefix = "System.Nullable<"; - - if (typeName.Length > nullablePrefix.Length + 1 && - typeName.StartsWith(nullablePrefix.AsSpan()) && - typeName[typeName.Length - 1] == '>') - return typeName.Slice(nullablePrefix.Length, typeName.Length - nullablePrefix.Length - 1); - - return typeName; - } } diff --git a/Src/Builders/StoredProcedureParametersBuilder.Typed.cs b/Src/Builders/StoredProcedureParametersBuilder.Typed.cs index ba8ce29..2a730a1 100644 --- a/Src/Builders/StoredProcedureParametersBuilder.Typed.cs +++ b/Src/Builders/StoredProcedureParametersBuilder.Typed.cs @@ -85,9 +85,7 @@ public StoredProcedureParametersBuilder AddGeneratedParameter( byte? precision = null, byte? scale = null) { - ArgumentException.ThrowIfNullOrEmpty(name); - - var parameter = new SqlParameter(name, dbType) { Value = value ?? DBNull.Value }; + var parameter = new SqlParameter(SqlParameterName.Normalize(name), dbType) { Value = value ?? DBNull.Value }; if (size is { } actualSize) parameter.Size = actualSize; if (precision is { } actualPrecision) @@ -119,16 +117,16 @@ public StoredProcedureParametersBuilder AddGeneratedTvpParameter( ReadOnlyMemory rows, GeneratedTvpRowMapper mapRow) { - ArgumentException.ThrowIfNullOrEmpty(name); - ArgumentException.ThrowIfNullOrEmpty(typeName); + ArgumentException.ThrowIfNullOrWhiteSpace(typeName); ArgumentNullException.ThrowIfNull(metadata); ArgumentNullException.ThrowIfNull(mapRow); + var parameterName = SqlParameterName.Normalize(name); if (metadata.Length == 0) throw new ArgumentException( $"Generated TVP parameter '{name}' for procedure '{TProcedure.FullName}' must declare at least one column.", nameof(metadata)); - var parameter = new SqlParameter(name, SqlDbType.Structured) + var parameter = new SqlParameter(parameterName, SqlDbType.Structured) { TypeName = typeName, Value = new ReadOnlyMemoryTvpParameterValue(metadata, rows, mapRow) @@ -215,9 +213,7 @@ private StoredProcedureParametersBuilder AddGeneratedSqlParameter(in { ValidateGeneratedOrdinal(ordinal); ArgumentNullException.ThrowIfNull(parameter); - ArgumentException.ThrowIfNullOrEmpty(parameter.ParameterName); - parameter.Value ??= DBNull.Value; _inner.AddParameter(parameter); _generatedParameterCount++; _generatedParametersBound = false; diff --git a/Src/Builders/StoredProcedureParametersBuilder.cs b/Src/Builders/StoredProcedureParametersBuilder.cs index e374b7e..1b678f8 100644 --- a/Src/Builders/StoredProcedureParametersBuilder.cs +++ b/Src/Builders/StoredProcedureParametersBuilder.cs @@ -56,16 +56,68 @@ public sealed record StoredProcedureParametersBuilder( /// public StoredProcedureParametersBuilder AddParameter(string parameter, object? value, SqlDbType dbType) { - ArgumentException.ThrowIfNullOrEmpty(parameter); - Parameters.Add(new SqlParameter(parameter, dbType) { Value = value ?? DBNull.Value }); + return AddParameter( + parameter, + value, + dbType, + size: null, + precision: null, + scale: null, + direction: ParameterDirection.Input); + } + + /// + /// Adds a parameter to the stored procedure call with explicit SQL Server facets. + /// + /// The name of the parameter. + /// The value of the parameter. + /// The SQL Server data type of the parameter. + /// Optional parameter size for variable-length types. + /// Optional decimal precision. + /// Optional decimal scale. + /// The parameter direction. + /// The current builder instance to enable method chaining. + /// + /// is null. + /// + /// + /// is empty, whitespace, or uses an invalid SQL parameter prefix. + /// + public StoredProcedureParametersBuilder AddParameter( + string parameter, + object? value, + SqlDbType dbType, + int? size = null, + byte? precision = null, + byte? scale = null, + ParameterDirection direction = ParameterDirection.Input) + { + if (direction is not (ParameterDirection.Input + or ParameterDirection.Output + or ParameterDirection.InputOutput + or ParameterDirection.ReturnValue)) + throw new ArgumentOutOfRangeException(nameof(direction), direction, "Unsupported SQL parameter direction."); + + var sqlParameter = new SqlParameter(SqlParameterName.Normalize(parameter), dbType) + { + Value = value ?? DBNull.Value, + Direction = direction + }; + + if (size is { } actualSize) + sqlParameter.Size = actualSize; + if (precision is { } actualPrecision) + sqlParameter.Precision = actualPrecision; + if (scale is { } actualScale) + sqlParameter.Scale = actualScale; + + Parameters.Add(sqlParameter); return this; } internal StoredProcedureParametersBuilder AddParameter(SqlParameter parameter) { - ArgumentNullException.ThrowIfNull(parameter); - ArgumentException.ThrowIfNullOrEmpty(parameter.ParameterName); - Parameters.Add(parameter); + Parameters.Add(StoredProcedureParameters.CloneParameter(parameter)); return this; } @@ -83,16 +135,15 @@ internal StoredProcedureParametersBuilder AddParameter(SqlParameter parameter) public StoredProcedureParametersBuilder AddTvpParameter(string parameter, IEnumerable items) where T : class, ITvpMapper { - ArgumentException.ThrowIfNullOrEmpty(parameter); ArgumentNullException.ThrowIfNull(items); - var tvpItems = items as T[] ?? items.ToArray(); - if (tvpItems.Length == 0) - throw new ArgumentException("No items found in the collection to map to a Table-Valued Parameter."); + var parameterName = SqlParameterName.Normalize(parameter); + var (mapper, tvpItems) = GetTvpMapperAndItems(items); + var typeName = T.TvpTypeName; + ArgumentException.ThrowIfNullOrWhiteSpace(typeName); - var mapper = tvpItems[0]; - var currentTvpParameter = new SqlParameter(parameter, SqlDbType.Structured) + var currentTvpParameter = new SqlParameter(parameterName, SqlDbType.Structured) { - TypeName = T.TvpTypeName, + TypeName = typeName, Value = new TvpParameterValue(() => mapper.MapAsSqlDataRecords(tvpItems)) }; @@ -111,6 +162,9 @@ public StoredProcedureParametersBuilder AddTvpParameter(string parameter, IEn /// public StoredProcedureParametersBuilder AddInMemoryCache(string cacheKey, TimeSpan expiration) { + ValidateCacheKey(cacheKey); + ValidatePositiveExpiration(expiration, nameof(expiration)); + _cacheKey = cacheKey; _cacheType = InMemory; _cacheExpiration = expiration; @@ -127,6 +181,8 @@ public StoredProcedureParametersBuilder AddInMemoryCache(string cacheKey, TimeSp /// public StoredProcedureParametersBuilder AddFrozenCache(string cacheKey) { + ValidateCacheKey(cacheKey); + _cacheKey = cacheKey; _cacheType = Frozen; _cacheExpiration = null; @@ -144,6 +200,9 @@ public StoredProcedureParametersBuilder AddFrozenCache(string cacheKey) /// public StoredProcedureParametersBuilder AddRedisCache(string cacheKey, TimeSpan? expiration = null) { + ValidateCacheKey(cacheKey); + ValidateOptionalPositiveExpiration(expiration, nameof(expiration)); + _cacheType = Redis; _cacheKey = cacheKey; _cacheExpiration = expiration; @@ -176,7 +235,7 @@ public StoredProcedureParameters Build() ArgumentOutOfRangeException.ThrowIfNegative(CommandTimeout); var parameters = Parameters.Count > 0 - ? CollectionsMarshal.AsSpan(Parameters).ToArray() + ? Parameters.ToArray() : []; return new StoredProcedureParameters( @@ -201,6 +260,51 @@ private static bool IsValidSqlIdentifier(string identifier) && !span[1..].ContainsAnyExcept(ValidIdentifierChars); } + private static (T Mapper, IEnumerable Items) GetTvpMapperAndItems(IEnumerable items) + where T : class, ITvpMapper + { + switch (items) + { + case IReadOnlyList { Count: > 0 } readOnlyList: + return (readOnlyList[0], readOnlyList); + + case IReadOnlyList: + break; + + case IList { Count: > 0 } list: + return (list[0], list); + + case IList: + break; + + default: + var tvpItems = items as T[] ?? items.ToArray(); + if (tvpItems.Length > 0) + return (tvpItems[0], tvpItems); + break; + } + + throw new ArgumentException("No items found in the collection to map to a Table-Valued Parameter.", + nameof(items)); + } + + private static void ValidateCacheKey(string cacheKey) + { + ArgumentException.ThrowIfNullOrWhiteSpace(cacheKey); + } + + private static void ValidateOptionalPositiveExpiration(TimeSpan? expiration, string paramName) + { + if (expiration is { } actualExpiration) + ValidatePositiveExpiration(actualExpiration, paramName); + } + + private static void ValidatePositiveExpiration(TimeSpan expiration, string paramName) + { + if (expiration <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException(paramName, expiration, "Cache expiration must be positive."); + } + private sealed class TvpParameterValue(Func> factory) : IEnumerable { public IEnumerator GetEnumerator() diff --git a/Src/Caches/CaeriusNetCache.cs b/Src/Caches/CaeriusNetCache.cs index eea79e3..adaf11a 100644 --- a/Src/Caches/CaeriusNetCache.cs +++ b/Src/Caches/CaeriusNetCache.cs @@ -8,7 +8,7 @@ internal sealed class CaeriusNetCache(IRedisCacheManager? redisCacheManager = nu { public ValueTask RemoveAsync(string cacheKey, CancellationToken cancellationToken = default) { - ArgumentException.ThrowIfNullOrEmpty(cacheKey); + ArgumentException.ThrowIfNullOrWhiteSpace(cacheKey); cancellationToken.ThrowIfCancellationRequested(); FrozenCacheManager.Remove(cacheKey); @@ -20,7 +20,7 @@ public ValueTask RemoveAsync(string cacheKey, CancellationToken cancellationToke public ValueTask RemoveAsync(string cacheKey, CacheType cacheType, CancellationToken cancellationToken = default) { - ArgumentException.ThrowIfNullOrEmpty(cacheKey); + ArgumentException.ThrowIfNullOrWhiteSpace(cacheKey); cancellationToken.ThrowIfCancellationRequested(); switch (cacheType) diff --git a/Src/Caches/FrozenCacheManager.cs b/Src/Caches/FrozenCacheManager.cs index 9821463..a870465 100644 --- a/Src/Caches/FrozenCacheManager.cs +++ b/Src/Caches/FrozenCacheManager.cs @@ -82,37 +82,47 @@ internal static void Store(string cacheKey, T value) /// avoiding the O(n²) rebuild cost of sequential single-item inserts. /// /// The type of the values to be stored. - /// The key-value pairs to add. Keys already present are overwritten. + /// The key-value pairs to add. Keys already present keep their current value. /// /// This method is intended for startup warm-up. is /// designed for "populate once, read many" scenarios; bulk insertion at startup is the ideal usage. + /// Runtime writes are still O(N) because every successful publication creates a new frozen snapshot. /// [MethodImpl(MethodImplOptions.AggressiveOptimization)] internal static void StoreRange(IEnumerable> entries) { + ArgumentNullException.ThrowIfNull(entries); + + var newEntries = entries.ToList(); + if (newEntries.Count == 0) return; + LockSlim.EnterWriteLock(); try { var currentCache = _frozenCache; - var newEntries = entries is ICollection> col - ? col - : entries.ToList(); - - if (newEntries.Count == 0) return; var newCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)(currentCache.Count + newEntries.Count)); var builder = new Dictionary(newCapacity, StringComparer.Ordinal); + var added = 0; foreach (var kvp in currentCache) builder[kvp.Key] = kvp.Value; foreach (var kvp in newEntries) - builder[kvp.Key] = kvp.Value!; + { + if (!builder.TryAdd(kvp.Key, kvp.Value!)) + continue; + + added++; + } + + if (added == 0) + return; Volatile.Write(ref _frozenCache, builder.ToFrozenDictionary(StringComparer.Ordinal)); if (IsLoggingEnabled) - Logger!.LogStoredRangeInFrozenCache(newEntries.Count); + Logger!.LogStoredRangeInFrozenCache(added); } finally { diff --git a/Src/Caches/InMemoryCacheManager.cs b/Src/Caches/InMemoryCacheManager.cs index f375ce1..fb25873 100644 --- a/Src/Caches/InMemoryCacheManager.cs +++ b/Src/Caches/InMemoryCacheManager.cs @@ -10,23 +10,14 @@ /// internal static class InMemoryCacheManager { - /// - /// The memory cache instance used for storing cached items. Replaceable via - /// before any Store/TryGet call (typically from the DI builder at startup). - /// - private static MemoryCache _memoryCache = new(new MemoryCacheOptions - { - SizeLimit = null, - CompactionPercentage = 0.05, - ExpirationScanFrequency = TimeSpan.FromMinutes(2), - TrackLinkedCacheEntries = false - }); + private const int RetiredStateFlag = unchecked((int)0x80000000); + private const int ReferenceCountMask = 0x7fffffff; /// - /// When non-null, every cache entry is sized as 1 so that - /// effectively caps the maximum number of resident entries. Null preserves legacy unbounded behavior. + /// The memory cache state used for storing cached items. Replaceable via + /// before any Store/TryGet call (typically from the DI builder at startup). /// - private static long? _entrySize; + private static CacheState _state = CreateDefaultState(); /// /// The logger instance used for recording cache operations. @@ -47,9 +38,10 @@ internal static class InMemoryCacheManager internal static void Configure(MemoryCacheOptions options) { ArgumentNullException.ThrowIfNull(options); - var previous = Interlocked.Exchange(ref _memoryCache, new MemoryCache(options)); - _entrySize = options.SizeLimit; - previous.Dispose(); + + var next = new CacheState(new MemoryCache(options), options.SizeLimit.HasValue); + var previous = Interlocked.Exchange(ref _state, next); + previous.Retire(); } /// @@ -61,16 +53,18 @@ internal static void Store(string cacheKey, T value, TimeSpan expiration) if (IsLoggingEnabled) Logger!.LogStoringInMemoryCache(cacheKey); + using var lease = AcquireState(); + var options = new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = expiration, Priority = CacheItemPriority.Normal }; - if (_entrySize.HasValue) + if (lease.SizeEntries) options.Size = 1; - _memoryCache.Set(cacheKey, value!, options); + lease.Cache.Set(cacheKey, value!, options); } /// @@ -79,7 +73,9 @@ internal static void Store(string cacheKey, T value, TimeSpan expiration) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool TryGet(string cacheKey, out T? value) { - if (!_memoryCache.TryGetValue(cacheKey, out var cached) || cached is not T typedValue) + using var lease = AcquireState(); + + if (!lease.Cache.TryGetValue(cacheKey, out var cached) || cached is not T typedValue) { value = default; return false; @@ -99,7 +95,8 @@ internal static bool TryGet(string cacheKey, out T? value) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Remove(string cacheKey) { - _memoryCache.Remove(cacheKey); + using var lease = AcquireState(); + lease.Cache.Remove(cacheKey); } /// @@ -107,6 +104,92 @@ internal static void Remove(string cacheKey) /// internal static void Clear() { - _memoryCache.Clear(); + using var lease = AcquireState(); + lease.Cache.Clear(); + } + + private static CacheState CreateDefaultState() + { + return new CacheState(new MemoryCache(new MemoryCacheOptions + { + SizeLimit = null, + CompactionPercentage = 0.05, + ExpirationScanFrequency = TimeSpan.FromMinutes(2), + TrackLinkedCacheEntries = false + }), false); + } + + private static CacheLease AcquireState() + { + while (true) + { + var state = Volatile.Read(ref _state); + if (state.TryAddReference()) + return new CacheLease(state); + } + } + + private sealed class CacheState(MemoryCache cache, bool sizeEntries) + { + private int _referenceState; + + internal MemoryCache Cache { get; } = cache; + + internal bool SizeEntries { get; } = sizeEntries; + + internal bool TryAddReference() + { + while (true) + { + var current = Volatile.Read(ref _referenceState); + if ((current & RetiredStateFlag) != 0) + return false; + + var referenceCount = current & ReferenceCountMask; + if (referenceCount == ReferenceCountMask) + throw new InvalidOperationException("The in-memory cache has too many concurrent operations."); + + if (Interlocked.CompareExchange(ref _referenceState, current + 1, current) == current) + return true; + } + } + + internal void Release() + { + var current = Interlocked.Decrement(ref _referenceState); + if ((current & RetiredStateFlag) != 0 && (current & ReferenceCountMask) == 0) + Cache.Dispose(); + } + + internal void Retire() + { + while (true) + { + var current = Volatile.Read(ref _referenceState); + if ((current & RetiredStateFlag) != 0) + return; + + var retired = current | RetiredStateFlag; + if (Interlocked.CompareExchange(ref _referenceState, retired, current) != current) + continue; + + if ((retired & ReferenceCountMask) == 0) + Cache.Dispose(); + + return; + } + } + } + + private readonly struct CacheLease(CacheState state) : IDisposable + { + internal MemoryCache Cache => state.Cache; + + internal bool SizeEntries => state.SizeEntries; + + public void Dispose() + { + state.Release(); + } } } diff --git a/Src/Commands/Transactions/TransactionReadSqlAsyncCommands.cs b/Src/Commands/Transactions/TransactionReadSqlAsyncCommands.cs index 754343e..afaa28e 100644 --- a/Src/Commands/Transactions/TransactionReadSqlAsyncCommands.cs +++ b/Src/Commands/Transactions/TransactionReadSqlAsyncCommands.cs @@ -18,6 +18,11 @@ private static ICaeriusNetTransactionInternal AsInternal(ICaeriusNetTransaction "ICaeriusNetTransaction implementations must derive from the framework's CaeriusNetTransaction."); } + private static bool ShouldPoisonCommandFailure(Exception ex) + { + return ex is not OutOfMemoryException; + } + /// Transaction whose connection and scope are reused. extension(ICaeriusNetTransaction transaction) { @@ -67,6 +72,12 @@ private static ICaeriusNetTransactionInternal AsInternal(ICaeriusNetTransaction throw new CaeriusNetSqlException( $"Failed to execute stored procedure: {spParameters.ProcedureName}", ex); } + catch (Exception ex) when (ShouldPoisonCommandFailure(ex)) + { + CaeriusActivityExtensions.RecordError(activity, tags, ex); + tx.Poison(); + throw; + } finally { tx.ReleaseCommandSlot(); @@ -122,6 +133,12 @@ public async ValueTask> QueryAsReadOnlyCollection throw new CaeriusNetSqlException( $"Failed to execute stored procedure: {spParameters.ProcedureName}", ex); } + catch (Exception ex) when (ShouldPoisonCommandFailure(ex)) + { + CaeriusActivityExtensions.RecordError(activity, tags, ex); + tx.Poison(); + throw; + } finally { tx.ReleaseCommandSlot(); @@ -184,6 +201,12 @@ public async ValueTask> QueryAsImmutableArrayAsyncTransaction whose connection and scope are reused. extension(ICaeriusNetTransaction transaction) { @@ -66,6 +71,12 @@ private static ICaeriusNetTransactionInternal AsInternal(ICaeriusNetTransaction tx.Poison(); throw; } + catch (Exception ex) when (ShouldPoisonCommandFailure(ex)) + { + CaeriusActivityExtensions.RecordError(activity, tags, ex); + tx.Poison(); + throw; + } finally { tx.ReleaseCommandSlot(); @@ -117,6 +128,12 @@ public async ValueTask ExecuteNonQueryAsync( tx.Poison(); throw; } + catch (Exception ex) when (ShouldPoisonCommandFailure(ex)) + { + CaeriusActivityExtensions.RecordError(activity, tags, ex); + tx.Poison(); + throw; + } finally { tx.ReleaseCommandSlot(); @@ -166,6 +183,12 @@ public async ValueTask ExecuteAsync( tx.Poison(); throw; } + catch (Exception ex) when (ShouldPoisonCommandFailure(ex)) + { + CaeriusActivityExtensions.RecordError(activity, tags, ex); + tx.Poison(); + throw; + } finally { tx.ReleaseCommandSlot(); diff --git a/Src/Factories/CaeriusNetDbContext.cs b/Src/Factories/CaeriusNetDbContext.cs index 2be9fbe..f456636 100644 --- a/Src/Factories/CaeriusNetDbContext.cs +++ b/Src/Factories/CaeriusNetDbContext.cs @@ -27,23 +27,50 @@ public async ValueTask DbConnectionAsync(CancellationToken cancel if (_isLoggingEnabled) _logger!.LogDatabaseConnecting(); + SqlConnection? connection = null; try { - var connection = _sqlConnectionFactory(); + connection = _sqlConnectionFactory() + ?? throw new InvalidOperationException("SQL connection factory returned null."); if (connection.State == ConnectionState.Closed) await connection.OpenAsync(cancellationToken).ConfigureAwait(false); + if (connection.State != ConnectionState.Open) + throw new InvalidOperationException("SQL connection factory returned a non-open connection."); if (_isLoggingEnabled) _logger!.LogDatabaseConnected(); - return connection; + var openedConnection = connection; + connection = null; + return openedConnection; } catch (SqlException ex) { + await DisposeFailedConnectionAsync(connection).ConfigureAwait(false); if (_isLoggingEnabled) _logger!.LogDatabaseConnectionFailed(ex); throw new CaeriusNetSqlException("Failed to open database connection", ex); } + catch + { + await DisposeFailedConnectionAsync(connection).ConfigureAwait(false); + throw; + } + } + + private static async ValueTask DisposeFailedConnectionAsync(SqlConnection? connection) + { + if (connection is null) + return; + + try + { + await connection.DisposeAsync().ConfigureAwait(false); + } + catch + { + // Preserve the original open/factory exception; cleanup is best-effort here. + } } } diff --git a/Src/Factories/CaeriusNetTransaction.cs b/Src/Factories/CaeriusNetTransaction.cs index 4c1f9a9..50136d0 100644 --- a/Src/Factories/CaeriusNetTransaction.cs +++ b/Src/Factories/CaeriusNetTransaction.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Runtime.ExceptionServices; namespace CaeriusNet.Factories; @@ -46,23 +47,31 @@ public async ValueTask CommitAsync(CancellationToken cancellationToken = default try { - var prev = Interlocked.CompareExchange(ref _state, StateCommitted, StateActive); - if (prev != StateActive) + var current = Volatile.Read(ref _state); + if (current != StateActive) throw new InvalidOperationException( - $"Cannot commit: transaction is in state '{StateName(prev)}'."); + $"Cannot commit: transaction is in state '{StateName(current)}'."); + + try + { + await Transaction.CommitAsync(cancellationToken).ConfigureAwait(false); + } + catch (SqlException ex) + { + MarkCompletionFailure("commit-failed"); + throw new CaeriusNetSqlException("Failed to commit SQL Server transaction.", ex); + } + catch (Exception ex) when (IsTransactionLifecycleFailure(ex)) + { + MarkCompletionFailure("commit-failed"); + throw; + } - await Transaction.CommitAsync(cancellationToken).ConfigureAwait(false); + Volatile.Write(ref _state, StateCommitted); CaeriusActivityExtensions.RecordTransactionOutcome(_txActivity, "committed"); _txActivity = null; if (_isLoggingEnabled) _logger!.LogTransactionCommitted(); } - catch (SqlException ex) - { - Volatile.Write(ref _state, StatePoisoned); - CaeriusActivityExtensions.RecordTransactionOutcome(_txActivity, "commit-failed", true); - _txActivity = null; - throw new CaeriusNetSqlException("Failed to commit SQL Server transaction.", ex); - } finally { ReleaseCommandSlot(); @@ -80,19 +89,26 @@ public async ValueTask RollbackAsync(CancellationToken cancellationToken = defau throw new InvalidOperationException( $"Cannot rollback: transaction is in state '{StateName(current)}'."); - await Transaction.RollbackAsync(cancellationToken).ConfigureAwait(false); + try + { + await Transaction.RollbackAsync(cancellationToken).ConfigureAwait(false); + } + catch (SqlException ex) + { + MarkCompletionFailure("rollback-failed"); + throw new CaeriusNetSqlException("Failed to rollback SQL Server transaction.", ex); + } + catch (Exception ex) when (IsTransactionLifecycleFailure(ex)) + { + MarkCompletionFailure("rollback-failed"); + throw; + } + Volatile.Write(ref _state, StateRolledBack); CaeriusActivityExtensions.RecordTransactionOutcome(_txActivity, "rolled-back"); _txActivity = null; if (_isLoggingEnabled) _logger!.LogTransactionRolledBack(); } - catch (SqlException ex) - { - Volatile.Write(ref _state, StatePoisoned); - CaeriusActivityExtensions.RecordTransactionOutcome(_txActivity, "rollback-failed", true); - _txActivity = null; - throw new CaeriusNetSqlException("Failed to rollback SQL Server transaction.", ex); - } finally { ReleaseCommandSlot(); @@ -126,35 +142,38 @@ public void Poison() public async ValueTask DisposeAsync() { - var prev = Interlocked.Exchange(ref _state, StateDisposed); - if (prev == StateDisposed) return; + if (Volatile.Read(ref _state) == StateDisposed) + return; - if (prev is StateActive or StatePoisoned && _transaction is not null) - try - { - await _transaction.RollbackAsync().ConfigureAwait(false); - var outcome = prev == StatePoisoned ? "poisoned-auto-rollback" : "auto-rollback"; - CaeriusActivityExtensions.RecordTransactionOutcome(_txActivity, outcome, prev == StatePoisoned); - _txActivity = null; - if (_isLoggingEnabled) _logger!.LogTransactionRolledBack(); - } - catch - { - // Best-effort rollback during disposal; surface no exception to callers. - _txActivity?.Stop(); - _txActivity = null; - } + AcquireCompletionSlot("dispose"); - if (_transaction is not null) + try { - await _transaction.DisposeAsync().ConfigureAwait(false); - _transaction = null; - } + var prev = Interlocked.Exchange(ref _state, StateDisposed); + if (prev == StateDisposed) return; + + var transaction = _transaction; + if ((prev is StateActive or StatePoisoned) && transaction is not null) + try + { + await transaction.RollbackAsync().ConfigureAwait(false); + var outcome = prev == StatePoisoned ? "poisoned-auto-rollback" : "auto-rollback"; + CaeriusActivityExtensions.RecordTransactionOutcome(_txActivity, outcome, prev == StatePoisoned); + _txActivity = null; + if (_isLoggingEnabled) _logger!.LogTransactionRolledBack(); + } + catch + { + // Best-effort rollback during disposal; surface no rollback exception to callers. + _txActivity?.Stop(); + _txActivity = null; + } - if (_connection is not null) + await DisposeOwnedResourcesAsync(suppressExceptions: false).ConfigureAwait(false); + } + finally { - await _connection.DisposeAsync().ConfigureAwait(false); - _connection = null; + ReleaseCommandSlot(); } } @@ -184,6 +203,8 @@ internal static async ValueTask BeginAsync( connection = await dbContext.DbConnectionAsync(cancellationToken).ConfigureAwait(false); if (connection.State == ConnectionState.Closed) await connection.OpenAsync(cancellationToken).ConfigureAwait(false); + if (connection.State != ConnectionState.Open) + throw new InvalidOperationException("DbConnectionAsync returned a non-open SQL connection."); transaction = (SqlTransaction)await connection .BeginTransactionAsync(isolationLevel, cancellationToken) @@ -200,18 +221,75 @@ internal static async ValueTask BeginAsync( } catch (SqlException ex) { - if (transaction is not null) await transaction.DisposeAsync().ConfigureAwait(false); - if (connection is not null) await connection.DisposeAsync().ConfigureAwait(false); + await DisposeResourcesAsync(transaction, connection, suppressExceptions: true).ConfigureAwait(false); throw new CaeriusNetSqlException("Failed to open SQL Server transaction.", ex); } catch { - if (transaction is not null) await transaction.DisposeAsync().ConfigureAwait(false); - if (connection is not null) await connection.DisposeAsync().ConfigureAwait(false); + await DisposeResourcesAsync(transaction, connection, suppressExceptions: true).ConfigureAwait(false); throw; } } + private void MarkCompletionFailure(string outcome) + { + Volatile.Write(ref _state, StatePoisoned); + CaeriusActivityExtensions.RecordTransactionOutcome(_txActivity, outcome, true); + _txActivity = null; + } + + private static bool IsTransactionLifecycleFailure(Exception ex) + { + return ex is InvalidOperationException or OperationCanceledException; + } + + private async ValueTask DisposeOwnedResourcesAsync(bool suppressExceptions) + { + var transaction = _transaction; + var connection = _connection; + _transaction = null; + _connection = null; + + await DisposeResourcesAsync(transaction, connection, suppressExceptions).ConfigureAwait(false); + } + + private static async ValueTask DisposeResourcesAsync( + SqlTransaction? transaction, + SqlConnection? connection, + bool suppressExceptions) + { + Exception? failure = null; + + if (transaction is not null) + try + { + await transaction.DisposeAsync().ConfigureAwait(false); + } + catch (Exception) when (suppressExceptions) + { + } + catch (Exception ex) + { + failure = ex; + } + + if (connection is not null) + try + { + await connection.DisposeAsync().ConfigureAwait(false); + } + catch (Exception) when (suppressExceptions) + { + } + catch (Exception ex) + { + failure ??= ex; + } + + if (failure is not null) + ExceptionDispatchInfo.Capture(failure).Throw(); + } + private static string StateName(int state) { return state switch diff --git a/Src/Helpers/MultiResultSetHelper.cs b/Src/Helpers/MultiResultSetHelper.cs index 09c03fb..0370988 100644 --- a/Src/Helpers/MultiResultSetHelper.cs +++ b/Src/Helpers/MultiResultSetHelper.cs @@ -14,22 +14,13 @@ internal static class MultiResultSetHelper /// Cancellation token /// A List containing the mapped rows [MethodImpl(MethodImplOptions.AggressiveOptimization)] - internal static async ValueTask> ReadResultSetAsync( + internal static ValueTask> ReadResultSetAsync( SqlDataReader reader, int capacity, CancellationToken cancellationToken) where T : class, ISpMapper { - var list = new List(capacity); - - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) - { - var item = T.MapFromDataReader(reader); - - list.Add(item); - } - - return list; + return ResultSetMaterializer.ReadListAsync(reader, capacity, cancellationToken); } /// @@ -44,37 +35,13 @@ internal static async ValueTask> ReadResultSetAsync( /// Uses direct creation of ImmutableArray to avoid an extra allocation compared to Builder + ToImmutable /// [MethodImpl(MethodImplOptions.AggressiveOptimization)] - internal static async ValueTask> ReadResultSetAsImmutableArrayAsync( + internal static ValueTask> ReadResultSetAsImmutableArrayAsync( SqlDataReader reader, int capacity, CancellationToken cancellationToken) where T : class, ISpMapper { - var buffer = ArrayPool.Shared.Rent(NormalizeCapacity(capacity)); - var count = 0; - - try - { - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) - { - if (count >= buffer.Length) - { - var newCapacity = GrowCapacity(buffer.Length); - var newBuffer = ArrayPool.Shared.Rent(newCapacity); - buffer.AsSpan(0, count).CopyTo(newBuffer); - ArrayPool.Shared.Return(buffer); - buffer = newBuffer; - } - - buffer[count++] = T.MapFromDataReader(reader); - } - - return [..buffer.AsSpan(0, count)]; - } - finally - { - ArrayPool.Shared.Return(buffer); - } + return ResultSetMaterializer.ReadImmutableArrayAsync(reader, capacity, cancellationToken); } /// @@ -94,15 +61,4 @@ internal static ValueTask TryMoveNextAsync( : new ValueTask(task); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int NormalizeCapacity(int capacity) - { - return Math.Max(capacity, 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GrowCapacity(int capacity) - { - return capacity <= 1 ? 2 : capacity * 3 / 2; - } } diff --git a/Src/Helpers/ResultSetMaterializer.cs b/Src/Helpers/ResultSetMaterializer.cs new file mode 100644 index 0000000..82c0df2 --- /dev/null +++ b/Src/Helpers/ResultSetMaterializer.cs @@ -0,0 +1,85 @@ +namespace CaeriusNet.Helpers; + +internal static class ResultSetMaterializer +{ + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static async ValueTask> ReadListAsync( + SqlDataReader reader, + int capacity, + CancellationToken cancellationToken) + where T : class, ISpMapper + { + var list = new List(capacity); + + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) + list.Add(T.MapFromDataReader(reader)); + + return list; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static async ValueTask> ReadImmutableArrayAsync( + SqlDataReader reader, + int capacity, + CancellationToken cancellationToken) + where T : class, ISpMapper + { + var buffer = ArrayPool.Shared.Rent(NormalizeCapacity(capacity)); + var count = 0; + + try + { + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) + { + if (count >= buffer.Length) + buffer = GrowBuffer(buffer, count); + + buffer[count++] = T.MapFromDataReader(reader); + } + + return [..buffer.AsSpan(0, count)]; + } + finally + { + ReturnBuffer(buffer); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int NormalizeCapacity(int capacity) + { + return Math.Max(capacity, 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GrowCapacity(int capacity) + { + ArgumentOutOfRangeException.ThrowIfNegative(capacity); + + if (capacity <= 1) + return 2; + + if (capacity >= Array.MaxLength) + throw new InvalidOperationException("The result set is too large to materialize."); + + var grown = capacity + (long)(capacity >> 1); + return grown > Array.MaxLength ? Array.MaxLength : (int)grown; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static T[] GrowBuffer(T[] buffer, int count) + where T : class + { + var newBuffer = ArrayPool.Shared.Rent(GrowCapacity(buffer.Length)); + buffer.AsSpan(0, count).CopyTo(newBuffer); + ReturnBuffer(buffer); + return newBuffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ReturnBuffer(T[] buffer) + where T : class + { + ArrayPool.Shared.Return(buffer, clearArray: true); + } +} diff --git a/Src/Helpers/SqlCommandHelper.cs b/Src/Helpers/SqlCommandHelper.cs index 340f3ae..782b883 100644 --- a/Src/Helpers/SqlCommandHelper.cs +++ b/Src/Helpers/SqlCommandHelper.cs @@ -189,21 +189,14 @@ internal static async ValueTask> ResultSetAsReadO CancellationToken cancellationToken = default) where TResultSet : class, ISpMapper { - var results = new List(spParameters.Capacity); - await using var command = await ExecuteSqlCommandAsync(spParameters, connection, cancellationToken).ConfigureAwait(false); await using var reader = await command .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) .ConfigureAwait(false); - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) - { - var item = TResultSet.MapFromDataReader(reader); - - results.Add(item); - } - + var results = await ResultSetMaterializer.ReadListAsync( + reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); return results.AsReadOnly(); } @@ -259,36 +252,14 @@ internal static async ValueTask> ResultSetAsImmutable CancellationToken cancellationToken = default) where TResultSet : class, ISpMapper { - var buffer = ArrayPool.Shared.Rent(NormalizeCapacity(spParameters.Capacity)); - var count = 0; - - try - { - await using var command = await ExecuteSqlCommandAsync(spParameters, connection, cancellationToken) - .ConfigureAwait(false); - await using var reader = await command - .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) - .ConfigureAwait(false); - - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) - { - if (count >= buffer.Length) - { - var newBuffer = ArrayPool.Shared.Rent(GrowCapacity(buffer.Length)); - buffer.AsSpan(0, count).CopyTo(newBuffer); - ArrayPool.Shared.Return(buffer); - buffer = newBuffer; - } - - buffer[count++] = TResultSet.MapFromDataReader(reader); - } + await using var command = await ExecuteSqlCommandAsync(spParameters, connection, cancellationToken) + .ConfigureAwait(false); + await using var reader = await command + .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) + .ConfigureAwait(false); - return [..buffer.AsSpan(0, count)]; - } - finally - { - ArrayPool.Shared.Return(buffer); - } + return await ResultSetMaterializer.ReadImmutableArrayAsync( + reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); } /// @@ -345,18 +316,6 @@ private static async ValueTask ExecuteSqlCommandAsync( return command; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int NormalizeCapacity(int capacity) - { - return Math.Max(capacity, 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GrowCapacity(int capacity) - { - return capacity <= 1 ? 2 : capacity * 3 / 2; - } - /// /// Executes an asynchronous SQL command using a stored procedure and a provided execution function. /// diff --git a/Src/Helpers/SqlCommandHelperTx.cs b/Src/Helpers/SqlCommandHelperTx.cs index 8bc3edb..3bd053c 100644 --- a/Src/Helpers/SqlCommandHelperTx.cs +++ b/Src/Helpers/SqlCommandHelperTx.cs @@ -27,18 +27,6 @@ private static SqlCommand BuildCommand( return command; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int NormalizeCapacity(int capacity) - { - return Math.Max(capacity, 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GrowCapacity(int capacity) - { - return capacity <= 1 ? 2 : capacity * 3 / 2; - } - [MethodImpl(MethodImplOptions.AggressiveOptimization)] internal static async ValueTask ScalarQueryTxAsync( StoredProcedureParameters spParameters, @@ -83,19 +71,13 @@ internal static async ValueTask> ResultSetAsReadO CancellationToken cancellationToken) where TResultSet : class, ISpMapper { - var results = new List(spParameters.Capacity); - await using var command = BuildCommand(spParameters, connection, transaction); await using var reader = await command .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) .ConfigureAwait(false); - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) - { - var item = TResultSet.MapFromDataReader(reader); - results.Add(item); - } - + var results = await ResultSetMaterializer.ReadListAsync( + reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); return results.AsReadOnly(); } @@ -107,35 +89,13 @@ internal static async ValueTask> ResultSetAsImmutable CancellationToken cancellationToken) where TResultSet : class, ISpMapper { - var buffer = ArrayPool.Shared.Rent(NormalizeCapacity(spParameters.Capacity)); - var count = 0; - - try - { - await using var command = BuildCommand(spParameters, connection, transaction); - await using var reader = await command - .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) - .ConfigureAwait(false); - - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) - { - if (count >= buffer.Length) - { - var newBuffer = ArrayPool.Shared.Rent(GrowCapacity(buffer.Length)); - buffer.AsSpan(0, count).CopyTo(newBuffer); - ArrayPool.Shared.Return(buffer); - buffer = newBuffer; - } - - buffer[count++] = TResultSet.MapFromDataReader(reader); - } + await using var command = BuildCommand(spParameters, connection, transaction); + await using var reader = await command + .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) + .ConfigureAwait(false); - return [..buffer.AsSpan(0, count)]; - } - finally - { - ArrayPool.Shared.Return(buffer); - } + return await ResultSetMaterializer.ReadImmutableArrayAsync( + reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); } /// diff --git a/Src/Mappers/StoredProcedureParameters.cs b/Src/Mappers/StoredProcedureParameters.cs index fd8abeb..797c21b 100644 --- a/Src/Mappers/StoredProcedureParameters.cs +++ b/Src/Mappers/StoredProcedureParameters.cs @@ -31,13 +31,14 @@ public StoredProcedureParameters( CacheType? cacheType, int commandTimeout = 30) { + ArgumentNullException.ThrowIfNull(parameters); ArgumentOutOfRangeException.ThrowIfNegative(capacity); ArgumentOutOfRangeException.ThrowIfNegative(commandTimeout); SchemaName = schemaName; ProcedureName = procedureName; Capacity = capacity; - _parameters = parameters; + _parameters = CloneParameters(parameters); CacheKey = cacheKey; CacheExpiration = cacheExpiration; CacheType = cacheType; @@ -68,17 +69,61 @@ public ReadOnlySpan GetParametersSpan() [MethodImpl(MethodImplOptions.AggressiveOptimization)] internal void AddParametersTo(SqlParameterCollection parameters) { - var paramsSpan = GetParametersSpan(); - ref var searchSpace = ref MemoryMarshal.GetReference(paramsSpan); + ArgumentNullException.ThrowIfNull(parameters); - for (var i = 0; i < paramsSpan.Length; i++) - parameters.Add(CloneParameter(Unsafe.Add(ref searchSpace, i))); + for (var i = 0; i < _parameters.Length; i++) + parameters.Add(CloneParameter(_parameters[i])); } - private static SqlParameter CloneParameter(SqlParameter parameter) + private static SqlParameter[] CloneParameters(SqlParameter[] parameters) { + if (parameters.Length == 0) + return []; + + var clones = new SqlParameter[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) + clones[i] = CloneParameter(parameters[i]); + + return clones; + } + + internal static SqlParameter CloneParameter(SqlParameter parameter) + { + ArgumentNullException.ThrowIfNull(parameter); var clone = (SqlParameter)((ICloneable)parameter).Clone(); + clone.ParameterName = SqlParameterName.Normalize(clone.ParameterName); clone.Value ??= DBNull.Value; return clone; } } + +internal static class SqlParameterName +{ + internal static string Normalize(string parameterName) + { + ArgumentException.ThrowIfNullOrWhiteSpace(parameterName); + + if (parameterName.Length != parameterName.Trim().Length) + throw new ArgumentException("SQL parameter names cannot contain leading or trailing whitespace.", + nameof(parameterName)); + + var normalized = parameterName[0] == '@' + ? parameterName + : "@" + parameterName; + + if (normalized.Length == 1) + throw new ArgumentException("SQL parameter names must include a name after '@'.", nameof(parameterName)); + + if (normalized[1] == '@') + throw new ArgumentException("SQL parameter names must use a single leading '@'.", nameof(parameterName)); + + for (var i = 0; i < normalized.Length; i++) + { + if (char.IsControl(normalized[i])) + throw new ArgumentException("SQL parameter names cannot contain control characters.", + nameof(parameterName)); + } + + return normalized; + } +} diff --git a/Tests/CaeriusNet.Analyzer.Tests/AutoContractsDiagnosticDescriptorTests.cs b/Tests/CaeriusNet.Analyzer.Tests/AutoContractsDiagnosticDescriptorTests.cs index e79f3af..e0b9d46 100644 --- a/Tests/CaeriusNet.Analyzer.Tests/AutoContractsDiagnosticDescriptorTests.cs +++ b/Tests/CaeriusNet.Analyzer.Tests/AutoContractsDiagnosticDescriptorTests.cs @@ -59,15 +59,27 @@ public sealed class AutoContractsDiagnosticDescriptorTests }; [Fact] - public void SupportedDiagnostics_Advertise_AutoContractsRules() + public void SupportedDiagnostics_Advertise_Only_Roslyn_Emitted_AutoContractsRules() { var supportedIds = new AutoContractsManifestAnalyzer() .SupportedDiagnostics .Select(diagnostic => diagnostic.Id) - .ToHashSet(StringComparer.Ordinal); + .OrderBy(id => id, StringComparer.Ordinal) + .ToArray(); - foreach (var id in ExpectedDiagnostics.Keys) - Assert.Contains(id, supportedIds); + Assert.Equal( + [ + "CAERIUS200", + "CAERIUS202", + "CAERIUS203", + "CAERIUS204", + "CAERIUS205", + "CAERIUS206", + "CAERIUS207", + "CAERIUS208", + "CAERIUS210" + ], + supportedIds); } [Fact] diff --git a/Tests/CaeriusNet.Analyzer.Tests/AutoContractsManifestAnalyzerTests.cs b/Tests/CaeriusNet.Analyzer.Tests/AutoContractsManifestAnalyzerTests.cs index 316e899..34d213a 100644 --- a/Tests/CaeriusNet.Analyzer.Tests/AutoContractsManifestAnalyzerTests.cs +++ b/Tests/CaeriusNet.Analyzer.Tests/AutoContractsManifestAnalyzerTests.cs @@ -2,6 +2,15 @@ namespace CaeriusNet.Analyzer.Tests; public sealed class AutoContractsManifestAnalyzerTests { + private const string ValidEmptyManifest = """ + { + "version": 1, + "namespace": "Consumer.Contracts", + "tableTypes": [], + "procedures": [] + } + """; + [Fact] public void EmptyManifest_Reports_CAERIUS200() { @@ -12,6 +21,35 @@ public void EmptyManifest_Reports_CAERIUS200() AssertDiagnostic(diagnostics, "CAERIUS200", DiagnosticSeverity.Error); } + [Fact] + public void CustomManifestPath_WithMetadata_Is_Consumed_And_Satisfies_Pull_Mode() + { + var diagnostics = AnalyzerTestHelper.RunAnalyzer( + "namespace Consumer;", + new Dictionary(StringComparer.Ordinal) + { + ["build_property.CaeriusContractsMode"] = " Pull " + }, + Manifest(ValidEmptyManifest, "generated.contracts.json", isManifest: true)); + + Assert.DoesNotContain(diagnostics, + diagnostic => diagnostic.Id.StartsWith("CAERIUS2", StringComparison.Ordinal)); + } + + [Fact] + public void CustomManifestPath_WithoutMetadata_Is_Ignored() + { + var diagnostics = AnalyzerTestHelper.RunAnalyzer( + "namespace Consumer;", + new Dictionary(StringComparer.Ordinal) + { + ["build_property.CaeriusContractsMode"] = "Pull" + }, + Manifest(ValidEmptyManifest, "generated.contracts.json")); + + AssertDiagnostic(diagnostics, "CAERIUS200", DiagnosticSeverity.Error); + } + [Fact] public void PullModeWithoutManifest_Reports_CAERIUS200() { @@ -63,6 +101,33 @@ public void InvalidManifest_Reports_CAERIUS210_FromAnalyzer() AssertDiagnostic(diagnostics, "CAERIUS210", DiagnosticSeverity.Warning); } + [Fact] + public void EmptyJsonObject_Reports_CAERIUS210_FromAnalyzer() + { + var diagnostics = AnalyzerTestHelper.RunAnalyzer( + "namespace Consumer;", + Manifest("{}")); + + AssertDiagnostic(diagnostics, "CAERIUS210", DiagnosticSeverity.Warning); + } + + [Fact] + public void UnsupportedManifestVersion_Reports_CAERIUS210() + { + var diagnostics = AnalyzerTestHelper.RunAnalyzer( + "namespace Consumer;", + Manifest(""" + { + "version": 2, + "namespace": "Consumer.Contracts", + "tableTypes": [], + "procedures": [] + } + """)); + + AssertDiagnostic(diagnostics, "CAERIUS210", DiagnosticSeverity.Warning); + } + [Fact] public void NoResultSet_Reports_CAERIUS205() { @@ -142,6 +207,35 @@ public void ContractShapeProblems_ReportExpectedDiagnostics() AssertDiagnostic(diagnostics, "CAERIUS208", DiagnosticSeverity.Warning); } + [Fact] + public void ManifestDiagnostics_Are_Reported_On_AdditionalFile_Location() + { + var diagnostics = AnalyzerTestHelper.RunAnalyzer( + "namespace Consumer;", + Manifest(""" + { + "version": 1, + "namespace": "Consumer.Contracts", + "tableTypes": [ + { + "schema": "dbo", + "name": "InputRows", + "clrName": "InputRowsTvp", + "columns": [ + { "ordinal": 1, "name": "Payload", "sqlType": "xml", "clrType": "string", "nullable": false } + ], + "contractHash": "sha256:table" + } + ], + "procedures": [] + } + """, "contracts/custom.contracts.json", isManifest: true)); + + var diagnostic = Assert.Single(diagnostics, diagnostic => diagnostic.Id == "CAERIUS203"); + Assert.NotEqual(Location.None, diagnostic.Location); + Assert.Equal("contracts/custom.contracts.json", diagnostic.Location.GetLineSpan().Path); + } + [Fact] public void NoManifestAdditionalFile_Reports_NoAutoContractsDiagnostics() { @@ -151,9 +245,20 @@ public void NoManifestAdditionalFile_Reports_NoAutoContractsDiagnostics() diagnostic => diagnostic.Id.StartsWith("CAERIUS2", StringComparison.Ordinal)); } - private static TestAdditionalText Manifest(string json) + private static TestAdditionalText Manifest( + string json, + string path = "caerius.contracts.json", + bool? isManifest = null) { - return new TestAdditionalText("caerius.contracts.json", json); + var options = isManifest.HasValue + ? new Dictionary(StringComparer.Ordinal) + { + ["build_metadata.AdditionalFiles.CaeriusContractManifest"] = + isManifest.Value ? "true" : "false" + } + : null; + + return new TestAdditionalText(path, json, options); } private static void AssertDiagnostic( diff --git a/Tests/CaeriusNet.Analyzer.Tests/Utilities/AnalyzerTestHelper.cs b/Tests/CaeriusNet.Analyzer.Tests/Utilities/AnalyzerTestHelper.cs index 1132e90..4a821c9 100644 --- a/Tests/CaeriusNet.Analyzer.Tests/Utilities/AnalyzerTestHelper.cs +++ b/Tests/CaeriusNet.Analyzer.Tests/Utilities/AnalyzerTestHelper.cs @@ -44,7 +44,7 @@ internal static IReadOnlyList RunAnalyzer( var analyzerOptions = new AnalyzerOptions( additionalTexts.ToImmutableArray(), - new TestAnalyzerConfigOptionsProvider(globalOptions)); + new TestAnalyzerConfigOptionsProvider(globalOptions, additionalTexts)); var diagnostics = compilation.WithAnalyzers( ImmutableArray.Create( new GeneratorUsageAnalyzer(), @@ -104,9 +104,12 @@ private static IReadOnlyList BuildMetadataReferences() } internal sealed class TestAnalyzerConfigOptionsProvider( - IReadOnlyDictionary? globalOptions) : AnalyzerConfigOptionsProvider + IReadOnlyDictionary? globalOptions, + IReadOnlyList? additionalTexts = null) : AnalyzerConfigOptionsProvider { private static readonly AnalyzerConfigOptions EmptyOptions = new TestAnalyzerConfigOptions(null); + private readonly IReadOnlyDictionary> _additionalOptions = + BuildAdditionalOptions(additionalTexts); public override AnalyzerConfigOptions GlobalOptions { get; } = new TestAnalyzerConfigOptions(globalOptions); @@ -118,7 +121,23 @@ public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) { - return EmptyOptions; + return _additionalOptions.TryGetValue(textFile.Path, out var options) + ? new TestAnalyzerConfigOptions(options) + : EmptyOptions; + } + + private static IReadOnlyDictionary> BuildAdditionalOptions( + IReadOnlyList? additionalTexts) + { + if (additionalTexts is null || additionalTexts.Count == 0) + return new Dictionary>(StringComparer.Ordinal); + + var options = new Dictionary>(StringComparer.Ordinal); + foreach (var additionalText in additionalTexts) + if (additionalText is TestAdditionalText testAdditionalText && testAdditionalText.Options.Count > 0) + options[testAdditionalText.Path] = testAdditionalText.Options; + + return options; } } @@ -138,9 +157,14 @@ public override bool TryGetValue(string key, out string value) } } -internal sealed class TestAdditionalText(string path, string text) : AdditionalText +internal sealed class TestAdditionalText( + string path, + string text, + IReadOnlyDictionary? options = null) : AdditionalText { public override string Path { get; } = path; + internal IReadOnlyDictionary Options { get; } = + options ?? new Dictionary(StringComparer.Ordinal); public override SourceText GetText(CancellationToken cancellationToken = default) { diff --git a/Tests/CaeriusNet.Generator.Tests/AutoContracts/AutoContractsSourceGeneratorTests.cs b/Tests/CaeriusNet.Generator.Tests/AutoContracts/AutoContractsSourceGeneratorTests.cs index f2cd0f1..6c11965 100644 --- a/Tests/CaeriusNet.Generator.Tests/AutoContracts/AutoContractsSourceGeneratorTests.cs +++ b/Tests/CaeriusNet.Generator.Tests/AutoContracts/AutoContractsSourceGeneratorTests.cs @@ -96,6 +96,8 @@ public void ValidManifest_Generates_Procedure_Tvp_Dto_And_Builder_Extensions() Assert.Contains("public static string ByParameters(UserRetrieveByIdsParameters parameters)", generated); Assert.Contains("ReadOnlyMemory Ids,", generated); Assert.Contains("bool IncludeDisabled", generated); + Assert.Contains("public static SqlMetaData[] Metadata => (SqlMetaData[])_metadata.Clone();", generated); + Assert.Contains("AppendString(hash, \"sha256:table\");", generated); Assert.Contains("return builder.WithParameters(new UserRetrieveByIdsParameters(Ids, IncludeDisabled));", generated); Assert.Contains("[AutoContractGenerateTvp", generated); @@ -136,6 +138,59 @@ public void NonManifestAdditionalFile_Is_Ignored() Assert.Empty(result.GeneratedTrees); } + [Fact] + public void CustomManifestPath_WithMetadata_Is_Consumed() + { + var result = SourceGeneratorTestHelper.RunGenerator( + "namespace Consumer;", + Manifest(ValidManifest, "generated.contracts.json", isManifest: true)); + + Assert.Single(result.GeneratedTrees); + } + + [Fact] + public void ManifestFileName_WithMetadataFalse_Is_Ignored() + { + var result = SourceGeneratorTestHelper.RunGenerator( + "namespace Consumer;", + Manifest(ValidManifest, isManifest: false)); + + Assert.Empty(result.GeneratedTrees); + } + + [Fact] + public void MultipleManifests_WithSameNamespace_Use_Distinct_HintNames() + { + var result = SourceGeneratorTestHelper.RunGenerator( + "namespace Consumer;", + Manifest(ValidManifest, "a/caerius.contracts.json"), + Manifest(""" + { + "version": 1, + "namespace": "Consumer.Contracts", + "tableTypes": [], + "procedures": [ + { + "schema": "dbo", + "name": "CountUsers", + "clrName": "CountUsersProcedure", + "parametersClrName": "CountUsersParameters", + "parameters": [], + "resultSet": { "status": "None", "columns": [] }, + "contractHash": "sha256:count" + } + ] + } + """, "b/caerius.contracts.json")); + + Assert.Equal(2, result.GeneratedTrees.Length); + + var hintNames = result.GeneratedTrees + .Select(tree => Path.GetFileName(tree.FilePath)) + .ToArray(); + Assert.Equal(2, hintNames.Distinct(StringComparer.Ordinal).Count()); + } + [Fact] public void ScalarParameterFacets_Are_Emitted() { @@ -215,8 +270,65 @@ public void UndeterminedResultSet_Is_Not_Emitted_By_Generator() Assert.Empty(result.GeneratedTrees); } - private static TestAdditionalText Manifest(string json) + [Fact] + public void UnsupportedManifestVersion_Is_Not_Emitted_By_Generator() + { + var result = SourceGeneratorTestHelper.RunGenerator( + "namespace Consumer;", + Manifest(""" + { + "version": 2, + "namespace": "Consumer.Contracts", + "tableTypes": [], + "procedures": [] + } + """)); + + Assert.Empty(result.Diagnostics); + Assert.Empty(result.GeneratedTrees); + } + + [Fact] + public void ManifestWithMemberNameCollision_Is_Not_Emitted_By_Generator() + { + var result = SourceGeneratorTestHelper.RunGenerator( + "namespace Consumer;", + Manifest(""" + { + "version": 1, + "namespace": "Consumer.Contracts", + "tableTypes": [ + { + "schema": "dbo", + "name": "CollisionRows", + "clrName": "CollisionRowsTvp", + "columns": [ + { "ordinal": 1, "name": "User-Id", "sqlType": "int", "clrType": "int", "nullable": false }, + { "ordinal": 2, "name": "User_Id", "sqlType": "int", "clrType": "int", "nullable": false } + ], + "contractHash": "sha256:table" + } + ], + "procedures": [] + } + """)); + + Assert.Empty(result.GeneratedTrees); + } + + private static TestAdditionalText Manifest( + string json, + string path = "caerius.contracts.json", + bool? isManifest = null) { - return new TestAdditionalText("caerius.contracts.json", json); + var options = isManifest.HasValue + ? new Dictionary(StringComparer.Ordinal) + { + ["build_metadata.AdditionalFiles.CaeriusContractManifest"] = + isManifest.Value ? "true" : "false" + } + : null; + + return new TestAdditionalText(path, json, options); } } diff --git a/Tests/CaeriusNet.Generator.Tests/Helpers/TypeDetectorTests.cs b/Tests/CaeriusNet.Generator.Tests/Helpers/TypeDetectorTests.cs index 85be906..c6d30fe 100644 --- a/Tests/CaeriusNet.Generator.Tests/Helpers/TypeDetectorTests.cs +++ b/Tests/CaeriusNet.Generator.Tests/Helpers/TypeDetectorTests.cs @@ -32,28 +32,4 @@ public void GetReaderMethodForSqlType_Returns_Expected_Method(string sqlType, st Assert.Equal(expectedMethod, actual); } - - [Theory] - [InlineData("System.DateOnly", true)] - [InlineData("DateOnly", true)] - [InlineData("System.TimeOnly", true)] - [InlineData("TimeOnly", true)] - [InlineData("byte[]", true)] - [InlineData("System.Byte[]", true)] - [InlineData("System.Nullable", true)] - [InlineData("System.Nullable", true)] - [InlineData("System.Nullable", true)] - [InlineData("System.Half", true)] - [InlineData("Half", true)] - [InlineData("System.Int32", false)] - [InlineData("System.String", false)] - [InlineData("System.Guid", false)] - [InlineData("System.DateTime", false)] - [InlineData("System.Nullable", false)] - public void RequiresSpecialConversion_Returns_Expected_Result(string typeName, bool expected) - { - var actual = TypeDetector.RequiresSpecialConversion(typeName); - - Assert.Equal(expected, actual); - } } diff --git a/Tests/CaeriusNet.Generator.Tests/Utilities/SourceGeneratorTestHelper.cs b/Tests/CaeriusNet.Generator.Tests/Utilities/SourceGeneratorTestHelper.cs index 5a8c008..ace048d 100644 --- a/Tests/CaeriusNet.Generator.Tests/Utilities/SourceGeneratorTestHelper.cs +++ b/Tests/CaeriusNet.Generator.Tests/Utilities/SourceGeneratorTestHelper.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Text; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; using Microsoft.Data.SqlClient; @@ -31,7 +32,8 @@ internal static GeneratorDriverRunResult RunGenerator( var generator = new TGenerator(); var driver = CSharpGeneratorDriver.Create(generator) .AddAdditionalTexts(additionalTexts.ToImmutableArray()) - .WithUpdatedParseOptions(parseOptions); + .WithUpdatedParseOptions(parseOptions) + .WithUpdatedAnalyzerConfigOptions(new TestAnalyzerConfigOptionsProvider(additionalTexts)); return driver.RunGenerators(compilation).GetRunResult(); } @@ -57,7 +59,8 @@ internal static (GeneratorDriverRunResult RunResult, Compilation Compilation) var generator = new TGenerator(); var driver = CSharpGeneratorDriver.Create(generator) .AddAdditionalTexts(additionalTexts.ToImmutableArray()) - .WithUpdatedParseOptions(parseOptions); + .WithUpdatedParseOptions(parseOptions) + .WithUpdatedAnalyzerConfigOptions(new TestAnalyzerConfigOptionsProvider(additionalTexts)); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _); return (driver.GetRunResult(), outputCompilation); @@ -118,9 +121,66 @@ private static IReadOnlyList BuildMetadataReferences() } } -internal sealed class TestAdditionalText(string path, string text) : AdditionalText +internal sealed class TestAnalyzerConfigOptionsProvider( + IReadOnlyList? additionalTexts = null) : AnalyzerConfigOptionsProvider +{ + private static readonly AnalyzerConfigOptions EmptyOptions = new TestAnalyzerConfigOptions(null); + private readonly IReadOnlyDictionary> _additionalOptions = + BuildAdditionalOptions(additionalTexts); + + public override AnalyzerConfigOptions GlobalOptions => EmptyOptions; + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) + { + return EmptyOptions; + } + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) + { + return _additionalOptions.TryGetValue(textFile.Path, out var options) + ? new TestAnalyzerConfigOptions(options) + : EmptyOptions; + } + + private static IReadOnlyDictionary> BuildAdditionalOptions( + IReadOnlyList? additionalTexts) + { + if (additionalTexts is null || additionalTexts.Count == 0) + return new Dictionary>(StringComparer.Ordinal); + + var options = new Dictionary>(StringComparer.Ordinal); + foreach (var additionalText in additionalTexts) + if (additionalText is TestAdditionalText testAdditionalText && testAdditionalText.Options.Count > 0) + options[testAdditionalText.Path] = testAdditionalText.Options; + + return options; + } +} + +internal sealed class TestAnalyzerConfigOptions( + IReadOnlyDictionary? options) : AnalyzerConfigOptions +{ + public override bool TryGetValue(string key, out string value) + { + if (options is not null && options.TryGetValue(key, out var found)) + { + value = found; + return true; + } + + value = string.Empty; + return false; + } +} + +internal sealed class TestAdditionalText( + string path, + string text, + IReadOnlyDictionary? options = null) : AdditionalText { public override string Path { get; } = path; + internal IReadOnlyDictionary Options { get; } = + options ?? new Dictionary(StringComparer.Ordinal); public override SourceText GetText(CancellationToken cancellationToken = default) { diff --git a/Tests/CaeriusNet.Packaging.Tests/AutoContractsPackagingTests.cs b/Tests/CaeriusNet.Packaging.Tests/AutoContractsPackagingTests.cs index 534bc2e..d32cc8c 100644 --- a/Tests/CaeriusNet.Packaging.Tests/AutoContractsPackagingTests.cs +++ b/Tests/CaeriusNet.Packaging.Tests/AutoContractsPackagingTests.cs @@ -75,6 +75,9 @@ public async Task PackageCanBeConsumedAsBuildTransitivePackageReference() Assert.True(packResult.ExitCode == 0, packResult.ToString()); + var package = Assert.Single(Directory.EnumerateFiles(packageOutput, "CaeriusNet.SqlServer.Contracts.*.nupkg")); + var packageVersion = GetToolPackageVersion(package); + await File.WriteAllTextAsync( consumerProject, $$""" @@ -86,7 +89,7 @@ await File.WriteAllTextAsync( @@ -136,6 +139,95 @@ await File.WriteAllTextAsync( Assert.Contains("CaeriusNet.SqlServer.Contracts.dll", parts[1], StringComparison.Ordinal); } + [Fact] + public async Task PackageReferencePullHonorsProjectDefinedModeAndOutput() + { + var repoRoot = FindRepoRoot(); + var toolSource = Path.Combine(repoRoot, "Tools", "CaeriusNet.SqlServer.Contracts"); + + using var temp = new TemporaryDirectory(); + var toolCopy = Path.Combine(temp.Path, "tool"); + var packageOutput = Path.Combine(temp.Path, "packages"); + var consumerDirectory = Path.Combine(temp.Path, "consumer"); + var consumerProject = Path.Combine(consumerDirectory, "Consumer.csproj"); + var packageCache = Path.Combine(temp.Path, "nuget-packages"); + var manifest = Path.Combine(consumerDirectory, "generated.contracts.json"); + var stateOutput = Path.Combine(consumerDirectory, "contracts-state.txt"); + var invocationLog = Path.Combine(consumerDirectory, "fake-tool.log"); + CopyToolProject(toolSource, toolCopy); + Directory.CreateDirectory(packageOutput); + Directory.CreateDirectory(consumerDirectory); + + var packResult = await RunDotnetAsync( + toolCopy, + "pack", + "CaeriusNet.SqlServer.Contracts.csproj", + "--configuration", + "Release", + "--output", + packageOutput); + + Assert.True(packResult.ExitCode == 0, packResult.ToString()); + + var package = Assert.Single(Directory.EnumerateFiles(packageOutput, "CaeriusNet.SqlServer.Contracts.*.nupkg")); + var packageVersion = GetToolPackageVersion(package); + var fakeTool = await CreateFakeContractsToolAsync(temp.Path); + + await File.WriteAllTextAsync( + consumerProject, + $$""" + + + net10.0 + Pull + {{Escape(manifest)}} + dotnet "{{Escape(fakeTool)}}" + + + + + + + + + + + + """); + + var buildResult = await RunDotnetAsync( + consumerDirectory, + new Dictionary + { + ["CAERIUS_FAKE_TOOL_LOG"] = invocationLog, + ["NUGET_PACKAGES"] = packageCache + }, + "build", + consumerProject, + "--configuration", + "Release", + "--source", + packageOutput); + + Assert.True(buildResult.ExitCode == 0, buildResult.ToString()); + Assert.True(File.Exists(manifest), $"Expected generated manifest at {manifest}."); + + var log = await File.ReadAllTextAsync(invocationLog); + Assert.Contains("pull", log, StringComparison.Ordinal); + Assert.Contains(manifest, log, StringComparison.Ordinal); + + var state = string.Join(Environment.NewLine, await File.ReadAllLinesAsync(stateOutput)); + Assert.Contains("PULL", state, StringComparison.Ordinal); + Assert.Contains(Path.GetFullPath(manifest), state, StringComparison.OrdinalIgnoreCase); + Assert.Contains("|true", state, StringComparison.Ordinal); + } + [Fact] public async Task TargetsAddManifestCreatedDuringMsbuildExecution() { @@ -207,7 +299,7 @@ await File.WriteAllTextAsync( - + @@ -297,6 +389,70 @@ private static string Escape(string value) return SecurityElement.Escape(value) ?? value; } + private static string GetToolPackageVersion(string package) + { + var fileName = Path.GetFileNameWithoutExtension(package); + const string packagePrefix = "CaeriusNet.SqlServer.Contracts."; + Assert.True(fileName.StartsWith(packagePrefix, StringComparison.Ordinal), fileName); + return fileName[packagePrefix.Length..]; + } + + private static async Task CreateFakeContractsToolAsync(string root) + { + var projectDirectory = Path.Combine(root, "fake-tool"); + var project = Path.Combine(projectDirectory, "FakeContractsTool.csproj"); + Directory.CreateDirectory(projectDirectory); + + await File.WriteAllTextAsync( + project, + """ + + + Exe + net10.0 + enable + enable + + + """); + + await File.WriteAllTextAsync( + Path.Combine(projectDirectory, "Program.cs"), + """ + string? output = null; + for (var index = 0; index < args.Length - 1; index++) + if (args[index] is "--output" or "--manifest") + output = args[index + 1]; + + output ??= Path.Combine(Directory.GetCurrentDirectory(), "caerius.contracts.json"); + var directory = Path.GetDirectoryName(Path.GetFullPath(output)); + if (!string.IsNullOrEmpty(directory)) + Directory.CreateDirectory(directory); + + File.WriteAllText(output, "{ \"version\": 1, \"namespace\": \"CaeriusNet.Generated\", \"database\": { \"name\": \"fake\", \"serverVersion\": \"fake\", \"compatibilityLevel\": 0 }, \"tableTypes\": [], \"procedures\": [] }"); + + var log = Environment.GetEnvironmentVariable("CAERIUS_FAKE_TOOL_LOG"); + if (!string.IsNullOrWhiteSpace(log)) + { + var logDirectory = Path.GetDirectoryName(Path.GetFullPath(log)); + if (!string.IsNullOrEmpty(logDirectory)) + Directory.CreateDirectory(logDirectory); + + File.AppendAllText(log, string.Join(" ", args) + Environment.NewLine); + } + """); + + var result = await RunDotnetAsync( + projectDirectory, + "build", + project, + "--configuration", + "Release"); + + Assert.True(result.ExitCode == 0, result.ToString()); + return Path.Combine(projectDirectory, "bin", "Release", "net10.0", "FakeContractsTool.dll"); + } + private static void CopyToolProject(string source, string destination) { Directory.CreateDirectory(destination); diff --git a/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTests.cs b/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTests.cs index 9694743..704d3f1 100644 --- a/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTests.cs +++ b/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTests.cs @@ -102,7 +102,12 @@ public void AddParameter_Multiple_Parameters_All_Appear_In_Span() [Theory] [InlineData("")] - public void AddParameter_Empty_Name_Throws(string paramName) + [InlineData(" ")] + [InlineData("@")] + [InlineData("@@Id")] + [InlineData(" Id")] + [InlineData("Id ")] + public void AddParameter_Invalid_Name_Throws(string paramName) { var builder = new StoredProcedureParametersBuilder("dbo", "sp_Test"); @@ -141,6 +146,27 @@ public void AddInMemoryCache_Sets_CacheType_And_Key_And_Expiration() Assert.Equal(expiration, sp.CacheExpiration); } + [Theory] + [InlineData("")] + [InlineData(" ")] + public void AddInMemoryCache_Invalid_Key_Throws(string cacheKey) + { + var builder = new StoredProcedureParametersBuilder("dbo", "sp_Test"); + + Assert.Throws(() => builder.AddInMemoryCache(cacheKey, TimeSpan.FromMinutes(1))); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void AddInMemoryCache_NonPositive_Expiration_Throws(int seconds) + { + var builder = new StoredProcedureParametersBuilder("dbo", "sp_Test"); + + Assert.Throws(() => + builder.AddInMemoryCache("my_key", TimeSpan.FromSeconds(seconds))); + } + [Fact] public void AddFrozenCache_Sets_CacheType_And_Null_Expiration() { @@ -153,6 +179,16 @@ public void AddFrozenCache_Sets_CacheType_And_Null_Expiration() Assert.Null(sp.CacheExpiration); } + [Theory] + [InlineData("")] + [InlineData(" ")] + public void AddFrozenCache_Invalid_Key_Throws(string cacheKey) + { + var builder = new StoredProcedureParametersBuilder("dbo", "sp_Test"); + + Assert.Throws(() => builder.AddFrozenCache(cacheKey)); + } + [Fact] public void AddRedisCache_With_Expiration_Sets_All_Properties() { @@ -177,6 +213,27 @@ public void AddRedisCache_Without_Expiration_Sets_Null_Expiration() Assert.Null(sp.CacheExpiration); } + [Theory] + [InlineData("")] + [InlineData(" ")] + public void AddRedisCache_Invalid_Key_Throws(string cacheKey) + { + var builder = new StoredProcedureParametersBuilder("dbo", "sp_Test"); + + Assert.Throws(() => builder.AddRedisCache(cacheKey)); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void AddRedisCache_NonPositive_Expiration_Throws(int seconds) + { + var builder = new StoredProcedureParametersBuilder("dbo", "sp_Test"); + + Assert.Throws(() => + builder.AddRedisCache("redis_key", TimeSpan.FromSeconds(seconds))); + } + [Fact] public void Build_No_Cache_Methods_Leaves_Cache_Properties_Null() { @@ -206,6 +263,46 @@ public void AddParameter_CorrectParameterName_SetInSpan() Assert.Equal("@UserId", sp.GetParametersSpan()[0].ParameterName); } + [Fact] + public void AddParameter_Name_Without_Prefix_Is_Normalized() + { + var sp = new StoredProcedureParametersBuilder("dbo", "sp_Test") + .AddParameter("UserId", 42, SqlDbType.Int) + .Build(); + + Assert.Equal("@UserId", sp.GetParametersSpan()[0].ParameterName); + } + + [Fact] + public void AddParameter_With_Facets_And_Direction_Preserves_Metadata() + { + var sp = new StoredProcedureParametersBuilder("dbo", "sp_Test") + .AddParameter( + "@Amount", + 12.34m, + SqlDbType.Decimal, + precision: 19, + scale: 4, + direction: ParameterDirection.InputOutput) + .Build(); + + var parameter = sp.GetParametersSpan()[0]; + + Assert.Equal(19, parameter.Precision); + Assert.Equal(4, parameter.Scale); + Assert.Equal(ParameterDirection.InputOutput, parameter.Direction); + } + + [Fact] + public void AddParameter_With_Size_Preserves_Metadata() + { + var sp = new StoredProcedureParametersBuilder("dbo", "sp_Test") + .AddParameter("@Name", "ari", SqlDbType.NVarChar, size: 64) + .Build(); + + Assert.Equal(64, sp.GetParametersSpan()[0].Size); + } + [Fact] public void AddParameter_CorrectSqlDbType_SetInSpan() { @@ -300,6 +397,35 @@ public void AddTvpParameter_Sets_TypeName_Correctly() Assert.Equal(TestTvpItem.TvpTypeName, sp.GetParametersSpan()[0].TypeName); } + [Fact] + public void AddTvpParameter_Name_Without_Prefix_Is_Normalized() + { + var items = new List { new(1) }; + var sp = new StoredProcedureParametersBuilder("dbo", "sp_Test") + .AddTvpParameter("Ids", items) + .Build(); + + Assert.Equal("@Ids", sp.GetParametersSpan()[0].ParameterName); + } + + [Fact] + public void AddTvpParameter_ReadOnlyList_Does_Not_Materialize_During_Build() + { + var items = new CountingReadOnlyList([new TestTvpItem(1), new TestTvpItem(2)]); + + var sp = new StoredProcedureParametersBuilder("dbo", "sp_Test") + .AddTvpParameter("@Ids", items) + .Build(); + + Assert.Equal(0, items.EnumerationCount); + + var records = Assert.IsAssignableFrom>(sp.GetParametersSpan()[0].Value); + var values = records.Select(record => record.GetInt32(0)).ToArray(); + + Assert.Equal(1, items.EnumerationCount); + Assert.Equal([1, 2], values); + } + [Fact] public void AddTvpParameter_Multiple_TVPs_Adds_Multiple_Parameters() { @@ -366,4 +492,24 @@ public void Constructor_EmptyProcedureName_Throws() Assert.Throws(() => builder.Build()); } + + private sealed class CountingReadOnlyList(IReadOnlyList items) : IReadOnlyList + { + public int EnumerationCount { get; private set; } + + public int Count => items.Count; + + public T this[int index] => items[index]; + + public IEnumerator GetEnumerator() + { + EnumerationCount++; + return items.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } } diff --git a/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTypedTests.cs b/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTypedTests.cs index b72e23b..0032ce5 100644 --- a/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTypedTests.cs +++ b/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTypedTests.cs @@ -35,7 +35,7 @@ public void Build_With_Bound_Generated_Scalar_Parameters_Returns_Typed_Parameter { var sp = StoredProcedureParametersBuilder .Create(16) - .AddGeneratedParameter(1, "@Id", 42, SqlDbType.Int) + .AddGeneratedParameter(1, "Id", 42, SqlDbType.Int) .MarkGeneratedParametersBound() .AddRedisCache("test:key", TimeSpan.FromMinutes(5)) .Build(); @@ -52,6 +52,16 @@ public void Build_With_Bound_Generated_Scalar_Parameters_Returns_Typed_Parameter Assert.Equal(42, parameters[0].Value); } + [Fact] + public void AddGeneratedParameter_Invalid_Name_Throws() + { + var builder = StoredProcedureParametersBuilder + .Create(16); + + Assert.Throws(() => + builder.AddGeneratedParameter(1, "@@Id", 42, SqlDbType.Int)); + } + [Fact] public void Create_With_Generated_Parameters_Binds_In_One_Call() { @@ -87,7 +97,7 @@ public void AddGeneratedParameter_Preserves_Sql_Facets() [Fact] public void AddGeneratedParameter_With_Configured_SqlParameter_Preserves_Facets() { - var parameter = new SqlParameter("@Amount", SqlDbType.Decimal) + var parameter = new SqlParameter("Amount", SqlDbType.Decimal) { Value = 12.50m, Precision = 18, @@ -109,6 +119,21 @@ public void AddGeneratedParameter_With_Configured_SqlParameter_Preserves_Facets( Assert.Equal(4, actual.Scale); } + [Fact] + public void AddGeneratedParameter_With_Configured_SqlParameter_Does_Not_Mutate_Input() + { + var parameter = new SqlParameter("Id", SqlDbType.Int) { Value = 42 }; + + var sp = StoredProcedureParametersBuilder + .Create(16) + .AddGeneratedParameter(1, parameter) + .MarkGeneratedParametersBound() + .Build(); + + Assert.Equal("Id", parameter.ParameterName); + Assert.Equal("@Id", sp.GetParametersSpan()[0].ParameterName); + } + [Fact] public void AddGeneratedParameter_With_Configured_SqlParameter_Converts_Null_Value_To_DBNull() { @@ -136,7 +161,7 @@ public void AddGeneratedTvpParameter_Preserves_Structured_TypeName_And_Streams_R .Create(16) .AddGeneratedTvpParameter( 1, - "@Ids", + "Ids", "dbo.UserIdList", [new SqlMetaData("UserId", SqlDbType.Int)], rows, @@ -149,10 +174,27 @@ [new SqlMetaData("UserId", SqlDbType.Int)], var values = records.Select(record => record.GetInt32(0)).ToArray(); Assert.Equal(SqlDbType.Structured, parameter.SqlDbType); + Assert.Equal("@Ids", parameter.ParameterName); Assert.Equal("dbo.UserIdList", parameter.TypeName); Assert.Equal([1, 2], values); } + [Fact] + public void AddGeneratedTvpParameter_Invalid_Name_Throws() + { + var builder = StoredProcedureParametersBuilder + .Create(16); + + Assert.Throws(() => + builder.AddGeneratedTvpParameter( + 1, + "@@Ids", + "dbo.UserIdList", + [new SqlMetaData("UserId", SqlDbType.Int)], + ReadOnlyMemory.Empty, + static (_, _) => { })); + } + [Fact] public void AddGeneratedTvpParameter_Empty_Metadata_Throws() { diff --git a/Tests/CaeriusNet.Tests/Caches/CaeriusNetCacheInvalidationTests.cs b/Tests/CaeriusNet.Tests/Caches/CaeriusNetCacheInvalidationTests.cs index 32c6f9b..4cf8cd5 100644 --- a/Tests/CaeriusNet.Tests/Caches/CaeriusNetCacheInvalidationTests.cs +++ b/Tests/CaeriusNet.Tests/Caches/CaeriusNetCacheInvalidationTests.cs @@ -68,11 +68,13 @@ public async Task Cache_RemoveAsync_AcrossAllTiers_RemovesFromBothLocalTiers() FrozenCacheManager.Store(key, 1); InMemoryCacheManager.Store(key, 1, LongExpiry); - ICaeriusNetCache facade = new CaeriusNetCache(); + var redis = new RecordingRedisCacheManager(); + ICaeriusNetCache facade = new CaeriusNetCache(redis); await facade.RemoveAsync(key); Assert.False(FrozenCacheManager.TryGet(key, out _)); Assert.False(InMemoryCacheManager.TryGet(key, out _)); + Assert.Equal(new[] { key }, redis.RemovedKeys); } [Fact] @@ -89,6 +91,22 @@ public async Task Cache_RemoveAsync_TargetedTier_DoesNotTouchOtherTier() Assert.False(InMemoryCacheManager.TryGet(key, out _)); } + [Fact] + public async Task Cache_RemoveAsync_TargetedRedis_CallsRedisOnly() + { + var key = $"invalidate_redis_{Guid.NewGuid()}"; + FrozenCacheManager.Store(key, "F"); + InMemoryCacheManager.Store(key, "M", LongExpiry); + + var redis = new RecordingRedisCacheManager(); + ICaeriusNetCache facade = new CaeriusNetCache(redis); + await facade.RemoveAsync(key, CacheType.Redis); + + Assert.True(FrozenCacheManager.TryGet(key, out _)); + Assert.True(InMemoryCacheManager.TryGet(key, out _)); + Assert.Equal(new[] { key }, redis.RemovedKeys); + } + [Fact] public async Task Cache_ClearAsync_Frozen_EmptiesTier() { @@ -101,6 +119,18 @@ public async Task Cache_ClearAsync_Frozen_EmptiesTier() Assert.False(FrozenCacheManager.TryGet(key, out _)); } + [Fact] + public async Task Cache_ClearAsync_InMemory_EmptiesTier() + { + var key = $"invalidate_clear_memory_{Guid.NewGuid()}"; + InMemoryCacheManager.Store(key, 42, LongExpiry); + + ICaeriusNetCache facade = new CaeriusNetCache(); + await facade.ClearAsync(CacheType.InMemory); + + Assert.False(InMemoryCacheManager.TryGet(key, out _)); + } + [Fact] public async Task Cache_ClearAsync_Redis_Throws() { @@ -117,6 +147,56 @@ await Assert.ThrowsAsync(() => facade.RemoveAsync(null!).AsTask()); } + [Fact] + public async Task Cache_RemoveAsync_EmptyKey_Throws() + { + ICaeriusNetCache facade = new CaeriusNetCache(); + await Assert.ThrowsAsync(() => + facade.RemoveAsync(string.Empty).AsTask()); + } + + [Fact] + public async Task Cache_RemoveAsync_WhitespaceKey_Throws() + { + ICaeriusNetCache facade = new CaeriusNetCache(); + await Assert.ThrowsAsync(() => + facade.RemoveAsync(" ").AsTask()); + } + + [Fact] + public async Task Cache_RemoveAsync_CanceledToken_DoesNotTouchTiers() + { + var key = $"invalidate_canceled_{Guid.NewGuid()}"; + FrozenCacheManager.Store(key, 5); + InMemoryCacheManager.Store(key, 5, LongExpiry); + var redis = new RecordingRedisCacheManager(); + ICaeriusNetCache facade = new CaeriusNetCache(redis); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + await Assert.ThrowsAsync(() => + facade.RemoveAsync(key, cts.Token).AsTask()); + + Assert.True(FrozenCacheManager.TryGet(key, out _)); + Assert.True(InMemoryCacheManager.TryGet(key, out _)); + Assert.Empty(redis.RemovedKeys); + } + + [Fact] + public async Task Cache_ClearAsync_CanceledToken_DoesNotClearTier() + { + var key = $"invalidate_clear_canceled_{Guid.NewGuid()}"; + FrozenCacheManager.Store(key, 42); + ICaeriusNetCache facade = new CaeriusNetCache(); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + await Assert.ThrowsAsync(() => + facade.ClearAsync(CacheType.Frozen, cts.Token).AsTask()); + + Assert.True(FrozenCacheManager.TryGet(key, out _)); + } + [Fact] public async Task Cache_RemoveAsync_UnknownTierEnum_Throws() { @@ -124,4 +204,25 @@ public async Task Cache_RemoveAsync_UnknownTierEnum_Throws() await Assert.ThrowsAsync(() => facade.RemoveAsync("k", (CacheType)42).AsTask()); } + + private sealed class RecordingRedisCacheManager : IRedisCacheManager + { + public List RemovedKeys { get; } = []; + + public bool TryGet(string cacheKey, out T? value) + { + value = default; + return false; + } + + public void Store(string cacheKey, T value, TimeSpan? expiration) + where T : notnull + { + } + + public void Remove(string cacheKey) + { + RemovedKeys.Add(cacheKey); + } + } } diff --git a/Tests/CaeriusNet.Tests/Caches/FrozenCacheManagerTests.cs b/Tests/CaeriusNet.Tests/Caches/FrozenCacheManagerTests.cs index 53ed49c..b321ef5 100644 --- a/Tests/CaeriusNet.Tests/Caches/FrozenCacheManagerTests.cs +++ b/Tests/CaeriusNet.Tests/Caches/FrozenCacheManagerTests.cs @@ -134,4 +134,39 @@ public void StoreRange_SingleEntry_CanBeRetrieved() Assert.Equal(777, value); } + + [Fact] + public void StoreRange_ExistingKey_IsIdempotent_FirstValuePreserved() + { + var key = $"frozen_range_existing_{Guid.NewGuid()}"; + FrozenCacheManager.Store(key, "first"); + + FrozenCacheManager.StoreRange(new Dictionary { [key] = "second" }); + FrozenCacheManager.TryGet(key, out var value); + + Assert.Equal("first", value); + } + + [Fact] + public void StoreRange_DuplicateInputKey_PreservesFirstValue() + { + var key = $"frozen_range_duplicate_{Guid.NewGuid()}"; + var entries = new List> + { + new(key, 1), + new(key, 2) + }; + + FrozenCacheManager.StoreRange(entries); + FrozenCacheManager.TryGet(key, out var value); + + Assert.Equal(1, value); + } + + [Fact] + public void StoreRange_NullEntries_Throws() + { + Assert.Throws(() => + FrozenCacheManager.StoreRange(null!)); + } } diff --git a/Tests/CaeriusNet.Tests/Caches/FrozenCacheStateCollection.cs b/Tests/CaeriusNet.Tests/Caches/FrozenCacheStateCollection.cs index 619e116..d9cd74f 100644 --- a/Tests/CaeriusNet.Tests/Caches/FrozenCacheStateCollection.cs +++ b/Tests/CaeriusNet.Tests/Caches/FrozenCacheStateCollection.cs @@ -1,10 +1,10 @@ namespace CaeriusNet.Tests.Caches; /// -/// xUnit collection marker for all test classes that share the process-wide static -/// state. +/// xUnit collection marker for test classes that mutate process-wide static cache state. /// Tests in this collection are serialised (never run in parallel with each other), -/// preventing race conditions where Clear() wipes entries stored by a concurrent test. +/// preventing race conditions where Clear() or Configure() wipes entries or options +/// used by a concurrent test. /// [CollectionDefinition(Name)] public sealed class FrozenCacheStateCollection diff --git a/Tests/CaeriusNet.Tests/Caches/InMemoryCacheManagerTests.cs b/Tests/CaeriusNet.Tests/Caches/InMemoryCacheManagerTests.cs index 67b9922..e50b8df 100644 --- a/Tests/CaeriusNet.Tests/Caches/InMemoryCacheManagerTests.cs +++ b/Tests/CaeriusNet.Tests/Caches/InMemoryCacheManagerTests.cs @@ -1,13 +1,39 @@ +using System.Collections.Concurrent; +using Microsoft.Extensions.Caching.Memory; + namespace CaeriusNet.Tests.Caches; /// /// Unit tests for . /// Each test uses a GUID-based key to avoid interference from the shared static MemoryCache. /// +[Collection(FrozenCacheStateCollection.Name)] public sealed class InMemoryCacheManagerTests { private static readonly TimeSpan LongExpiry = TimeSpan.FromHours(1); + private static MemoryCacheOptions DefaultOptions() + { + return new MemoryCacheOptions + { + SizeLimit = null, + CompactionPercentage = 0.05, + ExpirationScanFrequency = TimeSpan.FromMinutes(2), + TrackLinkedCacheEntries = false + }; + } + + private static MemoryCacheOptions SizeLimitedOptions(long sizeLimit) + { + return new MemoryCacheOptions + { + SizeLimit = sizeLimit, + CompactionPercentage = 0.05, + ExpirationScanFrequency = TimeSpan.FromMinutes(2), + TrackLinkedCacheEntries = false + }; + } + [Fact] public void TryGet_MissingKey_Returns_False() { @@ -98,4 +124,79 @@ public void Store_ListValue_ThenTryGet_Returns_SameList() Assert.Equal(list, value); } + + [Fact] + public void Configure_WithSizeLimit_StoresEntryWithSize() + { + var key = $"memory_sizelimit_{Guid.NewGuid()}"; + + try + { + InMemoryCacheManager.Configure(SizeLimitedOptions(1)); + + InMemoryCacheManager.Store(key, "limited", LongExpiry); + var found = InMemoryCacheManager.TryGet(key, out var value); + + Assert.True(found); + Assert.Equal("limited", value); + } + finally + { + InMemoryCacheManager.Configure(DefaultOptions()); + } + } + + [Fact] + public async Task Configure_ConcurrentWithStoreAndTryGet_DoesNotThrow() + { + var errors = new ConcurrentQueue(); + var prefix = $"memory_concurrent_{Guid.NewGuid():N}"; + + try + { + var configureTask = Task.Run(() => + { + try + { + for (var i = 0; i < 250; i++) + InMemoryCacheManager.Configure(i % 2 == 0 + ? SizeLimitedOptions(4096) + : DefaultOptions()); + } + catch (Exception ex) + { + errors.Enqueue(ex); + } + }); + + var cacheTasks = Enumerable.Range(0, 4) + .Select(worker => Task.Run(() => + { + try + { + for (var i = 0; i < 500; i++) + { + var key = $"{prefix}_{worker}_{i}"; + InMemoryCacheManager.Store(key, i, LongExpiry); + InMemoryCacheManager.TryGet(key, out _); + InMemoryCacheManager.Remove(key); + } + } + catch (Exception ex) + { + errors.Enqueue(ex); + } + })) + .Append(configureTask) + .ToArray(); + + await Task.WhenAll(cacheTasks); + + Assert.Empty(errors); + } + finally + { + InMemoryCacheManager.Configure(DefaultOptions()); + } + } } diff --git a/Tests/CaeriusNet.Tests/Factories/CaeriusNetDbContextTests.cs b/Tests/CaeriusNet.Tests/Factories/CaeriusNetDbContextTests.cs index 853c6d6..6b7830f 100644 --- a/Tests/CaeriusNet.Tests/Factories/CaeriusNetDbContextTests.cs +++ b/Tests/CaeriusNet.Tests/Factories/CaeriusNetDbContextTests.cs @@ -28,6 +28,28 @@ public void Constructor_WithNullRedis_SetsNull() Assert.Null(ctx.RedisCacheManager); } + [Fact] + public async Task DbConnectionAsync_FactoryReturnsNull_Throws_InvalidOperationException() + { + var ctx = new CaeriusNetDbContext(() => null!); + + var ex = await Assert.ThrowsAsync(() => ctx.DbConnectionAsync().AsTask()); + + Assert.Contains("returned null", ex.Message); + } + + [Fact] + public async Task DbConnectionAsync_OpenAsyncThrows_Rethrows_Original_Exception() + { + var connection = new SqlConnection(); + var ctx = new CaeriusNetDbContext(() => connection); + + var ex = await Assert.ThrowsAsync(() => ctx.DbConnectionAsync().AsTask()); + + Assert.Contains("ConnectionString", ex.Message); + Assert.Equal(ConnectionState.Closed, connection.State); + } + private sealed class FakeRedisCacheManager : IRedisCacheManager { public bool TryGet(string cacheKey, out T? value) diff --git a/Tests/CaeriusNet.Tests/Helpers/ResultSetMaterializerTests.cs b/Tests/CaeriusNet.Tests/Helpers/ResultSetMaterializerTests.cs new file mode 100644 index 0000000..c72097e --- /dev/null +++ b/Tests/CaeriusNet.Tests/Helpers/ResultSetMaterializerTests.cs @@ -0,0 +1,41 @@ +namespace CaeriusNet.Tests.Helpers; + +public sealed class ResultSetMaterializerTests +{ + [Theory] + [InlineData(0, 1)] + [InlineData(1, 1)] + [InlineData(16, 16)] + public void NormalizeCapacity_Uses_At_Least_One(int capacity, int expected) + { + Assert.Equal(expected, ResultSetMaterializer.NormalizeCapacity(capacity)); + } + + [Theory] + [InlineData(0, 2)] + [InlineData(1, 2)] + [InlineData(2, 3)] + [InlineData(16, 24)] + public void GrowCapacity_Uses_One_And_A_Half_Growth(int capacity, int expected) + { + Assert.Equal(expected, ResultSetMaterializer.GrowCapacity(capacity)); + } + + [Fact] + public void GrowCapacity_Caps_At_Array_MaxLength_Before_Overflow() + { + Assert.Equal(Array.MaxLength, ResultSetMaterializer.GrowCapacity(Array.MaxLength - 1)); + } + + [Fact] + public void GrowCapacity_Throws_When_Already_At_Array_MaxLength() + { + Assert.Throws(() => ResultSetMaterializer.GrowCapacity(Array.MaxLength)); + } + + [Fact] + public void GrowCapacity_Negative_Capacity_Throws() + { + Assert.Throws(() => ResultSetMaterializer.GrowCapacity(-1)); + } +} diff --git a/Tests/CaeriusNet.Tests/Mappers/StoredProcedureParametersTests.cs b/Tests/CaeriusNet.Tests/Mappers/StoredProcedureParametersTests.cs index 898903b..b482d11 100644 --- a/Tests/CaeriusNet.Tests/Mappers/StoredProcedureParametersTests.cs +++ b/Tests/CaeriusNet.Tests/Mappers/StoredProcedureParametersTests.cs @@ -84,7 +84,7 @@ public void Constructor_No_Cache_Leaves_Cache_Properties_Null() [Fact] public void GetParametersSpan_Returns_Correct_Parameter_Name() { - var parameters = new SqlParameter[] { new("@UserId", SqlDbType.Int) }; + var parameters = new SqlParameter[] { new("UserId", SqlDbType.Int) }; var sp = new StoredProcedureParameters("dbo", "sp_Test", 16, parameters, null, null, null); var span = sp.GetParametersSpan(); @@ -127,6 +127,34 @@ public void Two_Distinct_Instances_HaveIndependent_ParameterSpans() Assert.Equal("@Name", sp2.GetParametersSpan()[0].ParameterName); } + [Fact] + public void Constructor_Clones_Parameter_Array_And_Parameters() + { + var parameter = new SqlParameter("Id", SqlDbType.Int) { Value = 42 }; + var parameters = new[] { parameter }; + + var sp = new StoredProcedureParameters("dbo", "sp_Test", 16, parameters, null, null, null); + + parameters[0] = new SqlParameter("@Other", SqlDbType.Int) { Value = 100 }; + parameter.ParameterName = "@Mutated"; + parameter.Value = 99; + + var actual = sp.GetParametersSpan()[0]; + + Assert.NotSame(parameter, actual); + Assert.Equal("@Id", actual.ParameterName); + Assert.Equal(42, actual.Value); + } + + [Fact] + public void Constructor_Invalid_Parameter_Name_Throws() + { + var parameters = new[] { new SqlParameter("@@Id", SqlDbType.Int) }; + + Assert.Throws(() => + new StoredProcedureParameters("dbo", "sp_Test", 16, parameters, null, null, null)); + } + [Fact] public void AddParametersTo_Clones_Parameters_For_Each_Command() { diff --git a/Tests/CaeriusNet.Tests/Transactions/CaeriusNetTransactionTests.cs b/Tests/CaeriusNet.Tests/Transactions/CaeriusNetTransactionTests.cs index a64d459..04ec9d6 100644 --- a/Tests/CaeriusNet.Tests/Transactions/CaeriusNetTransactionTests.cs +++ b/Tests/CaeriusNet.Tests/Transactions/CaeriusNetTransactionTests.cs @@ -109,6 +109,104 @@ public async Task RollbackAsync_With_Command_In_Flight_Throws_Deterministically( tx.ReleaseCommandSlot(); } + [Fact] + public async Task DisposeAsync_With_Command_In_Flight_Throws_Deterministically() + { + var tx = CreateStateMachineOnlyTransaction(); + tx.AcquireCommandSlot(); + + try + { + var ex = await Assert.ThrowsAsync(() => tx.DisposeAsync().AsTask()); + + Assert.Contains("command is already in flight", ex.Message); + } + finally + { + tx.ReleaseCommandSlot(); + await tx.DisposeAsync(); + } + } + + [Fact] + public async Task CommitAsync_When_Lifecycle_Fails_Poisons_Instead_Of_Marking_Committed() + { + var tx = CreateStateMachineOnlyTransaction(); + + await Assert.ThrowsAsync(() => tx.CommitAsync().AsTask()); + + var ex = await Assert.ThrowsAsync(() => tx.CommitAsync().AsTask()); + Assert.Contains("Poisoned", ex.Message); + + await tx.DisposeAsync(); + } + + [Fact] + public async Task RollbackAsync_When_Lifecycle_Fails_Poisons_Transaction() + { + var tx = CreateStateMachineOnlyTransaction(); + + await Assert.ThrowsAsync(() => tx.RollbackAsync().AsTask()); + + var ex = await Assert.ThrowsAsync(() => tx.CommitAsync().AsTask()); + Assert.Contains("Poisoned", ex.Message); + + await tx.DisposeAsync(); + } + + [Fact] + public async Task TransactionWriteCommand_InvalidOperation_Poisons_And_Releases_Slot() + { + var failure = new InvalidOperationException("Connection is not usable."); + var tx = new FailingInternalTransaction(failure); + var parameters = CreateStoredProcedureParameters(); + + var ex = await Assert.ThrowsAsync(() => + tx.ExecuteNonQueryAsync(parameters).AsTask()); + + Assert.Same(failure, ex); + Assert.True(tx.Poisoned); + Assert.Equal(1, tx.AcquireCount); + Assert.Equal(1, tx.ReleaseCount); + } + + [Fact] + public async Task TransactionWriteCommand_Cancellation_Poisons_And_Releases_Slot() + { + var failure = new OperationCanceledException("Command canceled."); + var tx = new FailingInternalTransaction(failure); + var parameters = CreateStoredProcedureParameters(); + + var ex = await Assert.ThrowsAsync(() => + tx.ExecuteAsync(parameters).AsTask()); + + Assert.Same(failure, ex); + Assert.True(tx.Poisoned); + Assert.Equal(1, tx.AcquireCount); + Assert.Equal(1, tx.ReleaseCount); + } + + [Fact] + public async Task TransactionReadCommand_InvalidOperation_Poisons_And_Releases_Slot() + { + var failure = new InvalidOperationException("Connection is not usable."); + var tx = new FailingInternalTransaction(failure); + var parameters = CreateStoredProcedureParameters(); + + var ex = await Assert.ThrowsAsync(() => + tx.FirstQueryAsync(parameters).AsTask()); + + Assert.Same(failure, ex); + Assert.True(tx.Poisoned); + Assert.Equal(1, tx.AcquireCount); + Assert.Equal(1, tx.ReleaseCount); + } + + private static StoredProcedureParameters CreateStoredProcedureParameters() + { + return new StoredProcedureParametersBuilder("dbo", "sp_Test").Build(); + } + private static ICaeriusNetTransactionInternal CreateStateMachineOnlyTransaction() { var constructor = typeof(CaeriusNetTransaction).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic) @@ -187,6 +285,54 @@ public ValueTask DisposeAsync() } } + private sealed class FailingInternalTransaction(Exception failure) : ICaeriusNetTransactionInternal + { + public int AcquireCount { get; private set; } + public int ReleaseCount { get; private set; } + public bool Poisoned { get; private set; } + public bool IsActive => !Poisoned; + public SqlConnection Connection => throw failure; + public SqlTransaction Transaction => throw failure; + + public void AcquireCommandSlot() + { + AcquireCount++; + } + + public void ReleaseCommandSlot() + { + ReleaseCount++; + } + + public void Poison() + { + Poisoned = true; + } + + public ValueTask CommitAsync(CancellationToken cancellationToken = default) + { + return default; + } + + public ValueTask RollbackAsync(CancellationToken cancellationToken = default) + { + return default; + } + + public ValueTask DisposeAsync() + { + return default; + } + } + + private sealed class TestMapper : ISpMapper + { + public static TestMapper MapFromDataReader(SqlDataReader reader) + { + return new TestMapper(); + } + } + private sealed class ThrowingDbContext : ICaeriusNetDbContext { public IRedisCacheManager? RedisCacheManager => null; diff --git a/Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj b/Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj index cf9b414..af0abbf 100644 --- a/Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj +++ b/Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj @@ -26,8 +26,8 @@ - - + + diff --git a/Tools/CaeriusNet.SqlServer.Contracts/Program.cs b/Tools/CaeriusNet.SqlServer.Contracts/Program.cs index da77aef..4724fec 100644 --- a/Tools/CaeriusNet.SqlServer.Contracts/Program.cs +++ b/Tools/CaeriusNet.SqlServer.Contracts/Program.cs @@ -23,6 +23,15 @@ public static async Task Main(string[] args) return 0; } + using var cancellation = new CancellationTokenSource(); + ConsoleCancelEventHandler cancelHandler = (_, eventArgs) => + { + eventArgs.Cancel = true; + cancellation.Cancel(); + }; + + Console.CancelKeyPress += cancelHandler; + try { var command = args[0]; @@ -30,37 +39,42 @@ public static async Task Main(string[] args) return command switch { - "pull" => await PullAsync(options).ConfigureAwait(false), - "verify" => await VerifyAsync(options).ConfigureAwait(false), + "pull" => await PullAsync(options, cancellation.Token).ConfigureAwait(false), + "verify" => await VerifyAsync(options, cancellation.Token).ConfigureAwait(false), _ => UnknownCommand(command) }; } + catch (OperationCanceledException) + { + Console.Error.WriteLine("Operation canceled."); + return 130; + } catch (Exception ex) { Console.Error.WriteLine(ex.Message); return 1; } + finally + { + Console.CancelKeyPress -= cancelHandler; + } } - private static async Task PullAsync(CommandLineOptions options) + private static async Task PullAsync(CommandLineOptions options, CancellationToken cancellationToken) { var output = options.Output ?? options.Manifest ?? DefaultManifestPath; var diagnostics = new ContractDiagnosticSink(); - var manifest = await SqlServerContractDiscoverer.DiscoverAsync(options, diagnostics).ConfigureAwait(false); + var manifest = await SqlServerContractDiscoverer.DiscoverAsync(options, diagnostics, cancellationToken) + .ConfigureAwait(false); ContractDiagnosticWriter.Write(diagnostics); - var json = JsonSerializer.Serialize(manifest, ManifestJsonOptions); - var directory = Path.GetDirectoryName(Path.GetFullPath(output)); - if (!string.IsNullOrEmpty(directory)) - Directory.CreateDirectory(directory); - - await File.WriteAllTextAsync(output, json + Environment.NewLine, Encoding.UTF8).ConfigureAwait(false); + await WriteManifestAsync(output, manifest, cancellationToken).ConfigureAwait(false); Console.WriteLine($"Wrote {output}"); return diagnostics.HasErrors ? 1 : 0; } - private static async Task VerifyAsync(CommandLineOptions options) + private static async Task VerifyAsync(CommandLineOptions options, CancellationToken cancellationToken) { var manifestPath = options.Manifest ?? options.Output ?? DefaultManifestPath; if (!File.Exists(manifestPath)) @@ -69,28 +83,123 @@ private static async Task VerifyAsync(CommandLineOptions options) return 1; } - var diagnostics = new ContractDiagnosticSink(); - var discovered = await SqlServerContractDiscoverer.DiscoverAsync(options, diagnostics).ConfigureAwait(false); - ContractDiagnosticWriter.Write(diagnostics); - - var discoveredJson = JsonSerializer.Serialize(discovered, ManifestJsonOptions); - var existingJson = await File.ReadAllTextAsync(manifestPath).ConfigureAwait(false); + var existingJson = await File.ReadAllTextAsync(manifestPath, cancellationToken).ConfigureAwait(false); var existingManifest = JsonSerializer.Deserialize(existingJson, ManifestJsonOptions) ?? throw new InvalidOperationException($"Manifest could not be parsed: {manifestPath}"); - var normalizedExistingJson = JsonSerializer.Serialize(existingManifest, ManifestJsonOptions); - if (string.Equals(discoveredJson, normalizedExistingJson, StringComparison.Ordinal)) + var diagnostics = new ContractDiagnosticSink(); + var discovered = await SqlServerContractDiscoverer.DiscoverAsync(options, diagnostics, cancellationToken) + .ConfigureAwait(false); + ContractDiagnosticWriter.Write(diagnostics); + + if (!ReportVerifyDiagnostics(existingManifest, discovered)) { Console.WriteLine("Contract manifest is up to date."); return diagnostics.HasErrors ? 1 : 0; } - ReportVerifyDiagnostics(existingManifest, discovered); return diagnostics.HasErrors ? 1 : 2; } - private static void ReportVerifyDiagnostics(ContractManifest existing, ContractManifest discovered) + private static async Task WriteManifestAsync( + string path, + ContractManifest manifest, + CancellationToken cancellationToken) { + var absolutePath = Path.GetFullPath(path); + var directory = Path.GetDirectoryName(absolutePath); + if (!string.IsNullOrEmpty(directory)) + Directory.CreateDirectory(directory); + + var tempPath = Path.Combine( + directory ?? Directory.GetCurrentDirectory(), + "." + Path.GetFileName(absolutePath) + "." + Guid.NewGuid().ToString("N") + ".tmp"); + + try + { + var json = JsonSerializer.Serialize(manifest, ManifestJsonOptions); + await File.WriteAllTextAsync( + tempPath, + json + Environment.NewLine, + Encoding.UTF8, + cancellationToken) + .ConfigureAwait(false); + + File.Move(tempPath, absolutePath, true); + } + finally + { + if (File.Exists(tempPath)) + File.Delete(tempPath); + } + } + + private static bool ReportVerifyDiagnostics(ContractManifest existing, ContractManifest discovered) + { + var hasDifferences = false; + if (existing.Version != discovered.Version) + { + hasDifferences = true; + Console.Error.WriteLine( + $"CAERIUS209 error: Contract manifest version mismatch. Expected '{discovered.Version}', found '{existing.Version}'."); + } + + if (!string.Equals(existing.Namespace, discovered.Namespace, StringComparison.Ordinal)) + { + hasDifferences = true; + Console.Error.WriteLine( + $"CAERIUS209 error: Contract manifest namespace mismatch. Expected '{discovered.Namespace}', found '{existing.Namespace}'."); + } + + hasDifferences |= ReportTableTypeDiagnostics(existing, discovered); + hasDifferences |= ReportProcedureDiagnostics(existing, discovered); + + return hasDifferences; + } + + private static bool ReportTableTypeDiagnostics(ContractManifest existing, ContractManifest discovered) + { + var hasDifferences = false; + var existingTableTypes = existing.TableTypes.ToDictionary( + tableType => BuildFullName(tableType.Schema, tableType.Name), + StringComparer.OrdinalIgnoreCase); + var discoveredTableTypes = discovered.TableTypes.ToDictionary( + tableType => BuildFullName(tableType.Schema, tableType.Name), + StringComparer.OrdinalIgnoreCase); + + foreach (var (tableTypeName, discoveredTableType) in discoveredTableTypes) + { + if (!existingTableTypes.TryGetValue(tableTypeName, out var existingTableType)) + { + hasDifferences = true; + Console.Error.WriteLine( + $"CAERIUS209 error: Table type '{tableTypeName}' is missing from the CaeriusNet contract manifest."); + continue; + } + + if (!string.Equals( + existingTableType.ContractHash, + discoveredTableType.ContractHash, + StringComparison.Ordinal)) + { + hasDifferences = true; + Console.Error.WriteLine($"CAERIUS209 error: Contract hash mismatch for table type '{tableTypeName}'."); + } + } + + foreach (var tableTypeName in existingTableTypes.Keys) + if (!discoveredTableTypes.ContainsKey(tableTypeName)) + { + hasDifferences = true; + Console.Error.WriteLine($"CAERIUS209 error: Table type '{tableTypeName}' is no longer discovered."); + } + + return hasDifferences; + } + + private static bool ReportProcedureDiagnostics(ContractManifest existing, ContractManifest discovered) + { + var hasDifferences = false; var existingProcedures = existing.Procedures.ToDictionary( procedure => BuildFullName(procedure.Schema, procedure.Name), StringComparer.OrdinalIgnoreCase); @@ -98,11 +207,11 @@ private static void ReportVerifyDiagnostics(ContractManifest existing, ContractM procedure => BuildFullName(procedure.Schema, procedure.Name), StringComparer.OrdinalIgnoreCase); - var emittedHashMismatch = false; foreach (var (procedureName, discoveredProcedure) in discoveredProcedures) { if (!existingProcedures.TryGetValue(procedureName, out var existingProcedure)) { + hasDifferences = true; Console.Error.WriteLine( $"CAERIUS201 error: Procedure '{procedureName}' is missing from the CaeriusNet contract manifest."); continue; @@ -113,7 +222,7 @@ private static void ReportVerifyDiagnostics(ContractManifest existing, ContractM discoveredProcedure.ContractHash, StringComparison.Ordinal)) { - emittedHashMismatch = true; + hasDifferences = true; Console.Error.WriteLine($"CAERIUS209 error: Contract hash mismatch for '{procedureName}'."); } } @@ -121,12 +230,11 @@ private static void ReportVerifyDiagnostics(ContractManifest existing, ContractM foreach (var procedureName in existingProcedures.Keys) if (!discoveredProcedures.ContainsKey(procedureName)) { - emittedHashMismatch = true; - Console.Error.WriteLine($"CAERIUS209 error: Contract hash mismatch for '{procedureName}'."); + hasDifferences = true; + Console.Error.WriteLine($"CAERIUS209 error: Procedure '{procedureName}' is no longer discovered."); } - if (!emittedHashMismatch) - Console.Error.WriteLine("CAERIUS209 error: Contract hash mismatch for 'caerius.contracts.json'."); + return hasDifferences; } private static string BuildFullName(string schema, string name) diff --git a/Tools/CaeriusNet.SqlServer.Contracts/SqlServerContractDiscoverer.cs b/Tools/CaeriusNet.SqlServer.Contracts/SqlServerContractDiscoverer.cs index 4ee095b..1679a08 100644 --- a/Tools/CaeriusNet.SqlServer.Contracts/SqlServerContractDiscoverer.cs +++ b/Tools/CaeriusNet.SqlServer.Contracts/SqlServerContractDiscoverer.cs @@ -8,16 +8,17 @@ internal static class SqlServerContractDiscoverer { internal static async Task DiscoverAsync( CommandLineOptions options, - ContractDiagnosticSink diagnostics) + ContractDiagnosticSink diagnostics, + CancellationToken cancellationToken = default) { var connectionString = ConnectionStringResolver.Resolve(options); await using var connection = new SqlConnection(connectionString); - await connection.OpenAsync().ConfigureAwait(false); + await connection.OpenAsync(cancellationToken).ConfigureAwait(false); - var database = await DiscoverDatabaseAsync(connection).ConfigureAwait(false); - var tableTypes = await DiscoverTableTypesAsync(connection, diagnostics).ConfigureAwait(false); - var procedures = await DiscoverProceduresAsync(connection, tableTypes, diagnostics) + var database = await DiscoverDatabaseAsync(connection, cancellationToken).ConfigureAwait(false); + var tableTypes = await DiscoverTableTypesAsync(connection, diagnostics, cancellationToken).ConfigureAwait(false); + var procedures = await DiscoverProceduresAsync(connection, tableTypes, diagnostics, cancellationToken) .ConfigureAwait(false); return new ContractManifest( @@ -28,7 +29,9 @@ internal static async Task DiscoverAsync( procedures); } - private static async Task DiscoverDatabaseAsync(SqlConnection connection) + private static async Task DiscoverDatabaseAsync( + SqlConnection connection, + CancellationToken cancellationToken) { await using var command = connection.CreateCommand(); command.CommandText = """ @@ -40,8 +43,9 @@ FROM sys.databases WHERE database_id = DB_ID(); """; - await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SingleRow).ConfigureAwait(false); - if (!await reader.ReadAsync().ConfigureAwait(false)) + await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SingleRow, cancellationToken) + .ConfigureAwait(false); + if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) throw new InvalidOperationException("Could not read SQL Server database metadata."); return new DatabaseInfo( @@ -52,7 +56,8 @@ FROM sys.databases private static async Task> DiscoverTableTypesAsync( SqlConnection connection, - ContractDiagnosticSink diagnostics) + ContractDiagnosticSink diagnostics, + CancellationToken cancellationToken) { await using var command = connection.CreateCommand(); command.CommandText = """ @@ -98,8 +103,8 @@ ORDER BY """; var tableTypes = new Dictionary(StringComparer.OrdinalIgnoreCase); - await using var reader = await command.ExecuteReaderAsync().ConfigureAwait(false); - while (await reader.ReadAsync().ConfigureAwait(false)) + await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false); + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { var schema = reader.GetString(0); var name = reader.GetString(1); @@ -152,9 +157,10 @@ ORDER BY private static async Task> DiscoverProceduresAsync( SqlConnection connection, IReadOnlyList tableTypes, - ContractDiagnosticSink diagnostics) + ContractDiagnosticSink diagnostics, + CancellationToken cancellationToken) { - var procedures = await DiscoverProcedureHeadersAsync(connection).ConfigureAwait(false); + var procedures = await DiscoverProcedureHeadersAsync(connection, cancellationToken).ConfigureAwait(false); var tableTypesBySqlName = tableTypes.ToDictionary( tableType => tableType.Schema + "." + tableType.Name, StringComparer.OrdinalIgnoreCase); @@ -166,8 +172,10 @@ private static async Task> DiscoverProceduresAs connection, procedure, tableTypesBySqlName, - diagnostics).ConfigureAwait(false); - var resultSet = await DiscoverResultSetAsync(connection, procedure, diagnostics).ConfigureAwait(false); + diagnostics, + cancellationToken).ConfigureAwait(false); + var resultSet = await DiscoverResultSetAsync(connection, procedure, diagnostics, cancellationToken) + .ConfigureAwait(false); var clrName = CSharpName.FromSqlName(procedure.Name) + "Procedure"; var contractHash = ContractHasher.HashProcedure(procedure.Schema, procedure.Name, parameters, resultSet); @@ -190,7 +198,8 @@ private static async Task> DiscoverProceduresAs } private static async Task> DiscoverProcedureHeadersAsync( - SqlConnection connection) + SqlConnection connection, + CancellationToken cancellationToken) { await using var command = connection.CreateCommand(); command.CommandText = """ @@ -222,8 +231,8 @@ ORDER BY """; var procedures = new List(); - await using var reader = await command.ExecuteReaderAsync().ConfigureAwait(false); - while (await reader.ReadAsync().ConfigureAwait(false)) + await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false); + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) procedures.Add(new ProcedureHeader(reader.GetInt32(0), reader.GetString(1), reader.GetString(2))); return procedures; @@ -233,7 +242,8 @@ private static async Task> DiscoverParametersAs SqlConnection connection, ProcedureHeader procedure, Dictionary tableTypesBySqlName, - ContractDiagnosticSink diagnostics) + ContractDiagnosticSink diagnostics, + CancellationToken cancellationToken) { await using var command = connection.CreateCommand(); command.CommandText = """ @@ -261,8 +271,8 @@ LEFT JOIN sys.types AS sysTyp command.Parameters.Add(new SqlParameter("@ProcedureObjectId", SqlDbType.Int) { Value = procedure.ObjectId }); var parameters = new List(); - await using var reader = await command.ExecuteReaderAsync().ConfigureAwait(false); - while (await reader.ReadAsync().ConfigureAwait(false)) + await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false); + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { var maxLength = reader.GetInt16(4); var precision = reader.GetByte(5); @@ -317,7 +327,8 @@ LEFT JOIN sys.types AS sysTyp private static async Task DiscoverResultSetAsync( SqlConnection connection, ProcedureHeader procedure, - ContractDiagnosticSink diagnostics) + ContractDiagnosticSink diagnostics, + CancellationToken cancellationToken) { await using var command = connection.CreateCommand(); command.CommandText = """ @@ -335,7 +346,7 @@ FROM sys.dm_exec_describe_first_result_set_for_object( @ProcedureObjectId, 0 ) - WHERE is_hidden = 0 + WHERE is_hidden = 0 OR error_number IS NOT NULL ORDER BY column_ordinal; """; command.Parameters.Add(new SqlParameter("@ProcedureObjectId", SqlDbType.Int) { Value = procedure.ObjectId }); @@ -344,8 +355,8 @@ FROM sys.dm_exec_describe_first_result_set_for_object( string? errorMessage = null; var procedureName = procedure.Schema + "." + procedure.Name; - await using var reader = await command.ExecuteReaderAsync().ConfigureAwait(false); - while (await reader.ReadAsync().ConfigureAwait(false)) + await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false); + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { if (!reader.IsDBNull(7)) { @@ -370,7 +381,9 @@ FROM sys.dm_exec_describe_first_result_set_for_object( var maxLength = reader.GetInt16(4); var precision = reader.GetByte(5); var scale = reader.GetByte(6); - var sqlType = SqlServerTypeName.Format(reader.GetString(3), maxLength, precision, scale); + var sqlType = reader.IsDBNull(3) + ? "unknown" + : SqlServerTypeName.Format(reader.GetString(3), maxLength, precision, scale); var nullable = reader.GetBoolean(2); if (!SqlServerTypeMapper.IsSupported(sqlType)) diff --git a/Tools/CaeriusNet.SqlServer.Contracts/buildTransitive/CaeriusNet.SqlServer.Contracts.props b/Tools/CaeriusNet.SqlServer.Contracts/buildTransitive/CaeriusNet.SqlServer.Contracts.props index 37f5fdc..df9e2cc 100644 --- a/Tools/CaeriusNet.SqlServer.Contracts/buildTransitive/CaeriusNet.SqlServer.Contracts.props +++ b/Tools/CaeriusNet.SqlServer.Contracts/buildTransitive/CaeriusNet.SqlServer.Contracts.props @@ -1,21 +1,16 @@ Off - $([System.String]::Copy('$(CaeriusContractsMode)').Trim().ToUpperInvariant()) $(ProjectDir)caerius.contracts.json DefaultConnection $(MSBuildProjectDirectory) - $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\tools\net10.0\any\CaeriusNet.SqlServer.Contracts.dll')) + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../tools/net10.0/any/CaeriusNet.SqlServer.Contracts.dll')) dotnet "$(CaeriusContractsToolPath)" - - - - diff --git a/Tools/CaeriusNet.SqlServer.Contracts/buildTransitive/CaeriusNet.SqlServer.Contracts.targets b/Tools/CaeriusNet.SqlServer.Contracts/buildTransitive/CaeriusNet.SqlServer.Contracts.targets index db83bb1..2306c10 100644 --- a/Tools/CaeriusNet.SqlServer.Contracts/buildTransitive/CaeriusNet.SqlServer.Contracts.targets +++ b/Tools/CaeriusNet.SqlServer.Contracts/buildTransitive/CaeriusNet.SqlServer.Contracts.targets @@ -1,19 +1,30 @@ - - $(CaeriusContractsUserSecretsId) - $(UserSecretsId) - true - - --connection-string "$(CaeriusContractsConnectionString)" - --connection-env "$(CaeriusContractsConnectionStringEnv)" - --connection-name "$(CaeriusContractsConnectionName)" --configuration-base-path "$(CaeriusContractsConfigurationBasePath)" - $(CaeriusContractsConnectionArguments) --configuration-environment "$(CaeriusContractsConfigurationEnvironment)" - $(CaeriusContractsConnectionArguments) --user-secrets-id "$(CaeriusContractsEffectiveUserSecretsId)" - --connection-name "DefaultConnection" --configuration-base-path "$(CaeriusContractsConfigurationBasePath)" - + + + + + + + + $(CaeriusContractsMode.Trim().ToUpperInvariant()) + $(CaeriusContractsUserSecretsId) + $(UserSecretsId) + true + + $(CaeriusContractsConnectionArguments) + --connection-string "$(CaeriusContractsConnectionString)" + --connection-env "$(CaeriusContractsConnectionStringEnv)" + --connection-name "$(CaeriusContractsConnectionName)" --configuration-base-path "$(CaeriusContractsConfigurationBasePath)" + $(CaeriusContractsResolvedConnectionArguments) --configuration-environment "$(CaeriusContractsConfigurationEnvironment)" + $(CaeriusContractsResolvedConnectionArguments) --user-secrets-id "$(CaeriusContractsEffectiveUserSecretsId)" + --connection-name "DefaultConnection" --configuration-base-path "$(CaeriusContractsConfigurationBasePath)" + $(CaeriusContractsResolvedConnectionArguments) + + + DependsOnTargets="CaeriusValidateContractsMode" + Condition="'$(DesignTimeBuild)' != 'true'"> + Command="$(CaeriusContractsToolCommand) pull $(CaeriusContractsResolvedConnectionArguments) --output "$(CaeriusContractsOutput)""/> + DependsOnTargets="CaeriusValidateContractsMode" + Condition="'$(DesignTimeBuild)' != 'true'"> + Condition="'$(CaeriusContractsModeNormalized)' == 'VERIFY' and !Exists('$(CaeriusContractsOutput)')"/> + Command="$(CaeriusContractsToolCommand) verify $(CaeriusContractsResolvedConnectionArguments) --manifest "$(CaeriusContractsOutput)""/> Date: Thu, 14 May 2026 21:43:04 +0200 Subject: [PATCH 2/5] refactor: clean up code formatting and update version in GeneratedCodeInfo --- .../AutoContractsManifestAnalyzer.cs | 21 +- Documentations/docs/benchmarks/sql-server.md | 4 +- Documentations/docs/documentation/api.md | 4 +- .../docs/documentation/best-practices.md | 2 +- .../docs/documentation/multi-results.md | 6 +- .../docs/documentation/source-generators.md | 12 +- .../docs/examples/multi-result-sets.md | 13 +- .../Abstractions/IUsersRepository.cs | 2 + README.md | 4 +- SourceGenerators/Helpers/GeneratedCodeInfo.cs | 2 +- SourceGenerators/Helpers/TypeDetector.cs | 1 - .../MultiResultSetSignaturesEmitter.cs | 260 ++++++++++++++++++ ...MultiResultSetSignaturesSourceGenerator.cs | 33 +++ SourceGenerators/README.md | 12 +- .../StoredProcedureParametersBuilder.cs | 8 +- .../MultiIEnumerableReadSqlAsyncCommands.cs | 139 ---------- ...MultiImmutableArrayReadSqlAsyncCommands.cs | 143 ---------- ...iReadOnlyCollectionReadSqlAsyncCommands.cs | 153 ----------- Src/Factories/CaeriusNetTransaction.cs | 8 +- Src/Helpers/MultiResultSetHelper.cs | 1 - Src/Helpers/ResultSetMaterializer.cs | 2 +- Src/Mappers/StoredProcedureParameters.cs | 2 - Src/README.md | 5 +- .../AutoContractsManifestAnalyzerTests.cs | 18 +- .../Utilities/AnalyzerTestHelper.cs | 2 + .../AutoContractsSourceGeneratorTests.cs | 2 +- ...ResultSetSignaturesSourceGeneratorTests.cs | 38 +++ .../Utilities/SourceGeneratorTestHelper.cs | 20 +- .../StoredProcedureParametersBuilderTests.cs | 6 +- .../CaeriusNet.SqlServer.Contracts.csproj | 2 +- .../SqlServerContractDiscoverer.cs | 3 +- 31 files changed, 432 insertions(+), 496 deletions(-) create mode 100644 SourceGenerators/MultiResults/MultiResultSetSignaturesEmitter.cs create mode 100644 SourceGenerators/MultiResults/MultiResultSetSignaturesSourceGenerator.cs create mode 100644 Tests/CaeriusNet.Generator.Tests/MultiResults/MultiResultSetSignaturesSourceGeneratorTests.cs diff --git a/Analyzer/AutoContracts/AutoContractsManifestAnalyzer.cs b/Analyzer/AutoContracts/AutoContractsManifestAnalyzer.cs index 5deb5bd..8fd698d 100644 --- a/Analyzer/AutoContracts/AutoContractsManifestAnalyzer.cs +++ b/Analyzer/AutoContracts/AutoContractsManifestAnalyzer.cs @@ -249,7 +249,8 @@ private static void ValidateGenerationNames( foreach (var tableType in manifest.TableTypes.Items) { ReportIfInvalidIdentifier(context, manifestPath, location, tableType.ClrName, "table type CLR name"); - ReportIfDuplicate(context, manifestPath, location, tableSqlNames, BuildFullName(tableType.Schema, tableType.Name)); + ReportIfDuplicate(context, manifestPath, location, tableSqlNames, + BuildFullName(tableType.Schema, tableType.Name)); ReportIfDuplicate(context, manifestPath, location, generatedTypes, tableType.ClrName); ValidateUniqueMembers(context, manifestPath, location, tableType.ClrName, tableType.Columns.Items); } @@ -258,17 +259,21 @@ private static void ValidateGenerationNames( foreach (var procedure in manifest.Procedures.Items) { ReportIfInvalidIdentifier(context, manifestPath, location, procedure.ClrName, "procedure CLR name"); - ReportIfInvalidIdentifier(context, manifestPath, location, procedure.ParametersClrName, "parameters CLR name"); - ReportIfDuplicate(context, manifestPath, location, procedureSqlNames, BuildFullName(procedure.Schema, procedure.Name)); + ReportIfInvalidIdentifier(context, manifestPath, location, procedure.ParametersClrName, + "parameters CLR name"); + ReportIfDuplicate(context, manifestPath, location, procedureSqlNames, + BuildFullName(procedure.Schema, procedure.Name)); ReportIfDuplicate(context, manifestPath, location, generatedTypes, procedure.ClrName); ReportIfDuplicate(context, manifestPath, location, generatedTypes, procedure.ParametersClrName); - ValidateUniqueMembers(context, manifestPath, location, procedure.ParametersClrName, procedure.Parameters.Items); + ValidateUniqueMembers(context, manifestPath, location, procedure.ParametersClrName, + procedure.Parameters.Items); if (procedure.ResultClrName is { Length: > 0 } resultClrName) { ReportIfInvalidIdentifier(context, manifestPath, location, resultClrName, "result CLR name"); ReportIfDuplicate(context, manifestPath, location, generatedTypes, resultClrName); - ValidateUniqueMembers(context, manifestPath, location, resultClrName, procedure.ResultSet.Columns.Items); + ValidateUniqueMembers(context, manifestPath, location, resultClrName, + procedure.ResultSet.Columns.Items); } } } @@ -282,7 +287,8 @@ private static void ValidateUniqueMembers( { var identifiers = new HashSet(StringComparer.Ordinal); foreach (var column in columns) - ReportIfDuplicate(context, manifestPath, location, identifiers, NormalizeIdentifier(column.Name), containerName); + ReportIfDuplicate(context, manifestPath, location, identifiers, NormalizeIdentifier(column.Name), + containerName); } private static void ValidateUniqueMembers( @@ -294,7 +300,8 @@ private static void ValidateUniqueMembers( { var identifiers = new HashSet(StringComparer.Ordinal); foreach (var parameter in parameters) - ReportIfDuplicate(context, manifestPath, location, identifiers, NormalizeIdentifier(parameter.Name), containerName); + ReportIfDuplicate(context, manifestPath, location, identifiers, NormalizeIdentifier(parameter.Name), + containerName); } private static void ReportIfInvalidIdentifier( diff --git a/Documentations/docs/benchmarks/sql-server.md b/Documentations/docs/benchmarks/sql-server.md index 4ae240c..bd7f0a9 100644 --- a/Documentations/docs/benchmarks/sql-server.md +++ b/Documentations/docs/benchmarks/sql-server.md @@ -93,8 +93,8 @@ Two insertion strategies are compared at `[Params(10, 100, 500, 1_000, 5_000)]` ### What is measured -CaeriusNet supports `IAsyncEnumerable<(T1, T2, ...)>` multi-result-set SPs — a single SP call that returns -multiple independent result sets, read via successive `reader.NextResultAsync()` calls. +CaeriusNet supports typed tuple multi-result-set SP calls — a single SP call that returns +multiple independent result sets, materialized via successive `reader.NextResultAsync()` calls. This benchmark compares: - **Single SP, multiple result sets**: one TDS roundtrip, one connection checkout from pool, N result set reads. diff --git a/Documentations/docs/documentation/api.md b/Documentations/docs/documentation/api.md index de8a169..0ba499f 100644 --- a/Documentations/docs/documentation/api.md +++ b/Documentations/docs/documentation/api.md @@ -315,7 +315,7 @@ await dbContext.ExecuteAsync(sp, ct); Up to **5 result sets** per call. Each return type maps positionally to a `SELECT` statement in the SP. -Namespace: `CaeriusNet.Commands.Reads.MultiIEnumerableReadSqlAsyncCommands` (similar shapes exist for `ReadOnlyCollection` and `ImmutableArray`). +Namespace: `CaeriusNet.Commands.Reads`. The `T1,T2` overloads are maintained as the runtime source baseline; overloads for arities 3 through 5 are generated into the `CaeriusNet` assembly during package build. ```csharp Task<(IEnumerable, IEnumerable)> @@ -331,6 +331,8 @@ Task<(IEnumerable, IEnumerable, IEnumerable, IEnumerable, IEnume QueryMultipleIEnumerableAsync(/* ... */); ``` +The same arities are available for `QueryMultipleReadOnlyCollectionAsync` and `QueryMultipleImmutableArrayAsync`. + Example: ```csharp diff --git a/Documentations/docs/documentation/best-practices.md b/Documentations/docs/documentation/best-practices.md index 1d5bdbe..c4abee0 100644 --- a/Documentations/docs/documentation/best-practices.md +++ b/Documentations/docs/documentation/best-practices.md @@ -81,7 +81,7 @@ See [Caching](/documentation/cache) for the full guide. - **Set `resultSetCapacity` accurately.** It pre-allocates the `List` and avoids resize churn for large reads. - **Return only required columns.** Less data = fewer allocations and faster TDS framing. - **Pick the right collection.** `ImmutableArray` for frozen / shared data; `ReadOnlyCollection` for public APIs; `IEnumerable` for LINQ pipelines. -- **Stream multi-result-sets** with the `QueryMultipleIEnumerableAsync` family rather than chaining separate calls. +- **Combine related result sets** with the `QueryMultiple*Async` families rather than chaining separate stored-procedure calls. - **Benchmark critical flows.** CaeriusNet's own [BenchmarkDotNet suites](/benchmarks/) are reproducible (`Random(42)`); use them as a baseline. Internal performance levers worth knowing about: diff --git a/Documentations/docs/documentation/multi-results.md b/Documentations/docs/documentation/multi-results.md index 7d7072e..4d8a151 100644 --- a/Documentations/docs/documentation/multi-results.md +++ b/Documentations/docs/documentation/multi-results.md @@ -35,7 +35,7 @@ GO ## Available overloads -CaeriusNet offers three collection-type families, each with overloads for **2 to 5** result sets. Pick the family that matches your storage, mutation, and allocation needs: +CaeriusNet offers three collection-type families, each with overloads for **2 to 5** result sets. Pick the family that matches your storage, mutation, and allocation needs. The two-result-set overloads are the hand-written base signatures in the runtime source; arities 3 through 5 are generated into the package at build time from the same implementation template. ### `QueryMultipleIEnumerableAsync` @@ -58,6 +58,8 @@ Returns a tuple of `ReadOnlyCollection`: ```csharp Task<(ReadOnlyCollection, ReadOnlyCollection)> QueryMultipleReadOnlyCollectionAsync(/* ... */); + +// ... up to T5 ``` ### `QueryMultipleImmutableArrayAsync` @@ -67,6 +69,8 @@ Returns a tuple of `ImmutableArray`: ```csharp Task<(ImmutableArray, ImmutableArray)> QueryMultipleImmutableArrayAsync(/* ... */); + +// ... up to T5 ``` ## Example — two result sets diff --git a/Documentations/docs/documentation/source-generators.md b/Documentations/docs/documentation/source-generators.md index f911167..0a70440 100644 --- a/Documentations/docs/documentation/source-generators.md +++ b/Documentations/docs/documentation/source-generators.md @@ -4,11 +4,12 @@ CaeriusNet ships Roslyn **incremental source generators** that eliminate mapping ## Overview -| Generator | Attribute | Generated interface | +| Generator | Input | Output | |---|---|---| | `DtoSourceGenerator` | `[GenerateDto]` | `ISpMapper` | | `TvpSourceGenerator` | `[GenerateTvp]` | `ITvpMapper` | | `AutoContractsSourceGenerator` | `caerius.contracts.json` supplied as `AdditionalFiles` | typed stored procedure descriptors and DTO/TVP contracts | +| `MultiResultSetSignaturesSourceGenerator` | none | package-internal overloads for multi-result-set arities 3 through 5 | The DTO and TVP generators target **sealed partial records or classes** with a primary constructor. The `partial` keyword lets the generator add the interface implementation as a second declaration alongside your type. Constraints are enforced at compile time by the [CaeriusNet analyzer](/documentation/diagnostics) (`CAERIUS001` through `CAERIUS006`). @@ -17,6 +18,11 @@ the database during compilation. It reads only the `caerius.contracts.json` mani Roslyn `AdditionalFiles` item. The SQL Server metadata read happens earlier in the CLI/MSBuild AutoContracts phase. +The multi-result-set signature generator is an internal package build detail. It runs only while +compiling the `CaeriusNet` runtime assembly, keeps the hand-written runtime source limited to the +base two-result-set overloads, and emits the public `T1..T3` through `T1..T5` extension overloads into +the packaged assembly. Consumer projects do not receive duplicate generated methods. + ## `[GenerateDto]` — DTO mapper Annotate a sealed partial record (or class) with `[GenerateDto]`. The generator emits a `MapFromDataReader` method with ordinal-based column reads, correct nullability guards, and special-type conversions. @@ -46,7 +52,7 @@ public sealed partial record UserDto(int Id, string Username, byte Age); using System.CodeDom.Compiler; using System.Runtime.CompilerServices; -[GeneratedCode("CaeriusNet.Generator", "11.0.0")] +[GeneratedCode("CaeriusNet.Generator", "11.0.3")] partial record UserDto : ISpMapper { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -179,7 +185,7 @@ public sealed partial record UserIdTvp(int Id); using System.CodeDom.Compiler; using System.Runtime.CompilerServices; -[GeneratedCode("CaeriusNet.Generator", "11.0.0")] +[GeneratedCode("CaeriusNet.Generator", "11.0.3")] partial record UserIdTvp : ITvpMapper { public static string TvpTypeName => "dbo.tvp_int"; diff --git a/Documentations/docs/examples/multi-result-sets.md b/Documentations/docs/examples/multi-result-sets.md index 4611bcb..58ccaa5 100644 --- a/Documentations/docs/examples/multi-result-sets.md +++ b/Documentations/docs/examples/multi-result-sets.md @@ -135,14 +135,11 @@ public async Task<(IReadOnlyCollection Users, IReadOnlyCollection` | 2 | `(ReadOnlyCollection, ReadOnlyCollection)` | -| `QueryMultipleReadOnlyCollectionAsync` | 3 | `(ReadOnlyCollection, â€Ļ, ReadOnlyCollection)` | -| `QueryMultipleImmutableArrayAsync` | 2 | `(ImmutableArray, ImmutableArray)` | -| `QueryMultipleImmutableArrayAsync` | 3 | `(ImmutableArray, â€Ļ, ImmutableArray)` | -| `QueryMultipleIEnumerableAsync` | 2 | `(IEnumerable, IEnumerable)` | -| `QueryMultipleIEnumerableAsync` | 3 | `(IEnumerable, â€Ļ, IEnumerable)` | - -The `IEnumerable`, `ReadOnlyCollection`, and `ImmutableArray` families are all available with arities **2 → 5**. +| `QueryMultipleReadOnlyCollectionAsync` through `` | 2-5 | tuple of `ReadOnlyCollection` | +| `QueryMultipleImmutableArrayAsync` through `` | 2-5 | tuple of `ImmutableArray` | +| `QueryMultipleIEnumerableAsync` through `` | 2-5 | tuple of `IEnumerable` | + +The two-result-set signatures are the runtime source baseline; higher arities are emitted into the package by the CaeriusNet source generator during the package build. ::: warning Result-set order is the contract CaeriusNet maps result sets **positionally** — the first `SELECT` becomes `T1`, the second becomes `T2`, and so on. The DTO type passed at each position must match the columns of the corresponding `SELECT`. There is no runtime name-matching; misalignment produces an `InvalidCastException` (best case) or silently wrong values. diff --git a/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Abstractions/IUsersRepository.cs b/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Abstractions/IUsersRepository.cs index 317d3e4..f55124c 100644 --- a/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Abstractions/IUsersRepository.cs +++ b/Exemples/Libs/CaeriusNet.Exemples.Libs.Commons/Abstractions/IUsersRepository.cs @@ -9,7 +9,9 @@ public interface IUsersRepository Task> GetAllUsersWithRedisCache(CancellationToken cancellationToken = default); Task> GetUsersByTvpIntGuid(CancellationToken cancellationToken = default); Task> GetUsersByTvpInt(CancellationToken cancellationToken = default); + Task> GetUsersByTvpGuid(CancellationToken cancellationToken = default); + // Multi-result sets --------------------------------------------------- Task GetDashboardAsync(CancellationToken cancellationToken = default); diff --git a/README.md b/README.md index 3e15df8..ad76465 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ | Directory | Description | |-----------|-------------| | `Src/` | Core library — CaeriusNet NuGet package source | -| `SourceGenerators/` | Roslyn incremental source generators (`[GenerateDto]`, `[GenerateTvp]`) | +| `SourceGenerators/` | Roslyn incremental source generators (`[GenerateDto]`, `[GenerateTvp]`, AutoContracts, package-internal multi-result signatures) | | `Tests/` | Unit tests, generator tests, integration tests | | `Benchmark/` | BenchmarkDotNet performance benchmarks | | `Exemples/` | Example projects (Aspire + Console); directory name retained for compatibility | @@ -157,7 +157,7 @@ var (users, orders) = await dbContext.QueryMultipleIEnumerableAsync`. +You can query up to five result sets: `QueryMultipleIEnumerableAsync`. The two-result-set overload is the runtime baseline; higher arities are emitted into the package by the CaeriusNet source generator. --- diff --git a/SourceGenerators/Helpers/GeneratedCodeInfo.cs b/SourceGenerators/Helpers/GeneratedCodeInfo.cs index 0ae7c47..03ad55e 100644 --- a/SourceGenerators/Helpers/GeneratedCodeInfo.cs +++ b/SourceGenerators/Helpers/GeneratedCodeInfo.cs @@ -3,5 +3,5 @@ namespace CaeriusNet.Generator.Helpers; internal static class GeneratedCodeInfo { internal const string ToolName = "CaeriusNet.Generator"; - internal const string Version = "11.0.0"; + internal const string Version = "11.0.3"; } diff --git a/SourceGenerators/Helpers/TypeDetector.cs b/SourceGenerators/Helpers/TypeDetector.cs index 73073cf..d3403f5 100644 --- a/SourceGenerators/Helpers/TypeDetector.cs +++ b/SourceGenerators/Helpers/TypeDetector.cs @@ -211,5 +211,4 @@ public static string GetReaderMethodForSqlType(string sqlType) _ => "GetValue" }; } - } diff --git a/SourceGenerators/MultiResults/MultiResultSetSignaturesEmitter.cs b/SourceGenerators/MultiResults/MultiResultSetSignaturesEmitter.cs new file mode 100644 index 0000000..8a3b35a --- /dev/null +++ b/SourceGenerators/MultiResults/MultiResultSetSignaturesEmitter.cs @@ -0,0 +1,260 @@ +namespace CaeriusNet.Generator.MultiResults; + +internal static class MultiResultSetSignaturesEmitter +{ + internal static SourceText Emit() + { + var sb = new StringBuilder(32_768); + + AppendHeader(sb); + AppendUsings(sb); + sb.AppendLine("namespace CaeriusNet.Commands.Reads;"); + sb.AppendLine(); + + AppendFamily(sb, "GeneratedMultiIEnumerableReadSqlAsyncCommands", "QueryMultipleIEnumerableAsync", + "IEnumerable", ResultShape.Enumerable); + AppendFamily(sb, "GeneratedMultiReadOnlyCollectionReadSqlAsyncCommands", "QueryMultipleReadOnlyCollectionAsync", + "ReadOnlyCollection", ResultShape.ReadOnlyCollection); + AppendFamily(sb, "GeneratedMultiImmutableArrayReadSqlAsyncCommands", "QueryMultipleImmutableArrayAsync", + "ImmutableArray", ResultShape.ImmutableArray); + + return SourceText.From(sb.ToString(), Encoding.UTF8); + } + + private static void AppendHeader(StringBuilder sb) + { + sb.AppendLine("// "); + sb.AppendLine("#nullable enable"); + sb.AppendLine("#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member"); + sb.AppendLine(); + } + + private static void AppendUsings(StringBuilder sb) + { + sb.AppendLine("using System.CodeDom.Compiler;"); + sb.AppendLine("using System.Collections.Generic;"); + sb.AppendLine("using System.Collections.Immutable;"); + sb.AppendLine("using System.Collections.ObjectModel;"); + sb.AppendLine("using System.Data;"); + sb.AppendLine("using System.Runtime.CompilerServices;"); + sb.AppendLine("using System.Threading;"); + sb.AppendLine("using System.Threading.Tasks;"); + sb.AppendLine("using CaeriusNet.Abstractions;"); + sb.AppendLine("using CaeriusNet.Helpers;"); + sb.AppendLine("using CaeriusNet.Mappers;"); + sb.AppendLine("using CaeriusNet.Telemetry;"); + sb.AppendLine(); + } + + private static void AppendFamily( + StringBuilder sb, + string className, + string methodName, + string collectionType, + ResultShape shape) + { + sb.Append("public static class ").Append(className).AppendLine(); + sb.AppendLine("{"); + sb.AppendLine(" extension(ICaeriusNetDbContext context)"); + sb.AppendLine(" {"); + + for (var arity = 3; arity <= 5; arity++) + { + AppendMethod(sb, methodName, collectionType, shape, arity); + if (arity < 5) + sb.AppendLine(); + } + + sb.AppendLine(" }"); + sb.AppendLine("}"); + sb.AppendLine(); + } + + private static void AppendMethod( + StringBuilder sb, + string methodName, + string collectionType, + ResultShape shape, + int arity) + { + var typeParameters = BuildTypeParameters(arity); + + sb.AppendLine(" /// "); + sb.Append(" /// Execute a stored procedure and materialize up to ") + .Append(arity) + .Append(" result sets as ") + .Append(GetCollectionDescription(shape)) + .AppendLine("."); + sb.AppendLine(" /// "); + sb.AppendLine(" /// Stored procedure metadata and parameters."); + sb.AppendLine(" /// Token to cancel the operation."); + sb.AppendLine(" /// A tuple containing the materialized result sets. Missing trailing result sets are empty."); + AppendGeneratedCodeAttribute(sb, " "); + sb.AppendLine(" [MethodImpl(MethodImplOptions.AggressiveOptimization)]"); + sb.Append(" public async Task<(").Append(BuildTupleType(collectionType, arity)).AppendLine(")>"); + sb.Append(" ").Append(methodName).Append('<').Append(typeParameters).AppendLine(">("); + sb.AppendLine(" StoredProcedureParameters spParameters,"); + sb.AppendLine(" CancellationToken cancellationToken = default)"); + AppendConstraints(sb, arity); + sb.AppendLine(" {"); + sb.Append(" return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, ") + .Append(arity) + .AppendLine(","); + sb.Append(" nameof(").Append(methodName).AppendLine("), async command =>"); + sb.AppendLine(" {"); + sb.AppendLine(" await using var reader = await command.ExecuteReaderAsync("); + sb.AppendLine(" CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false);"); + sb.AppendLine(); + + for (var index = 1; index <= arity; index++) + { + if (index > 1) + { + sb.AppendLine(" if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false))"); + sb.Append(" return (").Append(BuildReturnTuple(shape, arity, index - 1)).AppendLine(");"); + sb.AppendLine(); + } + + AppendReadResultSet(sb, shape, index); + if (index < arity) + sb.AppendLine(); + } + + sb.AppendLine(); + sb.Append(" return (").Append(BuildReturnTuple(shape, arity, arity)).AppendLine(");"); + sb.AppendLine(" }, cancellationToken).ConfigureAwait(false);"); + sb.AppendLine(" }"); + } + + private static void AppendGeneratedCodeAttribute(StringBuilder sb, string indent) + { + sb.Append(indent) + .Append("[GeneratedCode(\"") + .Append(GeneratedCodeInfo.ToolName) + .Append("\", \"") + .Append(GeneratedCodeInfo.Version) + .AppendLine("\")]"); + } + + private static void AppendReadResultSet(StringBuilder sb, ResultShape shape, int index) + { + var typeName = "TResultSet" + index; + var resultName = GetResultName(shape, index); + + if (shape == ResultShape.ImmutableArray) + { + sb.Append(" var ").Append(resultName) + .Append(" = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync<") + .Append(typeName) + .AppendLine(">("); + sb.AppendLine(" reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false);"); + return; + } + + sb.Append(" var l").Append(index) + .Append(" = await MultiResultSetHelper.ReadResultSetAsync<") + .Append(typeName) + .AppendLine(">("); + sb.AppendLine(" reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false);"); + + if (shape == ResultShape.ReadOnlyCollection) + sb.Append(" var ").Append(resultName).Append(" = l").Append(index).AppendLine(".AsReadOnly();"); + } + + private static void AppendConstraints(StringBuilder sb, int arity) + { + for (var i = 1; i <= arity; i++) + sb.Append(" where TResultSet") + .Append(i) + .Append(" : class, ISpMapper"); + } + + private static string BuildTupleType(string collectionType, int arity) + { + var sb = new StringBuilder(arity * 36); + for (var i = 1; i <= arity; i++) + { + if (i > 1) + sb.Append(", "); + sb.Append(collectionType).Append("'); + } + + return sb.ToString(); + } + + private static string BuildTypeParameters(int arity) + { + var sb = new StringBuilder(arity * 12); + for (var i = 1; i <= arity; i++) + { + if (i > 1) + sb.Append(", "); + sb.Append("TResultSet").Append(i); + } + + return sb.ToString(); + } + + private static string BuildReturnTuple(ResultShape shape, int arity, int populated) + { + var sb = new StringBuilder(arity * 12); + for (var i = 1; i <= arity; i++) + { + if (i > 1) + sb.Append(", "); + sb.Append(i <= populated ? GetResultName(shape, i) : GetEmptyExpression(shape, i)); + } + + return sb.ToString(); + } + + private static string GetResultName(ResultShape shape, int index) + { + switch (shape) + { + case ResultShape.Enumerable: + return "l" + index; + case ResultShape.ReadOnlyCollection: + return "r" + index; + case ResultShape.ImmutableArray: + return "a" + index; + default: + throw new ArgumentOutOfRangeException(nameof(shape), shape, null); + } + } + + private static string GetEmptyExpression(ResultShape shape, int index) + { + switch (shape) + { + case ResultShape.ImmutableArray: + return "ImmutableArray.Empty"; + default: + return "[]"; + } + } + + private static string GetCollectionDescription(ResultShape shape) + { + switch (shape) + { + case ResultShape.Enumerable: + return "enumerable sequences"; + case ResultShape.ReadOnlyCollection: + return "read-only collections"; + case ResultShape.ImmutableArray: + return "immutable arrays"; + default: + throw new ArgumentOutOfRangeException(nameof(shape), shape, null); + } + } + + private enum ResultShape + { + Enumerable, + ReadOnlyCollection, + ImmutableArray + } +} diff --git a/SourceGenerators/MultiResults/MultiResultSetSignaturesSourceGenerator.cs b/SourceGenerators/MultiResults/MultiResultSetSignaturesSourceGenerator.cs new file mode 100644 index 0000000..f82d7ab --- /dev/null +++ b/SourceGenerators/MultiResults/MultiResultSetSignaturesSourceGenerator.cs @@ -0,0 +1,33 @@ +namespace CaeriusNet.Generator.MultiResults; + +/// +/// Emits the generated multi-result-set overloads into the CaeriusNet runtime assembly. +/// +/// +/// The generator is intentionally scoped to the runtime assembly. The NuGet package also ships +/// this analyzer DLL to consumers for DTO/TVP/AutoContracts generation; consumer projects must +/// not receive duplicate multi-result extension methods. +/// +[Generator(LanguageNames.CSharp)] +public sealed class MultiResultSetSignaturesSourceGenerator : IIncrementalGenerator +{ + private const string RuntimeAssemblyName = "CaeriusNet"; + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var shouldEmit = context.CompilationProvider + .Select(static (compilation, _) => + string.Equals(compilation.AssemblyName, RuntimeAssemblyName, StringComparison.Ordinal)); + + context.RegisterImplementationSourceOutput(shouldEmit, static (spc, emit) => + { + if (!emit) + return; + + spc.AddSource( + "CaeriusNet.Commands.Reads.MultiResultSetGeneratedSignatures.g.cs", + MultiResultSetSignaturesEmitter.Emit()); + }); + } +} diff --git a/SourceGenerators/README.md b/SourceGenerators/README.md index e1c62fc..820f081 100644 --- a/SourceGenerators/README.md +++ b/SourceGenerators/README.md @@ -1,6 +1,7 @@ # CaeriusNet Source Generators -Roslyn incremental source generators that emit compile-time mappers for DTOs, TVPs, and SQL Server AutoContracts. +Roslyn incremental source generators that emit compile-time mappers for DTOs, TVPs, SQL Server AutoContracts, +and the runtime multi-result-set overload signatures used by the main package. Targets `netstandard2.0` (Roslyn analyzer constraint). Uses `Microsoft.CodeAnalysis.CSharp 5.3.0`. ## Architecture @@ -8,13 +9,14 @@ Targets `netstandard2.0` (Roslyn analyzer constraint). Uses `Microsoft.CodeAnaly ### Pipeline ``` -ForAttributeWithMetadataName / AdditionalTextsProvider -> Extract -> Emit +ForAttributeWithMetadataName / AdditionalTextsProvider / CompilationProvider -> Extract -> Emit ``` Each generator follows the same three-stage pipeline: 1. **Filter** — `ForAttributeWithMetadataName` selects types annotated with `[GenerateDto]` or `[GenerateTvp]`; - AutoContracts selects only `caerius.contracts.json` from `AdditionalTextsProvider`. + AutoContracts selects only `caerius.contracts.json` from `AdditionalTextsProvider`; the multi-result signature + generator checks `CompilationProvider` and emits only when the target assembly is `CaeriusNet`. 2. **Extract** — Transforms syntax/semantic model into a value-equatable model (`DtoModel` / `TvpModel`). No Roslyn types flow past this stage. 3. **Emit** — Pure function that converts the model into generated C# source via `StringBuilder`. @@ -45,6 +47,8 @@ namespaces or generator kinds. | `Tvp/TvpExtractor.cs` | Extracts `TvpModel` from syntax/semantic model | | `Tvp/TvpEmitter.cs` | Emits `MapAsSqlDataRecords` + `TvpTypeName` source code | | `AutoContracts/AutoContractsSourceGenerator.cs` | `IIncrementalGenerator` for `caerius.contracts.json` via `AdditionalFiles` | +| `MultiResults/MultiResultSetSignaturesSourceGenerator.cs` | Emits package-internal multi-result overloads for arities 3 through 5 | +| `MultiResults/MultiResultSetSignaturesEmitter.cs` | Pure source emitter for generated multi-result extension classes | | `Helpers/TypeDetector.cs` | C# type → SQL type mapping and reader method resolution | | `Helpers/ColumnExtractor.cs` | Shared column extraction from primary constructor parameters | | `Helpers/HintNameBuilder.cs` | Stable unique hint names for generated source files | @@ -100,7 +104,7 @@ diagnostics link to ## Generated Code Features -- `[GeneratedCode("CaeriusNet.Generator", "11.0.0")]` attribute on generated types +- `[GeneratedCode("CaeriusNet.Generator", "11.0.3")]` attribute on generated types/members - `[MethodImpl(MethodImplOptions.AggressiveInlining)]` on `MapFromDataReader` - `[MethodImpl(MethodImplOptions.AggressiveOptimization)]` on `MapAsSqlDataRecords` - `#pragma warning disable CS1591` (suppresses missing XML docs warning) diff --git a/Src/Builders/StoredProcedureParametersBuilder.cs b/Src/Builders/StoredProcedureParametersBuilder.cs index 1b678f8..e738667 100644 --- a/Src/Builders/StoredProcedureParametersBuilder.cs +++ b/Src/Builders/StoredProcedureParametersBuilder.cs @@ -60,10 +60,10 @@ public StoredProcedureParametersBuilder AddParameter(string parameter, object? v parameter, value, dbType, - size: null, - precision: null, - scale: null, - direction: ParameterDirection.Input); + null, + null, + null, + ParameterDirection.Input); } /// diff --git a/Src/Commands/Reads/MultiIEnumerableReadSqlAsyncCommands.cs b/Src/Commands/Reads/MultiIEnumerableReadSqlAsyncCommands.cs index c04b6b9..d4b0f5a 100644 --- a/Src/Commands/Reads/MultiIEnumerableReadSqlAsyncCommands.cs +++ b/Src/Commands/Reads/MultiIEnumerableReadSqlAsyncCommands.cs @@ -37,144 +37,5 @@ public static class MultiIEnumerableReadSqlAsyncCommands return (l1, l2); }, cancellationToken).ConfigureAwait(false); } - - /// - /// Execute a stored procedure and materialize up to three result sets as enumerable sequences. - /// - /// Stored procedure metadata and parameters. - /// Token to cancel the operation. - /// A tuple containing the materialized result sets. Missing trailing result sets are empty. - public async Task<(IEnumerable, IEnumerable, IEnumerable)> - QueryMultipleIEnumerableAsync(StoredProcedureParameters spParameters, - CancellationToken cancellationToken = default) - where TResultSet1 : class, ISpMapper - where TResultSet2 : class, ISpMapper - where TResultSet3 : class, ISpMapper - { - return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, 3, - nameof(QueryMultipleIEnumerableAsync), async command => - { - await using var reader = await command.ExecuteReaderAsync( - CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false); - - var l1 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (l1, [], []); - - var l2 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (l1, l2, []); - - var l3 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - return (l1, l2, l3); - }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Execute a stored procedure and materialize up to four result sets as enumerable sequences. - /// - /// Stored procedure metadata and parameters. - /// Token to cancel the operation. - /// A tuple containing the materialized result sets. Missing trailing result sets are empty. - public async Task<(IEnumerable, IEnumerable, IEnumerable, - IEnumerable)> - QueryMultipleIEnumerableAsync( - StoredProcedureParameters spParameters, - CancellationToken cancellationToken = default) - where TResultSet1 : class, ISpMapper - where TResultSet2 : class, ISpMapper - where TResultSet3 : class, ISpMapper - where TResultSet4 : class, ISpMapper - { - return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, 4, - nameof(QueryMultipleIEnumerableAsync), async command => - { - await using var reader = await command.ExecuteReaderAsync( - CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false); - - var l1 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (l1, [], [], []); - - var l2 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (l1, l2, [], []); - - var l3 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (l1, l2, l3, []); - - var l4 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - return (l1, l2, l3, l4); - }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Execute a stored procedure and materialize up to five result sets as enumerable sequences. - /// - /// Stored procedure metadata and parameters. - /// Token to cancel the operation. - /// A tuple containing the materialized result sets. Missing trailing result sets are empty. - public async Task<(IEnumerable, IEnumerable, IEnumerable, - IEnumerable, IEnumerable)> - QueryMultipleIEnumerableAsync( - StoredProcedureParameters spParameters, - CancellationToken cancellationToken = default) - where TResultSet1 : class, ISpMapper - where TResultSet2 : class, ISpMapper - where TResultSet3 : class, ISpMapper - where TResultSet4 : class, ISpMapper - where TResultSet5 : class, ISpMapper - { - return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, 5, - nameof(QueryMultipleIEnumerableAsync), async command => - { - await using var reader = await command.ExecuteReaderAsync( - CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false); - - var l1 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (l1, [], [], [], []); - - var l2 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (l1, l2, [], [], []); - - var l3 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (l1, l2, l3, [], []); - - var l4 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (l1, l2, l3, l4, []); - - var l5 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - return (l1, l2, l3, l4, l5); - }, cancellationToken).ConfigureAwait(false); - } } } diff --git a/Src/Commands/Reads/MultiImmutableArrayReadSqlAsyncCommands.cs b/Src/Commands/Reads/MultiImmutableArrayReadSqlAsyncCommands.cs index e987ecb..3967c42 100644 --- a/Src/Commands/Reads/MultiImmutableArrayReadSqlAsyncCommands.cs +++ b/Src/Commands/Reads/MultiImmutableArrayReadSqlAsyncCommands.cs @@ -38,148 +38,5 @@ public static class MultiImmutableArrayReadSqlAsyncCommands return (a1, a2); }, cancellationToken).ConfigureAwait(false); } - - /// - /// Execute a stored procedure and materialize up to three result sets as immutable arrays. - /// - /// Stored procedure metadata and parameters. - /// Token to cancel the operation. - /// A tuple containing the materialized result sets. Missing trailing result sets are empty. - public async Task<(ImmutableArray, ImmutableArray, ImmutableArray)> - QueryMultipleImmutableArrayAsync( - StoredProcedureParameters spParameters, - CancellationToken cancellationToken = default) - where TResultSet1 : class, ISpMapper - where TResultSet2 : class, ISpMapper - where TResultSet3 : class, ISpMapper - { - return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, 3, - nameof(QueryMultipleImmutableArrayAsync), async command => - { - await using var reader = await command.ExecuteReaderAsync( - CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false); - - var a1 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (a1, ImmutableArray.Empty, ImmutableArray.Empty); - - var a2 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (a1, a2, ImmutableArray.Empty); - - var a3 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - return (a1, a2, a3); - }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Execute a stored procedure and materialize up to four result sets as immutable arrays. - /// - /// Stored procedure metadata and parameters. - /// Token to cancel the operation. - /// A tuple containing the materialized result sets. Missing trailing result sets are empty. - public async Task<(ImmutableArray, ImmutableArray, ImmutableArray, - ImmutableArray)> - QueryMultipleImmutableArrayAsync( - StoredProcedureParameters spParameters, - CancellationToken cancellationToken = default) - where TResultSet1 : class, ISpMapper - where TResultSet2 : class, ISpMapper - where TResultSet3 : class, ISpMapper - where TResultSet4 : class, ISpMapper - { - return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, 4, - nameof(QueryMultipleImmutableArrayAsync), async command => - { - await using var reader = await command.ExecuteReaderAsync( - CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false); - - var a1 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (a1, ImmutableArray.Empty, ImmutableArray.Empty, - ImmutableArray.Empty); - - var a2 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (a1, a2, ImmutableArray.Empty, ImmutableArray.Empty); - - var a3 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (a1, a2, a3, ImmutableArray.Empty); - - var a4 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - return (a1, a2, a3, a4); - }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Execute a stored procedure and materialize up to five result sets as immutable arrays. - /// - /// Stored procedure metadata and parameters. - /// Token to cancel the operation. - /// A tuple containing the materialized result sets. Missing trailing result sets are empty. - public async Task<(ImmutableArray, ImmutableArray, ImmutableArray, - ImmutableArray, ImmutableArray)> - QueryMultipleImmutableArrayAsync( - StoredProcedureParameters spParameters, - CancellationToken cancellationToken = default) - where TResultSet1 : class, ISpMapper - where TResultSet2 : class, ISpMapper - where TResultSet3 : class, ISpMapper - where TResultSet4 : class, ISpMapper - where TResultSet5 : class, ISpMapper - { - return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, 5, - nameof(QueryMultipleImmutableArrayAsync), async command => - { - await using var reader = await command.ExecuteReaderAsync( - CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false); - - var a1 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (a1, ImmutableArray.Empty, ImmutableArray.Empty, - ImmutableArray.Empty, ImmutableArray.Empty); - - var a2 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (a1, a2, ImmutableArray.Empty, ImmutableArray.Empty, - ImmutableArray.Empty); - - var a3 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (a1, a2, a3, ImmutableArray.Empty, ImmutableArray.Empty); - - var a4 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (a1, a2, a3, a4, ImmutableArray.Empty); - - var a5 = await MultiResultSetHelper.ReadResultSetAsImmutableArrayAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - - return (a1, a2, a3, a4, a5); - }, cancellationToken).ConfigureAwait(false); - } } } diff --git a/Src/Commands/Reads/MultiReadOnlyCollectionReadSqlAsyncCommands.cs b/Src/Commands/Reads/MultiReadOnlyCollectionReadSqlAsyncCommands.cs index 603de02..6e3f4b9 100644 --- a/Src/Commands/Reads/MultiReadOnlyCollectionReadSqlAsyncCommands.cs +++ b/Src/Commands/Reads/MultiReadOnlyCollectionReadSqlAsyncCommands.cs @@ -39,158 +39,5 @@ public static class MultiReadOnlyCollectionReadSqlAsyncCommands return (r1, r2); }, cancellationToken).ConfigureAwait(false); } - - /// - /// Execute a stored procedure and materialize up to three result sets as read-only collections. - /// - /// Stored procedure metadata and parameters. - /// Token to cancel the operation. - /// A tuple containing the materialized result sets. Missing trailing result sets are empty. - public async Task<(ReadOnlyCollection, ReadOnlyCollection, - ReadOnlyCollection)> - QueryMultipleReadOnlyCollectionAsync( - StoredProcedureParameters spParameters, - CancellationToken cancellationToken = default) - where TResultSet1 : class, ISpMapper - where TResultSet2 : class, ISpMapper - where TResultSet3 : class, ISpMapper - { - return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, 3, - nameof(QueryMultipleReadOnlyCollectionAsync), async command => - { - await using var reader = await command.ExecuteReaderAsync( - CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false); - - var l1 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r1 = l1.AsReadOnly(); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (r1, [], []); - - var l2 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r2 = l2.AsReadOnly(); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (r1, r2, []); - - var l3 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r3 = l3.AsReadOnly(); - - return (r1, r2, r3); - }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Execute a stored procedure and materialize up to four result sets as read-only collections. - /// - /// Stored procedure metadata and parameters. - /// Token to cancel the operation. - /// A tuple containing the materialized result sets. Missing trailing result sets are empty. - public async Task<(ReadOnlyCollection, ReadOnlyCollection, - ReadOnlyCollection, ReadOnlyCollection)> - QueryMultipleReadOnlyCollectionAsync( - StoredProcedureParameters spParameters, - CancellationToken cancellationToken = default) - where TResultSet1 : class, ISpMapper - where TResultSet2 : class, ISpMapper - where TResultSet3 : class, ISpMapper - where TResultSet4 : class, ISpMapper - { - return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, 4, - nameof(QueryMultipleReadOnlyCollectionAsync), async command => - { - await using var reader = await command.ExecuteReaderAsync( - CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false); - - var l1 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r1 = l1.AsReadOnly(); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (r1, [], [], []); - - var l2 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r2 = l2.AsReadOnly(); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (r1, r2, [], []); - - var l3 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r3 = l3.AsReadOnly(); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (r1, r2, r3, []); - - var l4 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r4 = l4.AsReadOnly(); - - return (r1, r2, r3, r4); - }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Execute a stored procedure and materialize up to five result sets as read-only collections. - /// - /// Stored procedure metadata and parameters. - /// Token to cancel the operation. - /// A tuple containing the materialized result sets. Missing trailing result sets are empty. - public async Task<(ReadOnlyCollection, ReadOnlyCollection, - ReadOnlyCollection, ReadOnlyCollection, ReadOnlyCollection)> - QueryMultipleReadOnlyCollectionAsync( - StoredProcedureParameters spParameters, - CancellationToken cancellationToken = default) - where TResultSet1 : class, ISpMapper - where TResultSet2 : class, ISpMapper - where TResultSet3 : class, ISpMapper - where TResultSet4 : class, ISpMapper - where TResultSet5 : class, ISpMapper - { - return await CaeriusActivityExtensions.InstrumentMultiResultSetAsync(context, spParameters, 5, - nameof(QueryMultipleReadOnlyCollectionAsync), async command => - { - await using var reader = await command.ExecuteReaderAsync( - CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false); - - var l1 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r1 = l1.AsReadOnly(); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (r1, [], [], [], []); - - var l2 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r2 = l2.AsReadOnly(); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (r1, r2, [], [], []); - - var l3 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r3 = l3.AsReadOnly(); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (r1, r2, r3, [], []); - - var l4 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r4 = l4.AsReadOnly(); - - if (!await MultiResultSetHelper.TryMoveNextAsync(reader, cancellationToken).ConfigureAwait(false)) - return (r1, r2, r3, r4, []); - - var l5 = await MultiResultSetHelper.ReadResultSetAsync( - reader, spParameters.Capacity, cancellationToken).ConfigureAwait(false); - var r5 = l5.AsReadOnly(); - - return (r1, r2, r3, r4, r5); - }, cancellationToken).ConfigureAwait(false); - } } } diff --git a/Src/Factories/CaeriusNetTransaction.cs b/Src/Factories/CaeriusNetTransaction.cs index 50136d0..77cbb7e 100644 --- a/Src/Factories/CaeriusNetTransaction.cs +++ b/Src/Factories/CaeriusNetTransaction.cs @@ -153,7 +153,7 @@ public async ValueTask DisposeAsync() if (prev == StateDisposed) return; var transaction = _transaction; - if ((prev is StateActive or StatePoisoned) && transaction is not null) + if (prev is StateActive or StatePoisoned && transaction is not null) try { await transaction.RollbackAsync().ConfigureAwait(false); @@ -169,7 +169,7 @@ public async ValueTask DisposeAsync() _txActivity = null; } - await DisposeOwnedResourcesAsync(suppressExceptions: false).ConfigureAwait(false); + await DisposeOwnedResourcesAsync(false).ConfigureAwait(false); } finally { @@ -221,12 +221,12 @@ internal static async ValueTask BeginAsync( } catch (SqlException ex) { - await DisposeResourcesAsync(transaction, connection, suppressExceptions: true).ConfigureAwait(false); + await DisposeResourcesAsync(transaction, connection, true).ConfigureAwait(false); throw new CaeriusNetSqlException("Failed to open SQL Server transaction.", ex); } catch { - await DisposeResourcesAsync(transaction, connection, suppressExceptions: true).ConfigureAwait(false); + await DisposeResourcesAsync(transaction, connection, true).ConfigureAwait(false); throw; } } diff --git a/Src/Helpers/MultiResultSetHelper.cs b/Src/Helpers/MultiResultSetHelper.cs index 0370988..a36e3ee 100644 --- a/Src/Helpers/MultiResultSetHelper.cs +++ b/Src/Helpers/MultiResultSetHelper.cs @@ -60,5 +60,4 @@ internal static ValueTask TryMoveNextAsync( ? new ValueTask(task.Result) : new ValueTask(task); } - } diff --git a/Src/Helpers/ResultSetMaterializer.cs b/Src/Helpers/ResultSetMaterializer.cs index 82c0df2..dfe7335 100644 --- a/Src/Helpers/ResultSetMaterializer.cs +++ b/Src/Helpers/ResultSetMaterializer.cs @@ -80,6 +80,6 @@ private static T[] GrowBuffer(T[] buffer, int count) private static void ReturnBuffer(T[] buffer) where T : class { - ArrayPool.Shared.Return(buffer, clearArray: true); + ArrayPool.Shared.Return(buffer, true); } } diff --git a/Src/Mappers/StoredProcedureParameters.cs b/Src/Mappers/StoredProcedureParameters.cs index 797c21b..1011365 100644 --- a/Src/Mappers/StoredProcedureParameters.cs +++ b/Src/Mappers/StoredProcedureParameters.cs @@ -118,11 +118,9 @@ internal static string Normalize(string parameterName) throw new ArgumentException("SQL parameter names must use a single leading '@'.", nameof(parameterName)); for (var i = 0; i < normalized.Length; i++) - { if (char.IsControl(normalized[i])) throw new ArgumentException("SQL parameter names cannot contain control characters.", nameof(parameterName)); - } return normalized; } diff --git a/Src/README.md b/Src/README.md index fcc4d53..d880b5c 100644 --- a/Src/README.md +++ b/Src/README.md @@ -211,7 +211,8 @@ var sp = new StoredProcedureParametersBuilder("dbo", "sp_GetDashboard", ResultSe ``` Supported up to 5 result sets: `QueryMultipleIEnumerableAsync` through -`QueryMultipleIEnumerableAsync`. +`QueryMultipleIEnumerableAsync`. The two-result-set overload is the hand-written baseline; +arities 3 through 5 are generated into the package assembly during build. ## Available Query Methods @@ -312,4 +313,4 @@ Source code & releases: **[https://github.com/CaeriusNET/CaeriusNet](https://git ## License -MIT \ No newline at end of file +MIT diff --git a/Tests/CaeriusNet.Analyzer.Tests/AutoContractsManifestAnalyzerTests.cs b/Tests/CaeriusNet.Analyzer.Tests/AutoContractsManifestAnalyzerTests.cs index 34d213a..7a55a75 100644 --- a/Tests/CaeriusNet.Analyzer.Tests/AutoContractsManifestAnalyzerTests.cs +++ b/Tests/CaeriusNet.Analyzer.Tests/AutoContractsManifestAnalyzerTests.cs @@ -3,13 +3,13 @@ namespace CaeriusNet.Analyzer.Tests; public sealed class AutoContractsManifestAnalyzerTests { private const string ValidEmptyManifest = """ - { - "version": 1, - "namespace": "Consumer.Contracts", - "tableTypes": [], - "procedures": [] - } - """; + { + "version": 1, + "namespace": "Consumer.Contracts", + "tableTypes": [], + "procedures": [] + } + """; [Fact] public void EmptyManifest_Reports_CAERIUS200() @@ -30,7 +30,7 @@ public void CustomManifestPath_WithMetadata_Is_Consumed_And_Satisfies_Pull_Mode( { ["build_property.CaeriusContractsMode"] = " Pull " }, - Manifest(ValidEmptyManifest, "generated.contracts.json", isManifest: true)); + Manifest(ValidEmptyManifest, "generated.contracts.json", true)); Assert.DoesNotContain(diagnostics, diagnostic => diagnostic.Id.StartsWith("CAERIUS2", StringComparison.Ordinal)); @@ -229,7 +229,7 @@ public void ManifestDiagnostics_Are_Reported_On_AdditionalFile_Location() ], "procedures": [] } - """, "contracts/custom.contracts.json", isManifest: true)); + """, "contracts/custom.contracts.json", true)); var diagnostic = Assert.Single(diagnostics, diagnostic => diagnostic.Id == "CAERIUS203"); Assert.NotEqual(Location.None, diagnostic.Location); diff --git a/Tests/CaeriusNet.Analyzer.Tests/Utilities/AnalyzerTestHelper.cs b/Tests/CaeriusNet.Analyzer.Tests/Utilities/AnalyzerTestHelper.cs index 4a821c9..6d504e5 100644 --- a/Tests/CaeriusNet.Analyzer.Tests/Utilities/AnalyzerTestHelper.cs +++ b/Tests/CaeriusNet.Analyzer.Tests/Utilities/AnalyzerTestHelper.cs @@ -108,6 +108,7 @@ internal sealed class TestAnalyzerConfigOptionsProvider( IReadOnlyList? additionalTexts = null) : AnalyzerConfigOptionsProvider { private static readonly AnalyzerConfigOptions EmptyOptions = new TestAnalyzerConfigOptions(null); + private readonly IReadOnlyDictionary> _additionalOptions = BuildAdditionalOptions(additionalTexts); @@ -163,6 +164,7 @@ internal sealed class TestAdditionalText( IReadOnlyDictionary? options = null) : AdditionalText { public override string Path { get; } = path; + internal IReadOnlyDictionary Options { get; } = options ?? new Dictionary(StringComparer.Ordinal); diff --git a/Tests/CaeriusNet.Generator.Tests/AutoContracts/AutoContractsSourceGeneratorTests.cs b/Tests/CaeriusNet.Generator.Tests/AutoContracts/AutoContractsSourceGeneratorTests.cs index 6c11965..7772cfd 100644 --- a/Tests/CaeriusNet.Generator.Tests/AutoContracts/AutoContractsSourceGeneratorTests.cs +++ b/Tests/CaeriusNet.Generator.Tests/AutoContracts/AutoContractsSourceGeneratorTests.cs @@ -143,7 +143,7 @@ public void CustomManifestPath_WithMetadata_Is_Consumed() { var result = SourceGeneratorTestHelper.RunGenerator( "namespace Consumer;", - Manifest(ValidManifest, "generated.contracts.json", isManifest: true)); + Manifest(ValidManifest, "generated.contracts.json", true)); Assert.Single(result.GeneratedTrees); } diff --git a/Tests/CaeriusNet.Generator.Tests/MultiResults/MultiResultSetSignaturesSourceGeneratorTests.cs b/Tests/CaeriusNet.Generator.Tests/MultiResults/MultiResultSetSignaturesSourceGeneratorTests.cs new file mode 100644 index 0000000..23cafeb --- /dev/null +++ b/Tests/CaeriusNet.Generator.Tests/MultiResults/MultiResultSetSignaturesSourceGeneratorTests.cs @@ -0,0 +1,38 @@ +using CaeriusNet.Generator.MultiResults; + +namespace CaeriusNet.Generator.Tests.MultiResults; + +public sealed class MultiResultSetSignaturesSourceGeneratorTests +{ + [Fact] + public void RuntimeAssembly_Generates_Multi_Result_Signatures() + { + var result = SourceGeneratorTestHelper.RunGeneratorForAssembly( + "namespace CaeriusNet;", + "CaeriusNet"); + + Assert.Single(result.GeneratedTrees); + + var generated = result.GeneratedTrees[0].GetText().ToString(); + Assert.Contains("public static class GeneratedMultiIEnumerableReadSqlAsyncCommands", generated); + Assert.Contains("public static class GeneratedMultiReadOnlyCollectionReadSqlAsyncCommands", generated); + Assert.Contains("public static class GeneratedMultiImmutableArrayReadSqlAsyncCommands", generated); + Assert.Contains("QueryMultipleIEnumerableAsync", generated); + Assert.Contains("QueryMultipleReadOnlyCollectionAsync", + generated); + Assert.Contains( + "QueryMultipleImmutableArrayAsync", + generated); + Assert.DoesNotContain("QueryMultipleIEnumerableAsync(", generated, + StringComparison.Ordinal); + } + + [Fact] + public void ConsumerAssembly_Does_Not_Generate_Runtime_Extensions() + { + var result = SourceGeneratorTestHelper.RunGenerator( + "namespace Consumer;"); + + Assert.Empty(result.GeneratedTrees); + } +} diff --git a/Tests/CaeriusNet.Generator.Tests/Utilities/SourceGeneratorTestHelper.cs b/Tests/CaeriusNet.Generator.Tests/Utilities/SourceGeneratorTestHelper.cs index ace048d..222a052 100644 --- a/Tests/CaeriusNet.Generator.Tests/Utilities/SourceGeneratorTestHelper.cs +++ b/Tests/CaeriusNet.Generator.Tests/Utilities/SourceGeneratorTestHelper.cs @@ -24,10 +24,19 @@ internal static GeneratorDriverRunResult RunGenerator( string source, params AdditionalText[] additionalTexts) where TGenerator : IIncrementalGenerator, new() + { + return RunGeneratorForAssembly(source, "TestAssembly", additionalTexts); + } + + internal static GeneratorDriverRunResult RunGeneratorForAssembly( + string source, + string assemblyName, + params AdditionalText[] additionalTexts) + where TGenerator : IIncrementalGenerator, new() { var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest); var syntaxTree = CSharpSyntaxTree.ParseText(source, parseOptions); - var compilation = CreateCompilation(syntaxTree); + var compilation = CreateCompilation(syntaxTree, assemblyName); var generator = new TGenerator(); var driver = CSharpGeneratorDriver.Create(generator) @@ -76,11 +85,16 @@ internal static CSharpCompilation CreateTestCompilation(SyntaxTree syntaxTree) } private static CSharpCompilation CreateCompilation(SyntaxTree syntaxTree) + { + return CreateCompilation(syntaxTree, "TestAssembly"); + } + + private static CSharpCompilation CreateCompilation(SyntaxTree syntaxTree, string assemblyName) { var references = BuildMetadataReferences(); return CSharpCompilation.Create( - "TestAssembly", + assemblyName, [syntaxTree], references, new CSharpCompilationOptions( @@ -125,6 +139,7 @@ internal sealed class TestAnalyzerConfigOptionsProvider( IReadOnlyList? additionalTexts = null) : AnalyzerConfigOptionsProvider { private static readonly AnalyzerConfigOptions EmptyOptions = new TestAnalyzerConfigOptions(null); + private readonly IReadOnlyDictionary> _additionalOptions = BuildAdditionalOptions(additionalTexts); @@ -179,6 +194,7 @@ internal sealed class TestAdditionalText( IReadOnlyDictionary? options = null) : AdditionalText { public override string Path { get; } = path; + internal IReadOnlyDictionary Options { get; } = options ?? new Dictionary(StringComparer.Ordinal); diff --git a/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTests.cs b/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTests.cs index 704d3f1..120cbc9 100644 --- a/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTests.cs +++ b/Tests/CaeriusNet.Tests/Builders/StoredProcedureParametersBuilderTests.cs @@ -1,3 +1,5 @@ +using System.Collections; + namespace CaeriusNet.Tests.Builders; public sealed class StoredProcedureParametersBuilderTests @@ -297,7 +299,7 @@ public void AddParameter_With_Facets_And_Direction_Preserves_Metadata() public void AddParameter_With_Size_Preserves_Metadata() { var sp = new StoredProcedureParametersBuilder("dbo", "sp_Test") - .AddParameter("@Name", "ari", SqlDbType.NVarChar, size: 64) + .AddParameter("@Name", "ari", SqlDbType.NVarChar, 64) .Build(); Assert.Equal(64, sp.GetParametersSpan()[0].Size); @@ -507,7 +509,7 @@ public IEnumerator GetEnumerator() return items.GetEnumerator(); } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } diff --git a/Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj b/Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj index af0abbf..8bd8c06 100644 --- a/Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj +++ b/Tools/CaeriusNet.SqlServer.Contracts/CaeriusNet.SqlServer.Contracts.csproj @@ -26,7 +26,7 @@ - + diff --git a/Tools/CaeriusNet.SqlServer.Contracts/SqlServerContractDiscoverer.cs b/Tools/CaeriusNet.SqlServer.Contracts/SqlServerContractDiscoverer.cs index 1679a08..f31fac4 100644 --- a/Tools/CaeriusNet.SqlServer.Contracts/SqlServerContractDiscoverer.cs +++ b/Tools/CaeriusNet.SqlServer.Contracts/SqlServerContractDiscoverer.cs @@ -17,7 +17,8 @@ internal static async Task DiscoverAsync( await connection.OpenAsync(cancellationToken).ConfigureAwait(false); var database = await DiscoverDatabaseAsync(connection, cancellationToken).ConfigureAwait(false); - var tableTypes = await DiscoverTableTypesAsync(connection, diagnostics, cancellationToken).ConfigureAwait(false); + var tableTypes = + await DiscoverTableTypesAsync(connection, diagnostics, cancellationToken).ConfigureAwait(false); var procedures = await DiscoverProceduresAsync(connection, tableTypes, diagnostics, cancellationToken) .ConfigureAwait(false); From 8d1c0a64ad3b957e4d28af017d7e6da403a5cb44 Mon Sep 17 00:00:00 2001 From: Arius Date: Thu, 14 May 2026 22:52:06 +0200 Subject: [PATCH 3/5] refactor: standardize terminology and formatting in documentation --- Documentations/docs/.vitepress/config.mts | 74 ++-- Documentations/docs/benchmarks/sql-server.md | 94 +++--- Documentations/docs/diagnostics/CAERIUS003.md | 2 +- Documentations/docs/diagnostics/CAERIUS004.md | 4 +- Documentations/docs/diagnostics/CAERIUS005.md | 2 +- .../docs/documentation/advanced-usage.md | 25 +- Documentations/docs/documentation/api.md | 30 +- Documentations/docs/documentation/aspire.md | 22 +- .../docs/documentation/auto-contracts.md | 99 +++--- .../docs/documentation/best-practices.md | 49 ++- Documentations/docs/documentation/cache.md | 2 +- .../docs/documentation/diagnostics.md | 4 +- .../docs/documentation/dto-mapping.md | 20 +- Documentations/docs/documentation/logging.md | 4 +- .../docs/documentation/multi-results.md | 158 +++++---- .../docs/documentation/reading-data.md | 14 +- .../docs/documentation/source-generators.md | 318 ++++++------------ .../docs/documentation/transactions.md | 27 +- Documentations/docs/documentation/tvp.md | 30 +- Documentations/docs/documentation/usage.md | 116 +++++-- .../docs/documentation/writing-data.md | 16 +- Documentations/docs/examples/index.md | 10 +- .../docs/examples/multi-result-sets.md | 30 +- .../docs/examples/stored-procedures.md | 24 +- Documentations/docs/examples/transactions.md | 28 +- Documentations/docs/examples/tvp.md | 20 +- Documentations/docs/index.md | 106 +++--- .../docs/quickstart/getting-started.md | 143 ++++---- .../docs/quickstart/what-is-caeriusnet.md | 185 +++++----- README.md | 5 +- .../MultiResultSetSignaturesEmitter.cs | 8 +- SourceGenerators/README.md | 1 + .../MultiIEnumerableReadSqlAsyncCommands.cs | 9 +- ...MultiImmutableArrayReadSqlAsyncCommands.cs | 9 +- ...iReadOnlyCollectionReadSqlAsyncCommands.cs | 9 +- Src/Helpers/ResultSetMaterializer.cs | 22 +- Src/Helpers/SqlCommandHelper.cs | 15 +- Src/Helpers/SqlCommandHelperTx.cs | 29 +- Src/Mappers/StoredProcedureParameters.cs | 8 + Src/README.md | 4 +- Src/Telemetry/CaeriusActivityExtensions.cs | 53 ++- ...ResultSetSignaturesSourceGeneratorTests.cs | 10 + .../Sql/schema.sql | 27 ++ .../Tests/MultiResultSetTests.cs | 41 ++- .../Tests/ReadCommandsTests.cs | 13 + .../Commands/MultiResultApiSurfaceTests.cs | 37 ++ Tests/README.md | 2 +- 47 files changed, 1019 insertions(+), 939 deletions(-) create mode 100644 Tests/CaeriusNet.Tests/Commands/MultiResultApiSurfaceTests.cs diff --git a/Documentations/docs/.vitepress/config.mts b/Documentations/docs/.vitepress/config.mts index 51386aa..d969fdd 100644 --- a/Documentations/docs/.vitepress/config.mts +++ b/Documentations/docs/.vitepress/config.mts @@ -18,19 +18,26 @@ export default defineConfig({ { text: 'Home', link: '/' }, { text: 'Quickstart', link: '/quickstart/getting-started' }, { - text: 'Guides', + text: 'Use CaeriusNet', items: [ + { text: 'Usage overview', link: '/documentation/usage' }, + { text: 'Reading data', link: '/documentation/reading-data' }, + { text: 'Writing data', link: '/documentation/writing-data' }, { text: 'DTO Mapping', link: '/documentation/dto-mapping' }, - { text: 'Source Generators', link: '/documentation/source-generators' }, - { text: 'AutoContracts', link: '/documentation/auto-contracts' }, - { text: 'Reading Data', link: '/documentation/reading-data' }, - { text: 'Writing Data', link: '/documentation/writing-data' }, - { text: 'Table-Valued Parameters', link: '/documentation/tvp' }, - { text: 'Multiple Result Sets', link: '/documentation/multi-results' }, + { text: 'Table-valued parameters', link: '/documentation/tvp' }, + { text: 'Multiple result sets', link: '/documentation/multi-results' }, { text: 'Caching', link: '/documentation/cache' }, - { text: 'Transactions', link: '/documentation/transactions' }, - { text: 'Logging & Observability', link: '/documentation/logging' }, - { text: 'Aspire Integration', link: '/documentation/aspire' }, + { text: 'Transactions', link: '/documentation/transactions' } + ] + }, + { + text: 'Advanced', + items: [ + { text: 'Source generators', link: '/documentation/source-generators' }, + { text: 'AutoContracts', link: '/documentation/auto-contracts' }, + { text: 'Advanced usage', link: '/documentation/advanced-usage' }, + { text: 'Logging and observability', link: '/documentation/logging' }, + { text: 'Aspire integration', link: '/documentation/aspire' }, ] }, { @@ -49,45 +56,46 @@ export default defineConfig({ sidebar: [ { - text: 'Quickstart', + text: 'Get started', collapsed: false, items: [ { text: 'What is CaeriusNet?', link: '/quickstart/what-is-caeriusnet' }, - { text: 'Installation & Setup', link: '/quickstart/getting-started' } + { text: 'Install and configure', link: '/quickstart/getting-started' }, + { text: 'Usage overview', link: '/documentation/usage' } ] }, { - text: 'Core Concepts', + text: 'Use CaeriusNet', collapsed: false, items: [ - { text: 'DTO Mapping', link: '/documentation/dto-mapping' }, - { text: 'Source Generators', link: '/documentation/source-generators' }, - { text: 'AutoContracts', link: '/documentation/auto-contracts' }, - { text: 'Table-Valued Parameters', link: '/documentation/tvp' } + { text: 'Reading data', link: '/documentation/reading-data' }, + { text: 'Writing data', link: '/documentation/writing-data' }, + { text: 'DTO mapping', link: '/documentation/dto-mapping' }, + { text: 'Table-valued parameters', link: '/documentation/tvp' }, + { text: 'Multiple result sets', link: '/documentation/multi-results' }, + { text: 'Caching', link: '/documentation/cache' }, + { text: 'Transactions', link: '/documentation/transactions' } ] }, { - text: 'Guides', + text: 'Advanced scenarios', collapsed: false, items: [ - { text: 'Reading Data', link: '/documentation/reading-data' }, - { text: 'Writing Data', link: '/documentation/writing-data' }, - { text: 'Multiple Result Sets', link: '/documentation/multi-results' }, - { text: 'Caching', link: '/documentation/cache' }, - { text: 'Transactions', link: '/documentation/transactions' }, - { text: 'Logging & Observability', link: '/documentation/logging' }, - { text: 'Aspire Integration', link: '/documentation/aspire' }, - { text: 'Advanced Usage', link: '/documentation/advanced-usage' } + { text: 'Source generators', link: '/documentation/source-generators' }, + { text: 'AutoContracts', link: '/documentation/auto-contracts' }, + { text: 'Logging and observability', link: '/documentation/logging' }, + { text: 'Aspire integration', link: '/documentation/aspire' }, + { text: 'Advanced usage', link: '/documentation/advanced-usage' } ] }, { text: 'Reference', collapsed: true, items: [ - { text: 'API Reference', link: '/documentation/api' }, - { text: 'Best Practices', link: '/documentation/best-practices' }, - { text: 'Telemetry & Diagnostics', link: '/documentation/diagnostics' }, - { text: 'Diagnostic Rules', link: '/diagnostics/' } + { text: 'API reference', link: '/documentation/api' }, + { text: 'Best practices', link: '/documentation/best-practices' }, + { text: 'Telemetry and diagnostics', link: '/documentation/diagnostics' }, + { text: 'Diagnostic rules', link: '/diagnostics/' } ] }, { @@ -119,9 +127,9 @@ export default defineConfig({ collapsed: false, items: [ { text: 'Overview', link: '/examples/' }, - { text: 'Stored Procedures', link: '/examples/stored-procedures' }, - { text: 'Table-Valued Parameters', link: '/examples/tvp' }, - { text: 'Multi-Result Sets', link: '/examples/multi-result-sets' }, + { text: 'Stored procedures', link: '/examples/stored-procedures' }, + { text: 'Table-valued parameters', link: '/examples/tvp' }, + { text: 'Multi-result sets', link: '/examples/multi-result-sets' }, { text: 'Transactions', link: '/examples/transactions' } ] }, diff --git a/Documentations/docs/benchmarks/sql-server.md b/Documentations/docs/benchmarks/sql-server.md index bd7f0a9..69629eb 100644 --- a/Documentations/docs/benchmarks/sql-server.md +++ b/Documentations/docs/benchmarks/sql-server.md @@ -1,9 +1,9 @@ --- -title: SQL Server Benchmarks -description: CaeriusNet SQL Server end-to-end benchmarks — stored procedure execution, batched vs single inserts, TVP full roundtrip, OUTPUT parameters, and connection pool reuse. +title: SQL Server benchmarks +description: CaeriusNet SQL Server end-to-end benchmarks for stored procedure execution, batched inserts, TVPs, OUTPUT parameters, and connection pool reuse. --- -# SQL Server Benchmarks +# SQL Server benchmarks These benchmarks measure **real end-to-end latency** of CaeriusNet operations against a live SQL Server 2022 instance running inside a Docker service container on Ubuntu. @@ -26,16 +26,16 @@ SQL Server benchmark results vary with server edition, CPU, memory, storage, ind --- -## Stored Procedure Execution Roundtrip +## Stored procedure execution round trip **Benchmark class: `SpExecutionBench`** ### What is measured -Full stored procedure roundtrip: `SqlConnection.Open()` (from pool) → `SqlCommand.ExecuteReaderAsync()` -→ `while (reader.ReadAsync())` → return row count. +Full stored procedure round trip: `SqlConnection.Open()` from the pool, `SqlCommand.ExecuteReaderAsync()`, +`while (reader.ReadAsync())`, and the final row count. -`RowCount` controls the `TOP N` in the SP query — the benchmark measures how roundtrip latency +`RowCount` controls the `TOP N` in the stored procedure query. The benchmark measures how round trip latency scales from 0 rows (no data, pure call overhead) to 50 000 rows (large result set streaming). `[Params(0, 10, 100, 1_000, 5_000, 10_000, 50_000)]` @@ -43,9 +43,9 @@ scales from 0 rows (no data, pure call overhead) to 50 000 rows (large result se ### Key insights - **At RowCount = 0**, the measurement isolates pure call overhead: TDS command framing + SQL Server parse/compile + - empty result set return. This is the irreducible floor for any SP call. + empty result set return. This is the irreducible floor for any stored procedure call. - **At small row counts (10–100)**, connection establishment and command preparation dominate over data transfer. - Connection pooling amortises the TCP handshake across calls — the warm-pool cost is lower + Connection pooling amortizes the TCP handshake across calls. The warm-pool cost is lower than a cold-start `SqlConnection`. - **At large row counts (5 000–50 000)**, streaming throughput (rows per microsecond) becomes the binding factor. CaeriusNet's `SqlDataReader` iteration cost grows linearly with rows. @@ -53,67 +53,67 @@ scales from 0 rows (no data, pure call overhead) to 50 000 rows (large result se --- -## Batched vs Single Inserts — The TVP Advantage +## Batched inserts vs single inserts **Benchmark class: `BatchedVsSingleBench`** ### What is measured -This benchmark compares per-row stored-procedure calls with a single TVP-based stored-procedure call. +This benchmark compares per-row stored procedure calls with a single TVP-based stored procedure call. Two insertion strategies are compared at `[Params(10, 100, 500, 1_000, 5_000)]` items: -| Method | Strategy | Roundtrips | +| Method | Strategy | Round trips | |---|---|---| -| `Insert_SingleCalls` *(Baseline)* | One `INSERT INTO ... VALUES (...)` SP call per item | N roundtrips | -| `Insert_BatchedTvp` | One `INSERT INTO ... SELECT * FROM @tvp` SP call with a TVP | 1 roundtrip | +| `Insert_SingleCalls` *(Baseline)* | One `INSERT INTO ... VALUES (...)` stored procedure call per item | N round trips | +| `Insert_BatchedTvp` | One `INSERT INTO ... SELECT * FROM @tvp` stored procedure call with a TVP | 1 round trip | ### Key insights -- **Single-call strategy costs O(N) roundtrips.** Each roundtrip includes: +- **Single-call strategy costs O(N) round trips.** Each round trip includes: TCP segment send + SQL Server parse + plan lookup + lock acquisition + row write + result return. At N = 1 000, this is 1 000 independent TCP exchanges. -- **TVP batch strategy costs O(1) roundtrips.** The entire dataset is serialized into the TVP stream, +- **TVP batch strategy costs O(1) round trips.** The entire dataset is serialized into the TVP stream, transmitted in a single TDS message batch, and processed in one server-side INSERT. - The performance gap between the two strategies widens with item count: - At N = 10: the difference exists but is modest (TVP has a fixed setup overhead). -- At N = 1 000: TVP avoids per-row roundtrip cost; confirm the measured ratio from your benchmark table. -- At N = 5 000: per-row roundtrip cost often dominates; confirm suitability from your benchmark table and latency budget. +- At N = 1 000: TVP avoids per-row round trip cost; confirm the measured ratio from your benchmark table. +- At N = 5 000: per-row round trip cost often dominates; confirm suitability from your benchmark table and latency budget. -- In production environments, network latency increases the cost of every roundtrip. +- In production environments, network latency increases the cost of every round trip. Local Docker numbers should not be treated as remote database latency estimates. --- -## Multi-Result Set vs Separate Calls +## Multiple result sets vs separate calls **Benchmark class: `MultiResultSetBench`** ### What is measured -CaeriusNet supports typed tuple multi-result-set SP calls — a single SP call that returns +CaeriusNet supports typed tuple multi-result-set calls: a single stored procedure call that returns multiple independent result sets, materialized via successive `reader.NextResultAsync()` calls. This benchmark compares: -- **Single SP, multiple result sets**: one TDS roundtrip, one connection checkout from pool, N result set reads. -- **N separate SP calls**: N independent roundtrips, N connection checkouts, each with parse/compile overhead. +- **Single stored procedure, multiple result sets**: one round trip, one connection checkout from the pool, and N result set reads. +- **N separate stored procedure calls**: N independent round trips, N connection checkouts, each with parse/compile overhead. ### Key insights -- Every SQL Server roundtrip adds a fixed overhead for TDS framing, plan cache lookup, and lock manager interaction. - Combining multiple result sets into one SP call eliminates all per-call fixed overhead after the first. +- Every SQL Server round trip adds a fixed overhead for command framing, plan cache lookup, and lock manager interaction. + Combining multiple result sets into one stored procedure call eliminates all per-call fixed overhead after the first. - The savings are most pronounced when each individual result set is small (< 100 rows) — in that regime, per-call overhead dominates over data transfer. - When individual result sets are large (> 10 000 rows), the gains from combining are proportionally smaller since data transfer time dominates over fixed overhead. -- Multi-result-set SPs also reduce connection pool contention under concurrent load by shortening total +- Multi-result-set stored procedures also reduce connection pool contention under concurrent load by shortening total connection hold time. --- -## TVP Full Roundtrip +## TVP full round trip **Benchmark class: `TvpFullRoundtripBench`** @@ -124,25 +124,25 @@ The complete CaeriusNet TVP pipeline, end-to-end: 1. `Randomizer.Seed = new Random(42)` → generate `RowCount` items via Bogus 2. `StoredProcedureParametersBuilder.AddTvpParameter(items)` → serialize to `SqlDataRecord` stream 3. `.Build()` → produce `SqlParameter[]` -4. `SqlCommand.ExecuteReaderAsync()` → execute SP with `OUTPUT INSERTED.*` -5. `while (reader.ReadAsync())` → stream back the inserted rows +4. `SqlCommand.ExecuteReaderAsync()` to execute the stored procedure with `OUTPUT INSERTED.*` +5. `while (reader.ReadAsync())` to stream back the inserted rows -Compared against a **manual ADO.NET TVP setup** without the builder — raw `SqlParameter(Structured)` assembly. +Compared against a **manual ADO.NET TVP setup** without the builder: raw `SqlParameter(Structured)` assembly. `[Params(10, 100, 1_000, 5_000, 10_000)]` ### Key insights - Compare the CaeriusNet builder against raw ADO.NET assembly by using the **Ratio**, **Error**, and **StdDev** columns together. Small differences may be measurement noise. -- The dominant cost at any row count > 100 is **network I/O + SQL Server execution** — not .NET-side work. +- The dominant cost at any row count greater than 100 is **network I/O + SQL Server execution**, not .NET-side work. - At RowCount = 10 000 the TVP pipeline throughput demonstrates that memory allocation stays constant (O(1) `SqlDataRecord` streaming) even as the SQL-side work grows linearly. -- The `OUTPUT INSERTED.*` pattern (streaming back inserted rows) proves that CaeriusNet's round-trip +- The `OUTPUT INSERTED.*` pattern (streaming back inserted rows) proves that CaeriusNet's round trip cost is dominated by the server-side work, not the client-side builder or serializer. --- -## OUTPUT Parameter vs SCOPE_IDENTITY() +## OUTPUT parameter vs SCOPE_IDENTITY() **Benchmark class: `SpOutputParameterBench`** @@ -150,27 +150,27 @@ Compared against a **manual ADO.NET TVP setup** without the builder — raw `Sql Two common patterns for retrieving a newly-inserted identity value: -| Method | SQL strategy | Roundtrips | .NET cost | +| Method | SQL strategy | Round trips | .NET cost | |---|---|---|---| -| `Insert_OutputParameter` *(Baseline)* | `@NewId INT OUTPUT` — value populated server-side inside the SP | 1 | One `SqlParameter` direction change | -| `Insert_ScopeIdentity` | `INSERT ...; SELECT SCOPE_IDENTITY()` — second result set after insert | 1 (same roundtrip, extra result) | `reader.NextResult()` + `reader.Read()` | -| `Insert_SeparateSelect` | `INSERT ...; SELECT MAX(Id) FROM ...` — separate query after SP call | 2 | Full second roundtrip | +| `Insert_OutputParameter` *(Baseline)* | `@NewId INT OUTPUT`, populated server-side inside the stored procedure | 1 | One `SqlParameter` direction change | +| `Insert_ScopeIdentity` | `INSERT ...; SELECT SCOPE_IDENTITY()`, second result set after insert | 1 (same round trip, extra result) | `reader.NextResult()` + `reader.Read()` | +| `Insert_SeparateSelect` | `INSERT ...; SELECT MAX(Id) FROM ...`, separate query after the stored procedure call | 2 | Full second round trip | ### Key insights - `@NewId INT OUTPUT` is the most efficient pattern: the identity value is written to the output parameter - slot by SQL Server during SP execution, retrieved by `SqlClient` as part of the SP's response packet. - No second result set, no second roundtrip. + slot by SQL Server during stored procedure execution, retrieved by `SqlClient` as part of the response packet. + No second result set, no second round trip. - `SELECT SCOPE_IDENTITY()` requires `reader.NextResultAsync()` to advance past the INSERT's empty result - set — a small but measurable overhead vs a pure OUTPUT parameter. -- The two-roundtrip `SELECT MAX(Id)` anti-pattern is significantly slower because it pays the full - per-roundtrip fixed overhead twice, plus it is unsafe under concurrent inserts. + set. This creates a small but measurable overhead compared with a pure OUTPUT parameter. +- The two-round-trip `SELECT MAX(Id)` anti-pattern is significantly slower because it pays the full + per-round-trip fixed overhead twice, plus it is unsafe under concurrent inserts. - **CaeriusNet best practice:** use `OUTPUT INSERTED.Id` (for multi-row TVP inserts) or `@NewId INT OUTPUT` (for single-row inserts). Never use a separate `SCOPE_IDENTITY()` query or `SELECT MAX(Id)`. --- -## Connection Pool Reuse vs Cold Start +## Connection pool reuse vs cold start **Benchmark class: `ConnectionPoolBench`** @@ -181,9 +181,9 @@ logical `Open/Close` calls. This benchmark quantifies the performance difference | Method | Strategy | |---|---| -| `Connect_WarmPool` *(Baseline)* | `Open()` returns a pooled physical connection — no TCP handshake | -| `Connect_ColdStart` | `ClearPool(connection)` then `Open()` — forces a new TCP handshake + TDS login | -| `Connect_Persistent` | Hold one physical connection open for the entire benchmark iteration — bypasses pool checkout overhead | +| `Connect_WarmPool` *(Baseline)* | `Open()` returns a pooled physical connection; no TCP handshake | +| `Connect_ColdStart` | `ClearPool(connection)` then `Open()`; forces a new TCP handshake and login | +| `Connect_Persistent` | Hold one physical connection open for the entire benchmark iteration; bypasses pool checkout overhead | ### Key insights @@ -192,7 +192,7 @@ logical `Open/Close` calls. This benchmark quantifies the performance difference - **Cold start** forces a full TCP three-way handshake + TDS pre-login + TDS login sequence. This is typically much more expensive than a warm pool checkout; use measured results for your environment. `ClearPool()` should **never be called in production** unless connection credentials change. -- **Persistent connection** bypasses checkout overhead — useful for understanding the irreducible +- **Persistent connection** bypasses checkout overhead. This is useful for understanding the irreducible command-execution cost (no pool interaction at all), but not suitable for concurrent workloads. - The Ratio between Warm Pool and Cold Start quantifies the value of the connection pool. In a local Docker environment, the cold-start cost is often dominated by the TDS handshake. diff --git a/Documentations/docs/diagnostics/CAERIUS003.md b/Documentations/docs/diagnostics/CAERIUS003.md index 89ba287..f5540b2 100644 --- a/Documentations/docs/diagnostics/CAERIUS003.md +++ b/Documentations/docs/diagnostics/CAERIUS003.md @@ -24,4 +24,4 @@ public sealed partial record FooDto(int Id, string Name, DateTime CreatedAt); ## See also - [DTO Mapping](/documentation/dto-mapping) -- [Source Generators](/documentation/source-generators) +- [source generators](/documentation/source-generators) diff --git a/Documentations/docs/diagnostics/CAERIUS004.md b/Documentations/docs/diagnostics/CAERIUS004.md index cfc6c50..eeabdcc 100644 --- a/Documentations/docs/diagnostics/CAERIUS004.md +++ b/Documentations/docs/diagnostics/CAERIUS004.md @@ -22,5 +22,5 @@ The `Schema` argument is optional and defaults to `dbo`. ## See also -- [Table-Valued Parameters](/documentation/tvp) -- [Source Generators — GenerateTvp](/documentation/source-generators#generatetvp-tvp-mapper) +- [table-valued parameters](/documentation/tvp) +- [source generators: GenerateTvp](/documentation/source-generators#generatetvp-tvp-mapper) diff --git a/Documentations/docs/diagnostics/CAERIUS005.md b/Documentations/docs/diagnostics/CAERIUS005.md index 83c0a1f..37c78eb 100644 --- a/Documentations/docs/diagnostics/CAERIUS005.md +++ b/Documentations/docs/diagnostics/CAERIUS005.md @@ -44,4 +44,4 @@ If you intentionally want `sql_variant`, suppress the warning with ## See also - [Compiler Diagnostics](/documentation/diagnostics#suppressing-diagnostics) -- [Table-Valued Parameters](/documentation/tvp) +- [table-valued parameters](/documentation/tvp) diff --git a/Documentations/docs/documentation/advanced-usage.md b/Documentations/docs/documentation/advanced-usage.md index 8ff00db..e1949cd 100644 --- a/Documentations/docs/documentation/advanced-usage.md +++ b/Documentations/docs/documentation/advanced-usage.md @@ -1,4 +1,4 @@ -# Advanced Usage +# Advanced usage This page collects patterns that combine multiple CaeriusNet features in real-world scenarios — TVPs alongside scalar parameters, conditional caching, multi-result-set calls with TVP filters, and fine-grained transaction handling. @@ -62,13 +62,14 @@ public async Task> GetUsersAsync( ## Multiple result sets with a TVP filter -Multi-result SP calls accept the same parameters as single-result calls — including TVPs and any cache tier: +Multi-result calls accept the same SQL parameters as single-result calls, including TVPs. Cache policies on +`StoredProcedureParametersBuilder` are ignored by `QueryMultiple*Async`; cache the repository-level result if the +complete tuple should be reused. ```csharp var sp = new StoredProcedureParametersBuilder("dbo", "sp_Get_Dashboard_Data", 512) .AddTvpParameter("FilterIds", filterIds) .AddParameter("MaxAge", maxAge, SqlDbType.Int) - .AddRedisCache($"dashboard:max:{maxAge}", TimeSpan.FromMinutes(3)) .Build(); var (users, orders) = await DbContext @@ -77,7 +78,7 @@ var (users, orders) = await DbContext ## Scalar return after a write -Use `ExecuteScalarAsync` when the SP returns a single value — for example a newly inserted identity: +Use `ExecuteScalarAsync` when the stored procedure returns a single value, such as a newly inserted identity: ```csharp var sp = new StoredProcedureParametersBuilder("dbo", "sp_InsertUser_Return_Id") @@ -88,14 +89,14 @@ var sp = new StoredProcedureParametersBuilder("dbo", "sp_InsertUser_Return_Id") var newId = await DbContext.ExecuteScalarAsync(sp, ct); ``` -## Performance levers +## Performance guidance | Technique | Benefit | |---|---| -| Pre-sized `resultSetCapacity` | Avoids `List` resizing for large result sets | -| `QueryAsImmutableArrayAsync` | Struct-backed, pool-allocated — zero extra copies | -| TVP `SqlDataRecord` reuse | Single instance per call regardless of row count | -| `AddFrozenCache` | Eliminates the DB round-trip entirely for static data | +| Pre-sized `resultSetCapacity` | Reduces resizing work for large result sets | +| `QueryAsImmutableArrayAsync` | Good for cached or shared read results | +| TVPs for set-based filters | Keeps SQL parameterized while avoiding many small calls | +| `AddFrozenCache` | Eliminates the database round trip for static data | | `CancellationToken` propagation | Cancels in-progress SQL commands when the request is aborted | ## Transaction patterns @@ -173,14 +174,14 @@ await using var tx = await DbContext try { await tx.ExecuteNonQueryAsync(sp1, ct); - await tx.ExecuteNonQueryAsync(sp2, ct); // may throw — poisons the scope + await tx.ExecuteNonQueryAsync(sp2, ct); // may throw; the scope is now poisoned await tx.CommitAsync(ct); } catch (CaeriusNetSqlException ex) { logger.LogWarning(ex, "Transaction poisoned by {Procedure}", ex.ProcedureName); await tx.RollbackAsync(ct); - throw; // retry at the caller — never partially recover + throw; // retry at the caller; never partially recover } ``` @@ -228,4 +229,4 @@ LoggerProvider.SetLogger(app.Services.GetRequiredService()); --- -**See also:** [Reading Data](/documentation/reading-data) ¡ [Writing Data](/documentation/writing-data) ¡ [Table-Valued Parameters](/documentation/tvp) ¡ [Multiple Result Sets](/documentation/multi-results) ¡ [Transactions](/documentation/transactions) ¡ [Logging](/documentation/logging) +**See also:** [Reading data](/documentation/reading-data), [Writing data](/documentation/writing-data), [table-valued parameters](/documentation/tvp), [Multiple result sets](/documentation/multi-results), [Transactions](/documentation/transactions), and [Logging](/documentation/logging). diff --git a/Documentations/docs/documentation/api.md b/Documentations/docs/documentation/api.md index 0ba499f..e9b977d 100644 --- a/Documentations/docs/documentation/api.md +++ b/Documentations/docs/documentation/api.md @@ -1,6 +1,6 @@ -# API Reference +# API reference -This page is a practical reference for the public API exposed by CaeriusNet. All examples target **C# 14 / .NET 10** with `Microsoft.Data.SqlClient`. Namespaces are abbreviated below — add the matching `using` directives in your project. C# 14 extension-block methods such as `QueryAsIEnumerableAsync`, `ExecuteNonQueryAsync`, and `BeginTransactionAsync` are available after importing their command namespaces. +This page is a practical reference for the public API exposed by CaeriusNet. All examples target **C# 14 / .NET 10** with `Microsoft.Data.SqlClient`. Namespaces are abbreviated below. Add the matching `using` directives in your project. C# 14 extension-block methods such as `QueryAsIEnumerableAsync`, `ExecuteNonQueryAsync`, and `BeginTransactionAsync` are available after you import their command namespaces. > Examples assume DI is configured via `CaeriusNetBuilder` (see [Installation & Setup](/quickstart/getting-started)). > @@ -41,7 +41,7 @@ CaeriusNetBuilder ### `CaeriusNet.Builders.StoredProcedureParametersBuilder` -Fluent builder for Stored Procedure execution settings, parameters, and caching. +Fluent builder for stored procedure execution settings, parameters, and caching. ::: tip Parameter identifiers Use the parameter name only when calling `AddParameter` or `AddTvpParameter`; do not include the SQL `@` prefix. This keeps C# call sites consistent while SQL definitions continue to use normal SQL Server parameter syntax. @@ -133,7 +133,7 @@ builder.Services.AddOpenTelemetry() .WithMetrics(m => m.AddMeter (CaeriusDiagnostics.SourceName)); ``` -See [Aspire Integration — Tracing & Telemetry](/documentation/aspire#tracing-telemetry) for the complete tag and metric reference. +See [Aspire integration](/documentation/aspire#tracing-telemetry) for the complete tag and metric reference. ## Abstractions @@ -224,7 +224,7 @@ public sealed record UserIdsTvp(int Id) : ITvpMapper } ``` -## Attributes (Source Generators) +## Attributes and source generators ### `CaeriusNet.Attributes.Dto.GenerateDtoAttribute` @@ -253,7 +253,7 @@ Example: public sealed partial record UsersIntTvp(int UserId); ``` -See [Source Generators](/documentation/source-generators) for the complete generator behaviour and [Compiler Diagnostics](/documentation/diagnostics) for the analyzer rules. +See [source generators](/documentation/source-generators) for the complete generator behavior and [compiler diagnostics](/documentation/diagnostics) for the analyzer rules. ## Read commands @@ -268,7 +268,7 @@ ValueTask ValueTask> QueryAsReadOnlyCollectionAsync(this ICaeriusNetDbContext, StoredProcedureParameters, CancellationToken); -ValueTask?> +ValueTask> QueryAsIEnumerableAsync(this ICaeriusNetDbContext, StoredProcedureParameters, CancellationToken); ValueTask> @@ -313,25 +313,25 @@ await dbContext.ExecuteAsync(sp, ct); ## Multiple result sets -Up to **5 result sets** per call. Each return type maps positionally to a `SELECT` statement in the SP. +Up to **5 result sets** per call. Each return type maps positionally to a `SELECT` statement in the stored procedure. -Namespace: `CaeriusNet.Commands.Reads`. The `T1,T2` overloads are maintained as the runtime source baseline; overloads for arities 3 through 5 are generated into the `CaeriusNet` assembly during package build. +Namespace: `CaeriusNet.Commands.Reads`. ```csharp -Task<(IEnumerable, IEnumerable)> +ValueTask<(IEnumerable, IEnumerable)> QueryMultipleIEnumerableAsync(this ICaeriusNetDbContext, StoredProcedureParameters, CancellationToken); -Task<(IEnumerable, IEnumerable, IEnumerable)> +ValueTask<(IEnumerable, IEnumerable, IEnumerable)> QueryMultipleIEnumerableAsync(/* ... */); -Task<(IEnumerable, IEnumerable, IEnumerable, IEnumerable)> +ValueTask<(IEnumerable, IEnumerable, IEnumerable, IEnumerable)> QueryMultipleIEnumerableAsync(/* ... */); -Task<(IEnumerable, IEnumerable, IEnumerable, IEnumerable, IEnumerable)> +ValueTask<(IEnumerable, IEnumerable, IEnumerable, IEnumerable, IEnumerable)> QueryMultipleIEnumerableAsync(/* ... */); ``` -The same arities are available for `QueryMultipleReadOnlyCollectionAsync` and `QueryMultipleImmutableArrayAsync`. +The same overload range is available for `QueryMultipleReadOnlyCollectionAsync` and `QueryMultipleImmutableArrayAsync`. Example: @@ -393,7 +393,7 @@ try } catch (CaeriusNetSqlException ex) when (ex.InnerException is SqlException sqlEx) { - logger.LogError(ex, "SP {Procedure} failed (error {Number})", ex.ProcedureName, sqlEx.Number); + logger.LogError(ex, "Stored procedure {Procedure} failed (error {Number})", ex.ProcedureName, sqlEx.Number); } ``` diff --git a/Documentations/docs/documentation/aspire.md b/Documentations/docs/documentation/aspire.md index bccefbe..6091614 100644 --- a/Documentations/docs/documentation/aspire.md +++ b/Documentations/docs/documentation/aspire.md @@ -1,4 +1,4 @@ -# Aspire Integration +# Aspire integration CaeriusNet provides first-class integration with [.NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview) through `WithAspireSqlServer` and `WithAspireRedis`. Aspire manages SQL Server and Redis as named resources in the AppHost; CaeriusNet resolves their connection strings automatically. @@ -67,7 +67,7 @@ app.Run(); ``` ::: -## Console / Worker Service pattern +## Console and worker service pattern For console apps or background workers running under Aspire: @@ -189,7 +189,7 @@ CaeriusNetBuilder.Create(builder) `CaptureParameterValues = true` is convenient in staging or development to correlate a trace with the exact parameters that produced it. In production, parameter values can contain sensitive data (user IDs, emails, amounts â€Ļ) and should generally **not** be emitted to a shared telemetry backend. ::: -## Tracing & telemetry {#tracing-telemetry} +## Tracing and telemetry {#tracing-telemetry} CaeriusNet emits OpenTelemetry-compatible signals through the BCL primitives — no OpenTelemetry SDK package is added to the library itself. Consumers (typically the Aspire `ServiceDefaults` project) opt in by registering the source and meter: @@ -206,21 +206,21 @@ When no listener is subscribed, no allocation is performed. ### Spans -Every Stored Procedure call creates an `Activity` of `ActivityKind.Client` named `SP {schema}.{procedure}`. Failures set `ActivityStatusCode.Error` and attach the SQL exception via `Activity.AddException`. +Every stored procedure call creates an `Activity` of `ActivityKind.Client` named `SP {schema}.{procedure}`. Failures set `ActivityStatusCode.Error` and attach the SQL exception via `Activity.AddException`. | Tag | Description | |---|---| | `db.system` | Always `mssql` (OpenTelemetry semantic convention) | | `db.operation` | The calling command (`FirstQueryAsync`, `QueryMultipleImmutableArrayAsync`, `ExecuteNonQueryAsync`, â€Ļ) | | `db.statement` | `{schema}.{procedure}` | -| `caerius.sp.schema` | Schema of the Stored Procedure | -| `caerius.sp.name` | Name of the Stored Procedure | +| `caerius.sp.schema` | Schema of the stored procedure | +| `caerius.sp.name` | Name of the stored procedure | | `caerius.sp.parameters` | Comma-separated parameter names (e.g. `@id,@tvp`); shows `@name=value` when `CaptureParameterValues = true`. TVP values always render as `[TVP]`. | | `caerius.sp.command` | Same as `db.operation` (kept for filter convenience) | | `caerius.tvp.used` | `true` when at least one TVP is attached | | `caerius.tvp.type_name` | TVP type name (e.g. `dbo.tvp_int`); comma-separated when several TVPs are used | | `caerius.resultset.multi` | `true` when more than one result set is requested | -| `caerius.resultset.expected_count` | Number of result sets requested (1 by default; 2/3/4/5 for the multi-RS overloads) | +| `caerius.resultset.expected_count` | Number of result sets requested (1 by default; 2/3/4/5 for multiple-result overloads) | | `caerius.cache.tier` / `caerius.cache.hit` | Set on the active span when a cache lookup happens during the call | | `caerius.tx` | `true` when the call runs inside an `ICaeriusNetTransaction` | | `caerius.rows_returned` / `caerius.rows_affected` | Set on success | @@ -231,7 +231,7 @@ Four instruments are exposed by the `CaeriusNet` meter, all tagged with the same | Instrument | Type | Unit | Purpose | |---|---|---|---| -| `caerius.sp.duration` | Histogram | ms | Stored Procedure execution duration | +| `caerius.sp.duration` | Histogram | ms | Stored procedure execution duration | | `caerius.sp.executions` | Counter | calls | Number of executions started (success or failure) | | `caerius.sp.errors` | Counter | calls | Number of executions that failed with a SQL error | | `caerius.cache.lookups` | Counter | lookups | Cache lookups, tagged with `caerius.cache.tier` (`Frozen` / `InMemory` / `Redis`) and `caerius.cache.hit` (`true` / `false`) | @@ -240,7 +240,7 @@ When a cache hit short-circuits the SQL call, **no DB span is created** and only ## Transaction tracing {#transaction-tracing} -Every `ICaeriusNetTransaction` scope emits a parent **`TX` span** (kind = Internal) that wraps all child SP spans. This produces a single cohesive trace in the Aspire dashboard instead of one orphaned span per command: +Every `ICaeriusNetTransaction` scope emits a parent **`TX` span** (kind = Internal) that wraps all child stored procedure spans. This produces a single cohesive trace in the Aspire dashboard: ```text TX (kind=Internal, caerius.tx.isolation_level=ReadCommitted, caerius.tx.outcome=committed) @@ -254,9 +254,9 @@ TX (kind=Internal, caerius.tx.isolation_level=ReadCommitted, caerius.tx.outcome | `caerius.tx.outcome` | `committed`, `rolled-back`, `auto-rollback`, `poisoned-auto-rollback`, `commit-failed`, `rollback-failed` | ::: tip SQL-side rollback in the dashboard -A Stored Procedure that wraps its own `BEGIN TRY / BEGIN CATCH` rolls back internally and re-throws, which surfaces as a `CaeriusNetSqlException`. The corresponding SP span is tagged `ActivityStatusCode.Error` — this is **expected** and intentional, not a CaeriusNet bug. +A stored procedure that wraps its own `BEGIN TRY / BEGIN CATCH` rolls back internally and re-throws, which surfaces as a `CaeriusNetSqlException`. The corresponding stored procedure span is tagged `ActivityStatusCode.Error`. This is expected behavior, not a CaeriusNet bug. ::: --- -**Next:** [API Reference](/documentation/api) — full surface of all public types and methods. +**Next:** [API reference](/documentation/api) - full surface of all public types and methods. diff --git a/Documentations/docs/documentation/auto-contracts.md b/Documentations/docs/documentation/auto-contracts.md index d2ccdc6..ed28407 100644 --- a/Documentations/docs/documentation/auto-contracts.md +++ b/Documentations/docs/documentation/auto-contracts.md @@ -1,57 +1,56 @@ # AutoContracts -AutoContracts keeps stored procedure call contracts aligned with SQL Server metadata. It is designed as a read-only safety layer: CaeriusNet reads metadata from SQL Server, but does not create, update, or delete database objects and does not change application data. +AutoContracts helps keep CaeriusNet call sites aligned with SQL Server stored procedure metadata. It reads stored procedure contracts from SQL Server, stores them in a local manifest, and can verify that the database still matches the contract your application expects. -## What it tracks +Use AutoContracts when stored procedures are shared across teams, deployed separately from application code, or validated in CI. -AutoContracts records the contract shape used by typed stored procedure calls: +## What AutoContracts checks -- procedure identity -- parameter names, SQL types, direction, and nullability -- result-set columns when SQL Server can expose them through metadata -- the typed call surface represented by `StoredProcedureParametersBuilder` +AutoContracts tracks the public contract shape used by typed stored procedure calls: -Generated contract artifacts are marked with `AutoContractGenerateDto`, `AutoContractGenerateTvp`, and -`AutoContractGenerateProcedure`. Use these exact AutoContracts marker names in public -documentation, samples, and generated API names. +- Stored procedure schema and name +- Parameter names, SQL types, direction, and nullability +- Result-set columns when SQL Server can expose them through metadata +- Typed call contracts used by `StoredProcedureParametersBuilder` -## Manifest location +AutoContracts does not inspect data rows and does not execute business logic. It only reads SQL Server metadata. -The snapshot is stored in `caerius.contracts.json`. +## Manifest file -When AutoContracts runs through MSBuild, the default path is -`$(ProjectDir)caerius.contracts.json`. Override `CaeriusContractsOutput` when a project needs a -different path. The build targets pass that same path to the CLI and include it as an -`AdditionalFiles` item for the source generator. +The contract snapshot is stored in `caerius.contracts.json`. -When the CLI is run directly, `pull` writes to `--output` when provided, then `--manifest` when -provided, and otherwise `caerius.contracts.json` in the current working directory. `verify` reads -from `--manifest` when provided, then `--output` when provided, and otherwise -`caerius.contracts.json` in the current working directory. +When AutoContracts runs through MSBuild, the default path is: -## Build responsibilities +```xml +$(ProjectDir)caerius.contracts.json +``` -AutoContracts has two separate execution phases: +Override `CaeriusContractsOutput` when a project needs a different manifest path: + +```xml + + Contracts\caerius.contracts.json + +``` -- The CLI/MSBuild phase connects to SQL Server in read-only mode, reads metadata, and writes or - verifies `caerius.contracts.json`. -- The Roslyn source generator does not connect to SQL Server. It only reads the - `caerius.contracts.json` file supplied through `AdditionalFiles`. +When you run the CLI directly: + +- `pull` writes to `--output` when provided, then `--manifest` when provided, and otherwise `caerius.contracts.json` in the current directory. +- `verify` reads from `--manifest` when provided, then `--output` when provided, and otherwise `caerius.contracts.json` in the current directory. ## Modes -`CaeriusContractsMode` controls how the snapshot is used: +Set `CaeriusContractsMode` to control how AutoContracts behaves. | Mode | Behavior | |---|---| | `Pull` | Reads SQL Server metadata and refreshes `caerius.contracts.json`. Use this after an intentional stored procedure contract change. | | `Verify` | Reads SQL Server metadata and compares it with `caerius.contracts.json`. Use this in validation workflows to detect drift. | -| `Off` | Skips AutoContracts work. Use this when contract checks are not needed for a run. | +| `Off` | Skips AutoContracts. Use this when contract checks are not needed for a run. | -## Connection string resolution +## Configure the connection string -Prefer resolving the discovery connection string by configuration name, so AutoContracts follows -the same development workflow as the application: +Prefer resolving the discovery connection string by name so AutoContracts follows the same configuration model as your application. ```xml @@ -60,23 +59,21 @@ the same development workflow as the application: ``` -The tool reads `ConnectionStrings:DefaultConnection` from .NET configuration. It loads -`appsettings.json`, optional `appsettings.{environment}.json`, optional user secrets, and -environment variables from the project directory. `CaeriusContractsConfigurationEnvironment` can -force the environment-specific file, and `CaeriusContractsUserSecretsId` can override the project -`UserSecretsId`. +AutoContracts reads `ConnectionStrings:DefaultConnection` from .NET configuration. It supports `appsettings.json`, optional `appsettings.{environment}.json`, user secrets, and environment variables from the project directory. + +Use these properties when you need to control configuration resolution: + +| Property | Purpose | +|---|---| +| `CaeriusContractsConfigurationEnvironment` | Selects the environment-specific configuration file. | +| `CaeriusContractsUserSecretsId` | Overrides the project `UserSecretsId`. | +| `CaeriusContractsConnectionName` | Selects the named connection string. | -This also fits Aspire-driven projects: when the tool runs in an Aspire-provided environment, -`ConnectionStrings__DefaultConnection` is resolved by the same configuration path. For a local -manual database, store the value in `appsettings.Development.json` or user secrets. Do not commit -production secrets. +For Aspire-driven projects, the same configuration path works with `ConnectionStrings__DefaultConnection`. For local manual databases, store the value in `appsettings.Development.json` or user secrets. Do not commit production secrets. -The target database is the database named by the connection string, for example `Database=...` or -`Initial Catalog=...`. AutoContracts scans all application schemas in that database, including -`dbo`, and ignores SQL Server system schemas such as `sys`, `INFORMATION_SCHEMA`, and fixed-role -compatibility schemas. +## CLI examples -The lower-level escape hatches remain available: +Refresh a manifest from a named connection: ```powershell dotnet run --project Tools/CaeriusNet.SqlServer.Contracts -- ` @@ -86,6 +83,8 @@ dotnet run --project Tools/CaeriusNet.SqlServer.Contracts -- ` --output .\src\MyApp\caerius.contracts.json ``` +Verify an existing manifest with a connection string from the environment: + ```powershell dotnet run --project Tools/CaeriusNet.SqlServer.Contracts -- ` verify ` @@ -99,10 +98,14 @@ dotnet run --project Tools/CaeriusNet.SqlServer.Contracts -- ` 2. Run AutoContracts with `Pull`. 3. Review the updated `caerius.contracts.json`. 4. Commit the application change and the refreshed contract snapshot together. -5. Run AutoContracts with `Verify` in validation to catch unexpected SQL Server drift. +5. Run AutoContracts with `Verify` in CI to catch unexpected SQL Server drift. ## Read-only guarantees -AutoContracts uses SQL Server metadata reads only. `Pull` updates `caerius.contracts.json` in the application workspace; it does not apply SQL changes back to the database. `Verify` reports mismatches and leaves both SQL Server and the snapshot unchanged. +AutoContracts uses SQL Server metadata reads only. + +- `Pull` updates `caerius.contracts.json` in the application workspace. It does not apply SQL changes back to the database. +- `Verify` reports mismatches and leaves both SQL Server and the snapshot unchanged. +- `Off` disables the check for local scenarios where SQL Server is unavailable. -Keep `Off` available for local scenarios where SQL Server is unavailable, but prefer `Verify` wherever a real database is part of the validation path. +Prefer `Verify` wherever a real database is part of validation. diff --git a/Documentations/docs/documentation/best-practices.md b/Documentations/docs/documentation/best-practices.md index c4abee0..b8d5c2f 100644 --- a/Documentations/docs/documentation/best-practices.md +++ b/Documentations/docs/documentation/best-practices.md @@ -1,15 +1,15 @@ -# Best Practices +# Best practices -This page distills practical recommendations for building reliable, secure, and high-performance applications with CaeriusNet. It complements the [Quickstart](/quickstart/getting-started), [Reading Data](/documentation/reading-data), [Writing Data](/documentation/writing-data), and [Advanced Usage](/documentation/advanced-usage) guides. +This page distills practical recommendations for building reliable, secure, and high-performance applications with CaeriusNet. It complements the [Quickstart](/quickstart/getting-started), [Reading data](/documentation/reading-data), [Writing data](/documentation/writing-data), and [Advanced usage](/documentation/advanced-usage) guides. > Applies to: **C# 14 / .NET 10**, **SQL Server 2019 +**, `Microsoft.Data.SqlClient`. -## Architecture & patterns +## Architecture and patterns - **Use the Repository pattern.** Keep data access isolated behind interfaces — `ICaeriusNetDbContext` is the only external dependency repositories should know about. - **Inject `ICaeriusNetDbContext` via DI.** Never instantiate it manually; the lifetime and connection management is handled by the framework. -- **Favour sealed records.** They are immutable, lightweight, value-equatable, and pair well with the source generators. -- **Always prefer source generators.** The analyzer enforces the contract and the generated code is identical to a hand-written mapper: +- **Favor sealed records.** They are immutable, lightweight, value-equatable, and pair well with the source generators. +- **Prefer source generators.** The analyzer validates the contract and the generated code matches the mapper you would write by hand: ```csharp [GenerateDto] public sealed partial record UserDto(int Id, string Name, byte? Age); @@ -21,7 +21,7 @@ This page distills practical recommendations for building reliable, secure, and ## DTO mapping -- Mapping is **ordinal-based** — the constructor parameter order **must** match the SP `SELECT` column order. Aliases are cosmetic. +- Mapping is **ordinal-based**. The constructor parameter order **must** match the stored procedure `SELECT` column order. Aliases are cosmetic. - Mark columns that may return `NULL` as nullable in the DTO; the source generator emits `IsDBNull` guards automatically. - Keep DTOs **purpose-specific**. Avoid catch-all DTOs that grow with every screen. - Manual mapping example for reference: @@ -36,12 +36,12 @@ This page distills practical recommendations for building reliable, secure, and } ``` -## Stored Procedure conventions (T-SQL) +## Stored procedure conventions - **Use dedicated schemas** (`Users`, `Orders`, `Sales`, â€Ļ) instead of the default `dbo` for application code where feasible. - Always start procedures with `SET NOCOUNT ON;` to avoid spurious result sets. - **Never `SELECT *`.** Explicitly list columns in the order your DTO expects. -- Keep result-set shape stable. Any change in cardinality or order requires a matching DTO change. +- Keep the result-set shape stable. Any change in column count, order, or type requires a matching DTO change. - Prefer parameterized procedures for all inputs — avoid dynamic SQL unless absolutely required. - Wrap multi-statement writes in `BEGIN TRY / BEGIN CATCH` with explicit `COMMIT` / `ROLLBACK` and `THROW;` for re-raise. - Adopt a consistent name convention, e.g. `Schema.sp_Action_Subject_By_Filter`. @@ -82,16 +82,9 @@ See [Caching](/documentation/cache) for the full guide. - **Return only required columns.** Less data = fewer allocations and faster TDS framing. - **Pick the right collection.** `ImmutableArray` for frozen / shared data; `ReadOnlyCollection` for public APIs; `IEnumerable` for LINQ pipelines. - **Combine related result sets** with the `QueryMultiple*Async` families rather than chaining separate stored-procedure calls. -- **Benchmark critical flows.** CaeriusNet's own [BenchmarkDotNet suites](/benchmarks/) are reproducible (`Random(42)`); use them as a baseline. - -Internal performance levers worth knowing about: - -| Mechanism | What it buys you | -|---|---| -| `SearchValues` SIMD scans | Near-zero-cost parameter-name validation on modern CPUs | -| `FrozenDictionary` for Frozen cache | Lock-free concurrent reads | -| `GC.AllocateUninitializedArray` | Skips zero-fill on large result arrays that will be fully populated | -| `CollectionsMarshal.SetCount` + `AsSpan` | Populates `List` in place without bounds checks per write | +- **Benchmark critical flows.** CaeriusNet's own [BenchmarkDotNet suites](/benchmarks/) are reproducible (`Random(42)`). Use them as a baseline before tuning application code. +- **Keep hot paths direct.** Avoid adding extra mapping layers around CaeriusNet calls unless they remove real duplication. +- **Use `ValueTask` naturally.** Await CaeriusNet calls directly. Convert to `Task` only when another API explicitly requires it. ## Transactions @@ -115,7 +108,7 @@ await tx.CommitAsync(ct); ## Logging - Configure `ILoggerFactory` **before** `CaeriusNetBuilder.Build()` — DI then wires the logger automatically. -- Filter by event-ID category to control verbosity per subsystem: +- Filter by event ID category to control verbosity per subsystem: - `CaeriusNet.Cache` → `Warning` in production - `CaeriusNet.Commands` → `Information` to keep timing visible - Use **structured sinks** (Seq, Elasticsearch, OTLP) to leverage CaeriusNet's named placeholders (`{ProcedureName}`, `{Duration}`, `{RowCount}`). @@ -129,7 +122,7 @@ builder.Services.AddLogging(logging => }); ``` -See [Logging & Observability](/documentation/logging) for the complete event-ID reference. +See [Logging and observability](/documentation/logging) for the complete event ID reference. ## Async, cancellation, reliability @@ -142,8 +135,8 @@ See [Logging & Observability](/documentation/logging) for the complete event-ID | Issue | Likely cause | Fix | |---|---|---| -| `InvalidCastException` at runtime | Reader method or DTO type doesn't match SQL column type | Align the `Get*` call (or DTO field type) with the actual SQL type | -| `IndexOutOfRangeException` | DTO has more parameters than the SP returns columns | Re-check `SELECT` arity and DTO parameter count | +| `InvalidCastException` at runtime | Reader method or DTO type doesn't match SQL column type | Align the reader call or DTO field type with the actual SQL type | +| `IndexOutOfRangeException` | DTO has more parameters than the stored procedure returns columns | Recheck the `SELECT` column count and DTO parameter count | | Aspire connection failure | `WithAspireSqlServer("name")` does not match AppHost name | Cross-check the resource name in the AppHost | | TVP type mismatch | Schema/TvpName diverge between SQL and .NET, or column order is wrong | Re-align `[GenerateTvp]` arguments and constructor params | | Cache miss when hit was expected | Different keys per call | Build keys deterministically from inputs | @@ -151,14 +144,14 @@ See [Logging & Observability](/documentation/logging) for the complete event-ID ## Security -- Use Stored Procedures with parameters — no string concatenation, no dynamic SQL. -- **Never cache sensitive data** (passwords, tokens, raw PII) without explicit threat-modelling. +- Use stored procedures with parameters — no string concatenation, no dynamic SQL. +- **Never cache sensitive data** (passwords, tokens, raw PII) without explicit threat modeling. - Secure Redis with TLS and authentication; restrict network access. -- Grant the application user the **minimum required SQL permissions** (`EXECUTE` on the SP schema, no `dbo`). +- Grant the application user the **minimum required SQL permissions** (`EXECUTE` on the stored procedure schema, no `dbo`). -## Versioning & migrations +## Versioning and migrations -- Treat Stored Procedures and DTOs as a contract — version them when breaking changes are needed (e.g. `sp_GetUsers` → `sp_GetUsers_v2`). +- Treat stored procedures and DTOs as a contract. Version them when breaking changes are needed, for example `sp_GetUsers` to `sp_GetUsers_v2`. - Add new procedures alongside existing ones; deprecate old ones once consumers migrate. ## Quick reference @@ -194,4 +187,4 @@ var users = await dbContext.QueryAsIEnumerableAsync(sp, ct); --- -Use this page as a checklist when designing new queries or auditing existing ones. For deeper APIs and patterns, see [API Reference](/documentation/api), [Advanced Usage](/documentation/advanced-usage), and the [Examples](/examples/). +Use this page as a checklist when designing new queries or auditing existing ones. For deeper APIs and patterns, see [API reference](/documentation/api), [Advanced usage](/documentation/advanced-usage), and the [Examples](/examples/). diff --git a/Documentations/docs/documentation/cache.md b/Documentations/docs/documentation/cache.md index b997d0a..90864fd 100644 --- a/Documentations/docs/documentation/cache.md +++ b/Documentations/docs/documentation/cache.md @@ -1,6 +1,6 @@ # Caching -CaeriusNet supports three caching strategies to reduce database load and improve latency. Caching is **opt-in per call** via `StoredProcedureParametersBuilder` — you choose the tier exactly where you need it. On a cache hit, CaeriusNet returns the cached result without executing the Stored Procedure and **without creating a DB span**. +CaeriusNet supports three caching strategies to reduce database load and improve latency. Caching is **opt-in per call** via `StoredProcedureParametersBuilder`; choose the tier where you need it. On a cache hit, CaeriusNet returns the cached result without executing the stored procedure and without creating a database span. ## Strategy comparison diff --git a/Documentations/docs/documentation/diagnostics.md b/Documentations/docs/documentation/diagnostics.md index bcc4100..8f7340d 100644 --- a/Documentations/docs/documentation/diagnostics.md +++ b/Documentations/docs/documentation/diagnostics.md @@ -1,4 +1,4 @@ -# Compiler Diagnostics +# Compiler diagnostics CaeriusNet's Roslyn analyzer emits **compile-time diagnostics** for `[GenerateDto]`, `[GenerateTvp]`, and AutoContracts manifest issues. The analyzer ships inside the `CaeriusNet` NuGet package; no extra reference is required. @@ -103,4 +103,4 @@ from reaching production. --- -**See also:** [Source Generators](/documentation/source-generators) — the full guide to `[GenerateDto]` and `[GenerateTvp]`. +**See also:** [source generators](/documentation/source-generators) - the full guide to `[GenerateDto]` and `[GenerateTvp]`. diff --git a/Documentations/docs/documentation/dto-mapping.md b/Documentations/docs/documentation/dto-mapping.md index 3cc7b64..cc41d27 100644 --- a/Documentations/docs/documentation/dto-mapping.md +++ b/Documentations/docs/documentation/dto-mapping.md @@ -1,4 +1,4 @@ -# DTO Mapping +# DTO mapping CaeriusNet maps SQL Server result sets to C# DTOs at **compile time** — no reflection, no expression-tree compilation, no runtime metadata lookups. This page explains how the contract works, what it requires from your DTOs, and the few special cases worth knowing. @@ -10,9 +10,9 @@ Every DTO implements `ISpMapper`, a static-interface contract with a single m public static abstract T MapFromDataReader(SqlDataReader reader); ``` -Reads are **ordinal-based**: each column is accessed by its zero-based index rather than by name. This eliminates per-row string lookups and matches the TDS wire protocol directly — the column order in your `SELECT` statement is the contract between SQL and C#. +Reads are **ordinal-based**: each column is accessed by its zero-based index rather than by name. This avoids per-row string lookups, and the column order in your `SELECT` statement becomes the contract between SQL and C#. -You can implement `ISpMapper` manually, or — preferably — let the source generator emit it for you with `[GenerateDto]`. Both produce the same machine code; the generator simply removes the boilerplate. +You can implement `ISpMapper` manually, or let the source generator emit it for you with `[GenerateDto]`. Use the generator for most DTOs; manual mapping is useful when you need custom conversion logic. ## A manual `ISpMapper` @@ -31,11 +31,11 @@ public sealed record UserDto(int Id, string Username, byte Age) } ``` -The constructor parameter order **must** match the `SELECT` column order in your Stored Procedure. +The constructor parameter order **must** match the `SELECT` column order in your stored procedure. ## Column order is the contract -The column at position `0` in the result set is read at ordinal `0`. Your Stored Procedure defines the contract: +The column at position `0` in the result set is read at ordinal `0`. Your stored procedure defines the contract: ```sql -- Columns: Id (0), Username (1), Age (2) @@ -54,7 +54,7 @@ public sealed record UserDto(int Id, string Username, byte Age) ``` ::: warning Column order matters -If the SP `SELECT` order changes, update the ordinal indices in `MapFromDataReader` accordingly. This is a deliberate design choice — ordinal reads are faster and enforce an explicit contract between SQL and C#. +If the stored procedure `SELECT` order changes, update the ordinal indices in `MapFromDataReader` accordingly. This is a deliberate design choice: ordinal reads are faster and enforce an explicit contract between SQL and C#. ::: ## Nullable columns @@ -129,17 +129,17 @@ using CaeriusNet.Attributes.Dto; public sealed partial record UserDto(int Id, string Username, byte Age); ``` -The DTO must be `sealed`, `partial`, and use a primary constructor — the [Roslyn analyzer](/documentation/diagnostics) reports a build error if any of these are missing. See the [Source Generators](/documentation/source-generators) page for the full list of features. +The DTO must be `sealed`, `partial`, and use a primary constructor. The [Roslyn analyzer](/documentation/diagnostics) reports a build error if any of these are missing. See the [source generators](/documentation/source-generators) page for the full list of features. ## Common pitfalls | Symptom | Likely cause | Fix | |---|---|---| | `InvalidCastException` at runtime | Reader method does not match the SQL column type | Align the `Get*` call (or DTO field type) with the actual SQL type | -| `IndexOutOfRangeException` | Ordinal index does not correspond to an actual column | Re-check the SP `SELECT` arity | +| `IndexOutOfRangeException` | Ordinal index does not correspond to an actual column | Recheck the stored procedure `SELECT` column count | | `NullReferenceException` on a column | Nullable column not guarded with `IsDBNull` | Make the field nullable or add the guard | -| Wrong values for several fields | SP column order does not match constructor parameter order | Re-align the SELECT with the constructor | +| Wrong values for several fields | Stored procedure column order does not match constructor parameter order | Realign the `SELECT` with the constructor | --- -**Next:** [Source Generators](/documentation/source-generators) — eliminate the `MapFromDataReader` boilerplate entirely. +**Next:** [source generators](/documentation/source-generators) - eliminate the `MapFromDataReader` boilerplate entirely. diff --git a/Documentations/docs/documentation/logging.md b/Documentations/docs/documentation/logging.md index 20ca2b4..5937f21 100644 --- a/Documentations/docs/documentation/logging.md +++ b/Documentations/docs/documentation/logging.md @@ -1,4 +1,4 @@ -# Logging & Observability +# Logging and observability CaeriusNet uses **source-generated** `[LoggerMessage]` logging for zero-allocation structured output. Every database operation emits timing, procedure name, and result metadata — enabling diagnostics, performance monitoring, and alerting without custom instrumentation. @@ -103,7 +103,7 @@ CaeriusNet log messages use semantic (structured) placeholders. Structured loggi | Placeholder | Type | Description | |---|---|---| -| `{ProcedureName}` | `string` | Fully qualified Stored Procedure name (`schema.name`) | +| `{ProcedureName}` | `string` | Fully qualified stored procedure name (`schema.name`) | | `{Duration}` | `TimeSpan` | Elapsed execution time | | `{RowCount}` | `int` | Number of rows returned or affected | | `{CacheKey}` | `string` | Cache key used for lookup or store | diff --git a/Documentations/docs/documentation/multi-results.md b/Documentations/docs/documentation/multi-results.md index 4d8a151..48ee6ca 100644 --- a/Documentations/docs/documentation/multi-results.md +++ b/Documentations/docs/documentation/multi-results.md @@ -1,18 +1,21 @@ -# Multiple Result Sets +# Multiple result sets -A SQL Server Stored Procedure can return more than one `SELECT` result. CaeriusNet exposes dedicated helpers that read up to **five typed result sets** from a single command execution — one round-trip, one connection, one telemetry span. +Use a multiple-result-set query when one stored procedure returns related data in separate `SELECT` statements. CaeriusNet reads those result sets in order and returns a typed tuple. -## When to use +Multiple result sets are useful for dashboards, detail pages, reports, and API responses that need related data from one database round trip. -Multiple result sets are the right tool whenever you need logically related data fetched together: +## When to use multiple result sets -- A **dashboard** query returning users + orders + summary totals -- A **paginated** response returning a data page + a total count -- A **report** combining header + line items in one call +Use multiple result sets when: -Compared to multiple separate SP calls, a single multi-result SP reduces round-trips, connection-pool pressure, and total latency — and it nests under a single span instead of fragmenting your trace. +- The data is requested together by the same use case. +- The stored procedure can return each shape with a clear `SELECT`. +- You want one database round trip instead of several calls. +- The result-set order is stable and versioned with the stored procedure contract. -## SQL Server setup +Do not use multiple result sets when each data set has a different cache lifetime or authorization rule. In those cases, separate calls can be clearer. + +## SQL Server example ```sql CREATE PROCEDURE dbo.sp_Get_Dashboard_Data @@ -20,120 +23,113 @@ AS BEGIN SET NOCOUNT ON; - -- Result set 1: recent users - SELECT Id, Username, Age - FROM dbo.Users - ORDER BY Id DESC; + SELECT Id, Name, Age + FROM dbo.Users + ORDER BY Id; - -- Result set 2: recent orders SELECT OrderId, UserId, Total - FROM dbo.Orders - ORDER BY OrderId DESC; + FROM dbo.Orders + ORDER BY OrderId; END GO ``` -## Available overloads +The first `SELECT` maps to the first generic type argument. The second `SELECT` maps to the second generic type argument. -CaeriusNet offers three collection-type families, each with overloads for **2 to 5** result sets. Pick the family that matches your storage, mutation, and allocation needs. The two-result-set overloads are the hand-written base signatures in the runtime source; arities 3 through 5 are generated into the package at build time from the same implementation template. +## Call the stored procedure -### `QueryMultipleIEnumerableAsync` +```csharp +var sp = new StoredProcedureParametersBuilder("dbo", "sp_Get_Dashboard_Data", 128) + .Build(); -Returns a tuple of `IEnumerable`: +var (users, orders) = await dbContext + .QueryMultipleReadOnlyCollectionAsync(sp, ct); +``` -```csharp -Task<(IEnumerable, IEnumerable)> - QueryMultipleIEnumerableAsync(/* ... */); +## Available method families -Task<(IEnumerable, IEnumerable, IEnumerable)> - QueryMultipleIEnumerableAsync(/* ... */); +Each method family supports two to five result sets. -// ... up to T5 -``` +| Method family | Returns | +|---|---| +| `QueryMultipleIEnumerableAsync(...)` through `QueryMultipleIEnumerableAsync(...)` | Tuple of `IEnumerable` values | +| `QueryMultipleReadOnlyCollectionAsync(...)` through `QueryMultipleReadOnlyCollectionAsync(...)` | Tuple of `ReadOnlyCollection` values | +| `QueryMultipleImmutableArrayAsync(...)` through `QueryMultipleImmutableArrayAsync(...)` | Tuple of `ImmutableArray` values | -### `QueryMultipleReadOnlyCollectionAsync` +## Choose a return shape -Returns a tuple of `ReadOnlyCollection`: +| Shape | Use when | +|---|---| +| `IEnumerable` | You want a simple materialized sequence for LINQ operations. | +| `ReadOnlyCollection` | You expose the result through an API and want an indexable read-only contract. | +| `ImmutableArray` | You want a compact immutable value that is efficient to pass around. | -```csharp -Task<(ReadOnlyCollection, ReadOnlyCollection)> - QueryMultipleReadOnlyCollectionAsync(/* ... */); +## Result-set order is the contract -// ... up to T5 +The type parameters must match the SQL `SELECT` order. + +```csharp +var (users, orders, stats) = await dbContext + .QueryMultipleImmutableArrayAsync(sp, ct); ``` -### `QueryMultipleImmutableArrayAsync` +In this example: -Returns a tuple of `ImmutableArray`: +1. The first result set maps to `UserDto`. +2. The second result set maps to `OrderDto`. +3. The third result set maps to `UserStatsDto`. -```csharp -Task<(ImmutableArray, ImmutableArray)> - QueryMultipleImmutableArrayAsync(/* ... */); +CaeriusNet does not match result sets by name. It consumes the `SqlDataReader` sequentially. -// ... up to T5 -``` +::: warning Keep the SQL contract stable +If you reorder, add, or remove `SELECT` statements in the stored procedure, update the C# call site and DTOs at the same time. +::: -## Example — two result sets +## Missing trailing result sets + +If the stored procedure returns fewer result sets than the method expects, CaeriusNet returns empty collections for the missing trailing sets. ```csharp -public sealed record DashboardRepository(ICaeriusNetDbContext DbContext) - : IDashboardRepository +var (users, counts, optionalDetails) = await dbContext + .QueryMultipleImmutableArrayAsync(sp, ct); + +if (optionalDetails.IsEmpty) { - public async Task<(IEnumerable Users, IEnumerable Orders)> - GetDashboardAsync(CancellationToken ct) - { - var sp = new StoredProcedureParametersBuilder("dbo", "sp_Get_Dashboard_Data", 128) - .Build(); - - return await DbContext.QueryMultipleIEnumerableAsync(sp, ct); - } + // The procedure did not return the optional trailing detail set. } ``` -Destructure at the call site: +## Combine with TVPs -```csharp -var (users, orders) = await repository.GetDashboardAsync(ct); -``` - -## Example — three result sets +You can combine TVP inputs with multiple result-set outputs. ```csharp -var sp = new StoredProcedureParametersBuilder("dbo", "sp_Get_Report_Data", 500) - .AddParameter("Month", month, SqlDbType.TinyInt) +var ids = userIds.Select(id => new UserIdTvp(id)); + +var sp = new StoredProcedureParametersBuilder("dbo", "sp_Get_Users_And_Orders_By_Ids", 256) + .AddTvpParameter("Ids", ids) .Build(); -var (users, orders, products) = await DbContext - .QueryMultipleIEnumerableAsync(sp, ct); +var (users, orders) = await dbContext + .QueryMultipleReadOnlyCollectionAsync(sp, ct); ``` -## Result-set order is the contract - -The type parameters `T1`, `T2`, â€Ļ must match the **order** in which the SP returns its `SELECT` statements. The first type maps to the first result set, the second to the second, and so on. There is no runtime name matching — sets are consumed sequentially from the `SqlDataReader`. - -::: warning Order is strict -If the SP changes its `SELECT` order, update the type parameters accordingly. Misaligning them produces an `InvalidCastException` (best case) or silently wrong values (worst case) at runtime. -::: - -## DTO requirements +## Caching behavior -Every type parameter must implement `ISpMapper` (manual or generated with `[GenerateDto]`). The same ordinal-mapping rules described in [DTO Mapping](/documentation/dto-mapping) apply per result set. +`QueryMultiple*Async` methods do not apply `StoredProcedureParametersBuilder` cache policies. If you need to cache a multi-result response, cache the complete repository-level tuple in your application layer. ## Telemetry -Multi-result-set calls produce a single span tagged with: +Multiple result-set calls emit a single stored procedure span. The span includes the expected result-set count. | Tag | Value | |---|---| | `caerius.resultset.multi` | `true` | -| `caerius.resultset.expected_count` | The number of result sets requested (2, 3, 4, or 5) | - -The single span keeps the trace cohesive — there is no fan-out into one span per `SELECT`. - -## Combining with TVPs and caching - -Multi-result-set calls accept the same builder features as single-result calls — TVPs, scalar parameters, and any of the cache tiers. See [Advanced Usage](/documentation/advanced-usage#multiple-result-sets-with-tvp) for combined examples. +| `caerius.resultset.expected_count` | `2`, `3`, `4`, or `5` | ---- +## Related content -**Next:** [Caching](/documentation/cache) — reduce database load with per-call Frozen, InMemory, or Redis caching. +- [Reading data](/documentation/reading-data) +- [Table-valued parameters](/documentation/tvp) +- [Multi-result set examples](/examples/multi-result-sets) +- [API reference](/documentation/api#multiple-result-sets) diff --git a/Documentations/docs/documentation/reading-data.md b/Documentations/docs/documentation/reading-data.md index c1bea02..302fc48 100644 --- a/Documentations/docs/documentation/reading-data.md +++ b/Documentations/docs/documentation/reading-data.md @@ -1,6 +1,6 @@ -# Reading Data +# Reading data -CaeriusNet exposes four read methods on `ICaeriusNetDbContext`, each returning a different collection type so you can match your performance, allocation, and API-shape requirements. All methods are `async`, accept a `CancellationToken`, and use `CommandBehavior.SequentialAccess` internally for efficient TDS streaming. +CaeriusNet exposes four read methods on `ICaeriusNetDbContext`. Each method returns a different shape so you can choose the contract that fits your application. ## Prerequisites @@ -12,7 +12,7 @@ CaeriusNet exposes four read methods on `ICaeriusNetDbContext`, each returning a | Method | Return type | When to use | |---|---|---| -| `QueryAsIEnumerableAsync` | `IEnumerable?` | Deferred enumeration, LINQ pipelines | +| `QueryAsIEnumerableAsync` | `IEnumerable` | Materialized sequence for LINQ pipelines | | `QueryAsReadOnlyCollectionAsync` | `ReadOnlyCollection` | Public APIs that expose an immutable contract | | `QueryAsImmutableArrayAsync` | `ImmutableArray` | Frozen, struct-backed, allocation-efficient data | | `FirstQueryAsync` | `T?` | Single-row lookups (returns `null` when empty) | @@ -41,7 +41,7 @@ public sealed record UserRepository(ICaeriusNetDbContext DbContext) ## `QueryAsIEnumerableAsync` -Returns `IEnumerable?` (`null` on empty result set). Best for LINQ-pipeline scenarios or when downstream code materializes the collection later. +Returns `IEnumerable`. Empty result sets return an empty sequence. Use this shape when callers want LINQ operations and do not need an indexable collection contract. ```csharp public async Task> GetUsersOlderThanAsync( @@ -57,7 +57,7 @@ public async Task> GetUsersOlderThanAsync( ## `QueryAsReadOnlyCollectionAsync` -Returns a `ReadOnlyCollection` — empty when the SP returns no rows. Ideal for public APIs where the caller should see an immutable contract. +Returns a `ReadOnlyCollection`, empty when the stored procedure returns no rows. Ideal for public APIs where the caller should see an immutable contract. ```csharp public async Task> GetAllUsersAsync(CancellationToken ct) @@ -101,7 +101,7 @@ public async Task GetUserByGuidAsync(Guid guid, CancellationToken ct) ## Result-set capacity -The third constructor argument of `StoredProcedureParametersBuilder` is `resultSetCapacity`. It pre-allocates the internal `List` to the expected row count, avoiding reallocations as rows are added: +The third constructor argument of `StoredProcedureParametersBuilder` is `resultSetCapacity`. Use it to tell CaeriusNet the expected row count for materialized result sets. ```csharp // Expecting ~250 rows — pre-allocate to skip List resizing @@ -138,4 +138,4 @@ On a cache hit, **no SQL command is executed and no DB span is created** — onl --- -**Next:** [Writing Data](/documentation/writing-data) — execute INSERT, UPDATE, DELETE, and scalar returns. +**Next:** [Writing data](/documentation/writing-data) - execute INSERT, UPDATE, DELETE, and scalar returns. diff --git a/Documentations/docs/documentation/source-generators.md b/Documentations/docs/documentation/source-generators.md index 0a70440..295dd3e 100644 --- a/Documentations/docs/documentation/source-generators.md +++ b/Documentations/docs/documentation/source-generators.md @@ -1,274 +1,156 @@ -# Source Generators +# Source generators -CaeriusNet ships Roslyn **incremental source generators** that eliminate mapping boilerplate at compile time. They run as part of your build, produce zero runtime overhead, and are fully AOT- and trim-compatible. +CaeriusNet includes source generators for DTO mappers and table-valued parameter mappers. They remove repetitive mapping code while keeping the mapping contract visible in your C# types. -## Overview +You do not install a separate analyzer or generator package. The generators are included with the `CaeriusNet` NuGet package. -| Generator | Input | Output | -|---|---|---| -| `DtoSourceGenerator` | `[GenerateDto]` | `ISpMapper` | -| `TvpSourceGenerator` | `[GenerateTvp]` | `ITvpMapper` | -| `AutoContractsSourceGenerator` | `caerius.contracts.json` supplied as `AdditionalFiles` | typed stored procedure descriptors and DTO/TVP contracts | -| `MultiResultSetSignaturesSourceGenerator` | none | package-internal overloads for multi-result-set arities 3 through 5 | - -The DTO and TVP generators target **sealed partial records or classes** with a primary constructor. The `partial` keyword lets the generator add the interface implementation as a second declaration alongside your type. Constraints are enforced at compile time by the [CaeriusNet analyzer](/documentation/diagnostics) (`CAERIUS001` through `CAERIUS006`). - -The AutoContracts generator is different: it does not connect to SQL Server and it does not inspect -the database during compilation. It reads only the `caerius.contracts.json` manifest provided as a -Roslyn `AdditionalFiles` item. The SQL Server metadata read happens earlier in the CLI/MSBuild -AutoContracts phase. +```bash +dotnet add package CaeriusNet +``` -The multi-result-set signature generator is an internal package build detail. It runs only while -compiling the `CaeriusNet` runtime assembly, keeps the hand-written runtime source limited to the -base two-result-set overloads, and emits the public `T1..T3` through `T1..T5` extension overloads into -the packaged assembly. Consumer projects do not receive duplicate generated methods. +## When to use source generators -## `[GenerateDto]` — DTO mapper +Use source generators when you want CaeriusNet to create mapper implementations from simple record or class definitions. -Annotate a sealed partial record (or class) with `[GenerateDto]`. The generator emits a `MapFromDataReader` method with ordinal-based column reads, correct nullability guards, and special-type conversions. +| Attribute | Use it for | Generated contract | +|---|---|---| +| `[GenerateDto]` | Stored procedure result rows | `ISpMapper` | +| `[GenerateTvp]` | SQL Server table-valued parameter rows | `ITvpMapper` | -### Requirements +The generator output is compile-time code. There is no runtime reflection or runtime mapper discovery. -- Type must be `sealed` -- Type must be `partial` -- Type must declare a **primary constructor** — its parameters become the mapped columns, in order. +## DTO mapper generation -### Basic example +Use `[GenerateDto]` on a sealed partial record or class with a primary constructor. Constructor parameters map to result-set columns by ordinal position. ```csharp using CaeriusNet.Attributes.Dto; [GenerateDto] -public sealed partial record UserDto(int Id, string Username, byte Age); -``` - -**Generated code** (simplified): - -```csharp -// -#pragma warning disable CS1591 -#nullable enable - -using System.CodeDom.Compiler; -using System.Runtime.CompilerServices; - -[GeneratedCode("CaeriusNet.Generator", "11.0.3")] -partial record UserDto : ISpMapper -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UserDto MapFromDataReader(SqlDataReader reader) - => new( - reader.GetInt32(0), - reader.GetString(1), - reader.GetByte(2)); -} -``` - -::: details Generated code attributes -- `[GeneratedCode]` marks the file as tool-generated for IDEs and analysis tooling -- `[MethodImpl(AggressiveInlining)]` on `MapFromDataReader` enables JIT inlining of the hot-path mapper -- `#pragma warning disable CS1591` suppresses XML doc warnings on generated code -- `#nullable enable` ensures nullable analysis applies regardless of project-level settings -::: - -### Nullable fields - -The generator respects C# nullable annotations and inserts `IsDBNull` guards automatically: - -```csharp -[GenerateDto] -public sealed partial record ProductDto(int Id, string? Description, int? Stock); +public sealed partial record UserDto(int Id, string Name, byte Age); ``` -Generated: +The stored procedure must return columns in the same order: -```csharp -public static ProductDto MapFromDataReader(SqlDataReader reader) - => new( - reader.GetInt32(0), - reader.IsDBNull(1) ? null : reader.GetString(1), - reader.IsDBNull(2) ? null : reader.GetInt32(2)); +```sql +SELECT Id, Name, Age +FROM dbo.Users +ORDER BY Id; ``` -### Special types +### Requirements -| Field type | Generated expression | -|---|---| -| `DateOnly` | `DateOnly.FromDateTime(reader.GetDateTime(n))` | -| `TimeOnly` | `TimeOnly.FromDateTime(reader.GetDateTime(n))` | -| `Half` | `(Half)reader.GetFloat(n)` | -| `byte[]` | `reader.GetFieldValue(n)` | +- The type must be `sealed`. +- The type must be `partial`. +- The type must have a primary constructor. +- Constructor parameter order must match the stored procedure result-set order. -::: tip `Half` support -`System.Half` maps to SQL `real`. The generator reads via `GetFloat` and casts to `Half`. Use it for low-precision floating-point values where memory matters. -::: +If a requirement is not met, the analyzer reports a build diagnostic. See [Diagnostic rules](/diagnostics/). -### Ordinal constants for wide DTOs +## Nullable columns -For DTOs with **more than 8 properties**, the generator emits named ordinal constants for readability: +Use nullable C# types for columns that can return `NULL`. ```csharp [GenerateDto] -public sealed partial record LargeDto( - int Id, string Name, string Email, byte Age, - DateTime CreatedAt, DateTime? UpdatedAt, bool IsActive, - decimal Balance, string Country, string? Phone); -``` - -Generated (simplified): - -```csharp -partial record LargeDto : ISpMapper -{ - private const int OrdId = 0; - private const int OrdName = 1; - private const int OrdEmail = 2; - private const int OrdAge = 3; - private const int OrdCreatedAt = 4; - private const int OrdUpdatedAt = 5; - private const int OrdIsActive = 6; - private const int OrdBalance = 7; - private const int OrdCountry = 8; - private const int OrdPhone = 9; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static LargeDto MapFromDataReader(SqlDataReader reader) - => new( - reader.GetInt32(OrdId), - reader.GetString(OrdName), - reader.GetString(OrdEmail), - reader.GetByte(OrdAge), - reader.GetDateTime(OrdCreatedAt), - reader.IsDBNull(OrdUpdatedAt) ? null : reader.GetDateTime(OrdUpdatedAt), - reader.GetBoolean(OrdIsActive), - reader.GetDecimal(OrdBalance), - reader.GetString(OrdCountry), - reader.IsDBNull(OrdPhone) ? null : reader.GetString(OrdPhone)); -} +public sealed partial record ProductDto( + int Id, + string Name, + string? Description, + int? Stock); ``` -::: tip Why a threshold? -Compact DTOs read well with raw indices; wide ones do not. The 8-property threshold keeps simple cases terse and improves readability for wider result sets. -::: - -## `[GenerateTvp]` — TVP mapper - -Annotate a sealed partial record (or class) with `[GenerateTvp]` and provide the SQL schema and type name. The generator emits: - -- A static `TvpTypeName` property (e.g., `"dbo.tvp_int"`) -- A static `_tvpMetaData` `SqlMetaData[]` field — one entry per constructor parameter -- A `MapAsSqlDataRecords` iterator that **reuses a single `SqlDataRecord`** instance across all rows (zero-copy streaming) - -### Requirements +The generated mapper checks `IsDBNull` before reading nullable values. -- Type must be `sealed` -- Type must be `partial` -- Attribute requires **`Schema`** and **`TvpName`** named arguments -- `TvpName` cannot be empty (analyzer rule `CAERIUS004`) +## Supported common types -### Basic example +| C# type | Typical SQL Server type | +|---|---| +| `bool` | `bit` | +| `byte` | `tinyint` | +| `short` | `smallint` | +| `int` | `int` | +| `long` | `bigint` | +| `decimal` | `decimal` | +| `float` | `real` | +| `double` | `float` | +| `string` | `nvarchar` | +| `DateTime` | `datetime2` | +| `DateOnly` | `date` | +| `TimeOnly` | `time` | +| `DateTimeOffset` | `datetimeoffset` | +| `TimeSpan` | `time` | +| `Guid` | `uniqueidentifier` | +| `byte[]` | `varbinary` | +| Enums | The enum underlying type | + +For unsupported types, the analyzer reports a diagnostic so you can choose a safer DTO shape. + +## TVP mapper generation + +Use `[GenerateTvp]` on a sealed partial record or class. The constructor parameters define the TVP row shape. ```csharp using CaeriusNet.Attributes.Tvp; -[GenerateTvp(Schema = "dbo", TvpName = "tvp_int")] +[GenerateTvp(Schema = "dbo", TvpName = "tvp_UserId")] public sealed partial record UserIdTvp(int Id); ``` -**Generated code** (simplified): +The SQL type must match the C# constructor order and compatible SQL types: -```csharp -// -#pragma warning disable CS1591 -#nullable enable - -using System.CodeDom.Compiler; -using System.Runtime.CompilerServices; - -[GeneratedCode("CaeriusNet.Generator", "11.0.3")] -partial record UserIdTvp : ITvpMapper -{ - public static string TvpTypeName => "dbo.tvp_int"; - - private static readonly SqlMetaData[] _tvpMetaData = - [ - new SqlMetaData("Id", SqlDbType.Int) - ]; - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public IEnumerable MapAsSqlDataRecords(IEnumerable items) - { - var record = new SqlDataRecord(_tvpMetaData); - foreach (var item in items) - { - record.SetInt32(0, item.Id); - yield return record; - } - } -} +```sql +CREATE TYPE dbo.tvp_UserId AS TABLE +( + Id INT NOT NULL +); ``` -::: tip Why a single reused `SqlDataRecord`? -`Microsoft.Data.SqlClient` synchronously consumes all column values before advancing to the next row, so overwriting the same record between `yield return` calls is safe — and it skips one allocation per row, which adds up at TVP sizes of 10 000 +. -::: - -### Nullable TVP fields - -The generator emits `record.SetDBNull(n)` for nullable fields whose value is null: +Use the generated TVP with `AddTvpParameter`. ```csharp -[GenerateTvp(Schema = "dbo", TvpName = "tvp_optional")] -public sealed partial record OptionalTvp(int Id, int? OptionalValue); -``` +var ids = userIds.Select(id => new UserIdTvp(id)); -### Custom schema - -Use a schema other than `dbo` to match your SQL Server type: - -```csharp -[GenerateTvp(Schema = "Types", TvpName = "tvp_UserId")] -public sealed partial record UserIdTvp(int Id); -// TvpTypeName => "Types.tvp_UserId" +var sp = new StoredProcedureParametersBuilder("dbo", "sp_GetUsers_By_Ids", 256) + .AddTvpParameter("Ids", ids) + .Build(); ``` -## Manual vs. generated comparison - -| Aspect | Manual | Generated | -|---|---|---| -| `MapFromDataReader` | You write it | Compiler emits it | -| Ordinal indices | You manage them | Auto-assigned from constructor order | -| Nullable guards | You add `IsDBNull` checks | Emitted when the parameter is nullable | -| `SqlDataRecord` reuse (TVP) | You implement it | Always reused (single instance) | -| Compilation requirement | Compiles as-is | Requires `sealed partial` + primary ctor | -| Contract violation feedback | Discovered at runtime | Reported at build time by the analyzer | - -## Enabling the generators +### TVP requirements -The generators ship **inside the `CaeriusNet` NuGet package** as embedded Roslyn analyzers. No additional package, MSBuild target, or property is required. +- `Schema` must match the SQL Server type schema. +- `TvpName` must match the SQL Server type name. +- Constructor parameter order must match the SQL type column order. +- Nullable C# parameters should match nullable SQL columns. +- The input collection must contain at least one row. -```bash -dotnet add package CaeriusNet -``` +## Manual mapper comparison -Once added, `[GenerateDto]` and `[GenerateTvp]` are available in the `CaeriusNet.Attributes.Dto` and `CaeriusNet.Attributes.Tvp` namespaces. +You can still write mappers manually when you need full control. -::: tip Inspecting generated code -Set `true` in your `.csproj` to write generated files to `obj/Generated/` for inspection. Useful when debugging mapper behaviour or reviewing what the compiler produced. -::: +| Task | Generated mapper | Manual mapper | +|---|---|---| +| DTO row mapping | Add `[GenerateDto]` to a DTO | Implement `ISpMapper` | +| TVP row mapping | Add `[GenerateTvp]` to a TVP row type | Implement `ITvpMapper` | +| Contract validation | Analyzer diagnostics at build time | Your tests and runtime behavior | +| Boilerplate | Minimal | You write every reader or TVP call | -## Pragma directives in generated files +## Inspect generated files -All generated files include standard pragma directives: +If you need to troubleshoot generated output, enable compiler-generated files in your project. -```csharp -// -#pragma warning disable CS1591 // Missing XML comment on a public member -#nullable enable +```xml + + true + obj\Generated + ``` -- `CS1591` suppression prevents documentation warnings on generated code when `` is enabled. -- `#nullable enable` ensures nullable analysis applies regardless of project-level settings. +You usually do not need this setting. It is useful when you are diagnosing a mapper issue or reviewing what code is compiled. ---- +## Related content -**Next:** [Table-Valued Parameters](/documentation/tvp) — bulk inputs without `DataTable` overhead. +- [DTO mapping](/documentation/dto-mapping) +- [Table-valued parameters](/documentation/tvp) +- [Compiler diagnostics](/documentation/diagnostics) +- [Diagnostic rules](/diagnostics/) diff --git a/Documentations/docs/documentation/transactions.md b/Documentations/docs/documentation/transactions.md index ee54b9f..f046cc1 100644 --- a/Documentations/docs/documentation/transactions.md +++ b/Documentations/docs/documentation/transactions.md @@ -1,18 +1,17 @@ # Transactions -CaeriusNet provides a lightweight transaction scope that wraps `SqlTransaction` with a thread-safe state machine, automatic rollback on dispose, cache bypass, and a parent `TX` activity for cohesive tracing. Multiple commands enlisted on the scope succeed or fail **atomically** — there are no partial commits. +CaeriusNet provides a transaction scope that wraps `SqlTransaction` with automatic rollback on dispose, cache bypass, and a parent `TX` activity for cohesive tracing. Multiple commands enlisted on the scope succeed or fail **atomically**. There are no partial commits. ## At a glance -| Feature | Behaviour | +| Feature | Behavior | |---|---| | **State machine** | `Active` → `Committed` / `RolledBack` / `Poisoned` / `Disposed` | -| **Thread safety** | State transitions guarded by `Interlocked.CompareExchange` | -| **Single in-flight command** | Enforced — `SqlConnection` is not thread-safe | +| **Single in-flight command** | Enforced because a SQL connection can execute only one command at a time in the scope | | **Failure handling** | A failure poisons the scope; only `RollbackAsync` / `DisposeAsync` remain valid | | **Cache** | Bypassed inside transactions (no dirty reads published) | | **Auto-rollback** | An uncommitted scope rolls back on `DisposeAsync` | -| **Telemetry** | Parent `TX` span (kind = Internal) wraps every child SP span | +| **Telemetry** | Parent `TX` span (kind = Internal) wraps every child stored procedure span | | **Logging** | Structured events for start, commit, rollback, and poison | ## Basic usage @@ -77,7 +76,7 @@ await using var tx = await DbContext await tx.ExecuteNonQueryAsync(sp, ct); -// No CommitAsync — the transaction is rolled back when tx is disposed. +// No CommitAsync; the transaction is rolled back when tx is disposed. ``` ::: warning Always call `CommitAsync` on the success path @@ -169,21 +168,21 @@ catch (CaeriusNetSqlException) `SqlConnection` is not thread-safe. CaeriusNet enforces a single command at a time within a scope. Concurrent commands throw `InvalidOperationException`: ```csharp -// ❌ DO NOT — concurrent commands on the same scope +// Do not run concurrent commands on the same scope. var task1 = tx.ExecuteNonQueryAsync(sp1, ct); var task2 = tx.ExecuteNonQueryAsync(sp2, ct); // throws InvalidOperationException await Task.WhenAll(task1, task2); ``` ```csharp -// ✅ DO — sequential commands +// Run commands sequentially. await tx.ExecuteNonQueryAsync(sp1, ct); await tx.ExecuteNonQueryAsync(sp2, ct); ``` ### No nested transactions -SQL Server does not support nested transactions on a single connection. Calling `BeginTransactionAsync` on a scope throws `NotSupportedException`. Use SQL `SAVEPOINT` inside a Stored Procedure if you need partial-rollback semantics: +SQL Server does not support nested transactions on a single connection. Calling `BeginTransactionAsync` on a scope throws `NotSupportedException`. Use SQL `SAVEPOINT` inside a stored procedure if you need partial-rollback semantics: ```sql CREATE PROCEDURE dbo.sp_With_Savepoint @@ -212,7 +211,7 @@ Any unhandled exception from a command poisons the scope. Partial success in a t ## Tracing -Every scope emits a parent **`TX` span** (kind = Internal) under which all child SP spans nest. The trace remains a single cohesive workflow in the Aspire dashboard: +Every scope emits a parent **`TX` span** (kind = Internal) under which all child stored procedure spans nest. The trace remains a single cohesive workflow in the Aspire dashboard: ```text TX (caerius.tx.isolation_level=ReadCommitted, caerius.tx.outcome=committed) @@ -224,9 +223,9 @@ TX (caerius.tx.isolation_level=ReadCommitted, caerius.tx.outcome=committed) |---|---| | `caerius.tx.isolation_level` | The SQL Server isolation level (e.g. `ReadCommitted`) | | `caerius.tx.outcome` | `committed`, `rolled-back`, `auto-rollback`, `poisoned-auto-rollback`, `commit-failed`, `rollback-failed` | -| `caerius.tx` | `true` on every child SP span enlisted in the scope | +| `caerius.tx` | `true` on every child stored procedure span enlisted in the scope | -See [Aspire Integration — Transaction tracing](/documentation/aspire#transaction-tracing) for full details. +See [Aspire integration — Transaction tracing](/documentation/aspire#transaction-tracing) for full details. ## Best practices @@ -252,7 +251,7 @@ See [Aspire Integration — Transaction tracing](/documentation/aspire#transacti SQL errors raised inside a transaction are wrapped in `CaeriusNetSqlException`: - Original `SqlException` is preserved as `InnerException` -- The active SP span is tagged `ActivityStatusCode.Error` before the exception bubbles up +- The active stored procedure span is tagged `ActivityStatusCode.Error` before the exception bubbles up ```csharp await using var tx = await DbContext @@ -283,4 +282,4 @@ CaeriusNet emits structured log events for the transaction lifecycle: --- -**Next:** [Logging & Observability](/documentation/logging) — structured logging, event IDs, and OTel integration. +**Next:** [Logging and observability](/documentation/logging) - structured logging, event IDs, and OpenTelemetry integration. diff --git a/Documentations/docs/documentation/tvp.md b/Documentations/docs/documentation/tvp.md index f1a4f4d..b57be18 100644 --- a/Documentations/docs/documentation/tvp.md +++ b/Documentations/docs/documentation/tvp.md @@ -1,10 +1,10 @@ -# Table-Valued Parameters +# Table-valued parameters -Table-Valued Parameters (TVP) let you pass an entire **set of rows** — IDs, GUIDs, composite keys, or wider row shapes — as a single typed parameter to a SQL Server Stored Procedure. CaeriusNet implements TVP transport via streaming `IEnumerable`, which avoids `DataTable` allocations and feeds rows directly to the TDS protocol layer. +Table-valued parameters (TVPs) let you pass an entire **set of rows** as a single typed parameter to a SQL Server stored procedure. Use them for IDs, GUIDs, composite keys, or wider row shapes when one stored procedure call should operate on many rows. ## Why TVPs? -A TVP is a SQL Server **user-defined table type** that you pass as a `READONLY` parameter. Instead of sending 1 000 IDs one by one (or padding a dynamic SQL `IN`-list), you send a single structured table with 1 000 rows in one round-trip. +A TVP is a SQL Server **user-defined table type** that you pass as a `READONLY` parameter. Instead of sending 1,000 IDs one by one or building dynamic SQL, you send one structured table in a single round trip. ```sql -- 1. Create the SQL Server type @@ -14,7 +14,7 @@ CREATE TYPE dbo.tvp_int AS TABLE ); GO --- 2. Use it in a Stored Procedure +-- 2. Use it in a stored procedure CREATE PROCEDURE dbo.sp_GetUsers_By_Tvp_Ids @Ids dbo.tvp_int READONLY AS @@ -40,7 +40,7 @@ using CaeriusNet.Attributes.Tvp; public sealed partial record UserIdTvp(int Id); ``` -The generator emits `ITvpMapper` with a zero-allocation `SqlDataRecord` streaming implementation. See [Source Generators](/documentation/source-generators#generatetvp-tvp-mapper) for the generated shape. +The generator emits the `ITvpMapper` implementation. See [source generators](/documentation/source-generators#generatetvp-tvp-mapper) for the supported type rules. ### Manual implementation @@ -97,7 +97,7 @@ if (ids.Count == 0) return []; ## Combining a TVP with regular parameters -`AddTvpParameter` and `AddParameter` mix freely. Order does not matter for SQL Server, but parameter names must match the SP definition exactly: +`AddTvpParameter` and `AddParameter` mix freely. Order does not matter for SQL Server, but parameter names must match the stored procedure definition exactly: ```sql CREATE PROCEDURE dbo.sp_GetUsers_By_Tvp_Ids_And_Age @@ -138,19 +138,19 @@ CREATE TYPE dbo.tvp_user_key AS TABLE public sealed partial record UserKeyTvp(int Id, Guid Guid); ``` -## Performance characteristics +## Usage guidance -| Aspect | Detail | +| Recommendation | Reason | |---|---| -| Allocation | Single `SqlDataRecord` allocated per call, reused for every row | -| Protocol | TDS-native structured parameter — no XML, no JSON, no `DataTable` | -| Throughput | Scales to tens of thousands of rows efficiently | -| Boxing | Strongly-typed `Set*` calls (`SetInt32`, `SetGuid`, â€Ļ) — no `object[]` row materialization | -| Empty input | `ArgumentException` prevents an invalid call (SQL Server requires â‰Ĩ 1 row) | +| Validate input before calling | SQL Server rejects empty TVPs; return early when the input set is empty. | +| Keep TVP row types focused | Include only the columns the stored procedure actually needs. | +| Match SQL order exactly | `[GenerateTvp]` maps constructor parameters by position. | +| Prefer TVPs over dynamic `IN` lists | TVPs keep SQL parameterized and avoid query text growth. | +| Batch very large inputs | Large sets can increase command time and transaction duration. | ## Telemetry -When a TVP is attached, the corresponding SP span is tagged accordingly: +When a TVP is attached, the corresponding stored procedure span is tagged accordingly: | Tag | Value | |---|---| @@ -160,4 +160,4 @@ When a TVP is attached, the corresponding SP span is tagged accordingly: --- -**Next:** [Reading Data](/documentation/reading-data) — fetch result sets as `IEnumerable`, `ReadOnlyCollection`, or `ImmutableArray`. +**Next:** [Reading data](/documentation/reading-data) - fetch result sets as `IEnumerable`, `ReadOnlyCollection`, or `ImmutableArray`. diff --git a/Documentations/docs/documentation/usage.md b/Documentations/docs/documentation/usage.md index f3426d8..4624c7d 100644 --- a/Documentations/docs/documentation/usage.md +++ b/Documentations/docs/documentation/usage.md @@ -1,59 +1,97 @@ -# Usage +# Usage overview -This section covers all runtime usage of CaeriusNet. Content is organized by topic — use the navigation below or the sidebar to jump directly to what you need. +This section shows where to go for each CaeriusNet task. Start here when you know what you want to do, but you are not sure which API or guide applies. -## Topics +## Typical workflow -### Repository setup +Most CaeriusNet code follows the same pattern: -All data access goes through repositories that inject `ICaeriusNetDbContext`. See the [Installation & Setup](/quickstart/getting-started) guide for DI registration. +1. Define or generate DTO and TVP types. +2. Build stored procedure settings with `StoredProcedureParametersBuilder`. +3. Call a read, write, multi-result, cache, or transaction API. +4. Let CaeriusNet handle connection lifetime, command execution, telemetry, and exception wrapping. ```csharp -public sealed record UserRepository(ICaeriusNetDbContext DbContext) - : IUserRepository { } +[GenerateDto] +public sealed partial record UserDto(int Id, string Name, byte Age); + +var sp = new StoredProcedureParametersBuilder("Users", "usp_Get_By_Age", 128) + .AddParameter("Age", 18, SqlDbType.Int) + .Build(); + +var users = await dbContext.QueryAsReadOnlyCollectionAsync(sp, ct); ``` -### Reading data +## Choose a guide -| Guide | Description | +| Task | Start with | |---|---| -| [DTO Mapping](/documentation/dto-mapping) | Ordinal-based mapping, nullability, special types | -| [Source Generators](/documentation/source-generators) | `[GenerateDto]` and `[GenerateTvp]` | -| [AutoContracts](/documentation/auto-contracts) | SQL Server read-only contract snapshots with `caerius.contracts.json` | -| [Reading Data](/documentation/reading-data) | `QueryAsIEnumerableAsync`, `FirstQueryAsync`, capacity | -| [Multiple Result Sets](/documentation/multi-results) | Fetch 2–5 result sets in one round-trip | +| Install the package and register services | [Install and configure](/quickstart/getting-started) | +| Map stored procedure result rows to DTOs | [DTO mapping](/documentation/dto-mapping) | +| Generate DTO or TVP mapper code | [Source generators](/documentation/source-generators) | +| Read a result set | [Reading data](/documentation/reading-data) | +| Execute writes, deletes, updates, or scalar commands | [Writing data](/documentation/writing-data) | +| Send a list of values to SQL Server | [Table-valued parameters](/documentation/tvp) | +| Read related result sets in one round trip | [Multiple result sets](/documentation/multi-results) | +| Cache a read call | [Caching](/documentation/cache) | +| Run several commands atomically | [Transactions](/documentation/transactions) | +| Connect to Aspire resources | [Aspire integration](/documentation/aspire) | +| Capture traces, metrics, and logs | [Logging and observability](/documentation/logging) | +| Review production recommendations | [Best practices](/documentation/best-practices) | +| Look up method signatures | [API reference](/documentation/api) | + +## Runtime API groups + +### Read APIs + +Use read APIs when the stored procedure returns one or more rows. + +| Method | Return shape | +|---|---| +| `FirstQueryAsync` | `T?` | +| `QueryAsIEnumerableAsync` | `IEnumerable` | +| `QueryAsReadOnlyCollectionAsync` | `ReadOnlyCollection` | +| `QueryAsImmutableArrayAsync` | `ImmutableArray` | + +### Write APIs -### Writing data +Use write APIs when the stored procedure changes data or returns a scalar value. -| Guide | Description | +| Method | Return shape | |---|---| -| [Writing Data](/documentation/writing-data) | `ExecuteNonQueryAsync`, `ExecuteAsync`, `ExecuteScalarAsync` | -| [Table-Valued Parameters](/documentation/tvp) | Bulk inputs via `ITvpMapper` | +| `ExecuteNonQueryAsync` | Rows affected | +| `ExecuteScalarAsync` | First column of the first row | +| `ExecuteAsync` | No result | -### Infrastructure +### Multi-result APIs -| Guide | Description | +Use multi-result APIs when one stored procedure returns related data sets. + +| Method family | Return shape | |---|---| -| [Caching](/documentation/cache) | Frozen, InMemory, Redis — per-call opt-in | -| [Aspire Integration](/documentation/aspire) | `WithAspireSqlServer`, `WithAspireRedis` | +| `QueryMultipleIEnumerableAsync` | Tuple of `IEnumerable` values | +| `QueryMultipleReadOnlyCollectionAsync` | Tuple of `ReadOnlyCollection` values | +| `QueryMultipleImmutableArrayAsync` | Tuple of `ImmutableArray` values | -### Reference +### Infrastructure APIs -| Guide | Description | -|---|---| -| [API Reference](/documentation/api) | Full public surface | -| [Best Practices](/documentation/best-practices) | Architecture, SQL, performance, security | +Use infrastructure APIs when you need caching, transactions, or observability. -## Quick example +| Area | API | +|---|---| +| Caching | `AddFrozenCache`, `AddInMemoryCache`, `AddRedisCache`, `ICaeriusNetCache` | +| Transactions | `BeginTransactionAsync`, `ICaeriusNetTransaction` | +| Telemetry | `CaeriusDiagnostics`, `CaeriusTelemetryOptions` | +| Setup | `CaeriusNetBuilder` | -A full read + write repository: +## Example repository ```csharp public sealed record UserRepository(ICaeriusNetDbContext DbContext) - : IUserRepository { - public async Task> GetUsersOlderThanAsync( - byte age, CancellationToken ct) + public async ValueTask> GetUsersOlderThanAsync( + byte age, + CancellationToken ct) { var sp = new StoredProcedureParametersBuilder("dbo", "sp_GetUsers_By_Age", 450) .AddParameter("Age", age, SqlDbType.TinyInt) @@ -62,8 +100,10 @@ public sealed record UserRepository(ICaeriusNetDbContext DbContext) return await DbContext.QueryAsIEnumerableAsync(sp, ct); } - public async Task UpdateUserAgeAsync( - Guid guid, byte age, CancellationToken ct) + public async ValueTask UpdateUserAgeAsync( + Guid guid, + byte age, + CancellationToken ct) { var sp = new StoredProcedureParametersBuilder("dbo", "sp_UpdateUserAge_By_Guid") .AddParameter("Guid", guid, SqlDbType.UniqueIdentifier) @@ -74,3 +114,11 @@ public sealed record UserRepository(ICaeriusNetDbContext DbContext) } } ``` + +## Documentation conventions + +- C# examples use `ValueTask` when they directly expose CaeriusNet hot-path calls. +- SQL examples use `SET NOCOUNT ON` to prevent extra result sets. +- Builder parameter names omit the SQL `@` prefix. +- DTO examples use `[GenerateDto]` unless a manual mapper is being explained. +- TVP examples use `[GenerateTvp]` unless a manual mapper is being explained. diff --git a/Documentations/docs/documentation/writing-data.md b/Documentations/docs/documentation/writing-data.md index 5f3ad57..2da73ea 100644 --- a/Documentations/docs/documentation/writing-data.md +++ b/Documentations/docs/documentation/writing-data.md @@ -1,6 +1,6 @@ -# Writing Data +# Writing data -CaeriusNet provides three write commands for executing Stored Procedures that modify data — all asynchronous, all `CancellationToken`-aware, and all pluggable into a transaction scope. +CaeriusNet provides three write commands for executing stored procedures that modify data. All write commands are asynchronous, `CancellationToken`-aware, and available inside a transaction scope. ## Overview @@ -10,7 +10,7 @@ CaeriusNet provides three write commands for executing Stored Procedures that mo | `ExecuteAsync` | *void* (`ValueTask`) | Fire-and-forget writes — row count not needed | | `ExecuteScalarAsync` | `T?` | SELECT scalar — `SCOPE_IDENTITY`, `COUNT`, status code, etc. | -## A representative write SP +## A representative write stored procedure ```sql CREATE PROCEDURE dbo.sp_UpdateUserAge_By_Guid @@ -33,7 +33,7 @@ BEGIN BEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION; - THROW; -- re-raise so the SP span is correctly tagged Error + THROW; -- re-raise so the stored procedure span is tagged Error END CATCH; END GO @@ -73,7 +73,7 @@ public async Task DeleteUserAsync(Guid guid, CancellationToken ct) ## `ExecuteScalarAsync` — scalar return -Use when the SP returns a single scalar value — a new identity, a count, a status code, etc. +Use when the stored procedure returns a single scalar value, such as a new identity, a count, or a status code. ```sql CREATE PROCEDURE dbo.sp_InsertUser_Return_Id @@ -112,7 +112,7 @@ new StoredProcedureParametersBuilder("dbo", "sp_DeleteUser_By_Guid"); // capacit ## Multiple parameters -Chain as many `.AddParameter()` calls as needed. Each maps to a named SP parameter: +Chain as many `.AddParameter()` calls as needed. Each maps to a named stored procedure parameter: ```csharp var sp = new StoredProcedureParametersBuilder("dbo", "sp_InsertAuditLog") @@ -136,7 +136,7 @@ var sp = new StoredProcedureParametersBuilder("dbo", "sp_DeleteUsers_By_Ids") var deleted = await DbContext.ExecuteNonQueryAsync(sp, ct); ``` -See [Table-Valued Parameters](/documentation/tvp) for the full TVP guide. +See [table-valued parameters](/documentation/tvp) for the full TVP guide. ## Writes inside a transaction @@ -152,4 +152,4 @@ await tx.CommitAsync(ct); --- -**Next:** [Multiple Result Sets](/documentation/multi-results) — fetch two to five result sets in a single round-trip. +**Next:** [Multiple result sets](/documentation/multi-results) - fetch two to five result sets in a single round trip. diff --git a/Documentations/docs/examples/index.md b/Documentations/docs/examples/index.md index fff5fcc..202f09d 100644 --- a/Documentations/docs/examples/index.md +++ b/Documentations/docs/examples/index.md @@ -8,9 +8,9 @@ The examples use SQL Server stored procedures and TVPs. C# parameter builder cal | Page | Scope | |---|---| -| [Stored Procedures](/examples/stored-procedures) | Basic reads, writes, cache tiers, error handling, return-type variants | -| [Table-Valued Parameters](/examples/tvp) | `tvp_int`, `tvp_guid`, composite-key TVPs, TVP combined with scalar writes | -| [Multi-Result Sets](/examples/multi-result-sets) | 2-set / 3-set reads, TVP + multi-RS in one round-trip | +| [Stored procedures](/examples/stored-procedures) | Basic reads, writes, cache tiers, error handling, return-type variants | +| [table-valued parameters](/examples/tvp) | `tvp_int`, `tvp_guid`, composite-key TVPs, TVP combined with scalar writes | +| [Multi-result sets](/examples/multi-result-sets) | Two-set and three-set reads, including a TVP filter | | [Transactions](/examples/transactions) | Commit, C#-side rollback, SQL-side rollback (`BEGIN CATCH`), poison handling | ## Conventions used in this section @@ -23,12 +23,12 @@ The examples use SQL Server stored procedures and TVPs. C# parameter builder cal ## Running the examples -The full schema and Stored Procedures are created by the `init.sql` script bundled with the runnable example projects: +The full schema and stored procedures are created by the `init.sql` script bundled with the runnable example projects: - `Exemples/Default/CaeriusNet.Exemples.Default.Console/` — traditional connection-string setup - `Exemples/Aspire/CaeriusNet.Exemples.Aspire.AppHost/` — cloud-native Aspire orchestration (SQL Server + Redis containers, `init.sql` applied automatically via `WithCreationScript`) -Both projects exercise the same `IUsersService` and demonstrate seven distinct scenarios — including caches, TVPs, multi-result-sets, and the three transaction outcomes (commit, C#-side rollback, SQL-side rollback). +Both projects exercise the same `IUsersService` and demonstrate seven distinct scenarios, including caches, TVPs, multiple result sets, and the three transaction outcomes: commit, C#-side rollback, and SQL-side rollback. --- diff --git a/Documentations/docs/examples/multi-result-sets.md b/Documentations/docs/examples/multi-result-sets.md index 58ccaa5..5a50258 100644 --- a/Documentations/docs/examples/multi-result-sets.md +++ b/Documentations/docs/examples/multi-result-sets.md @@ -1,13 +1,13 @@ -# Multi-Result Sets +# Multi-result sets -A Stored Procedure can return more than one `SELECT` result. CaeriusNet maps each set to a separate DTO collection in a single round-trip — no extra queries, no manual `NextResultAsync` calls, and a single cohesive span in the trace. +A stored procedure can return more than one `SELECT` result. CaeriusNet maps each set to a separate DTO collection in a single round trip. You do not need extra queries or manual `NextResultAsync` calls. -This page demonstrates two scenarios: a pure 3-set dashboard read, and a 2-set read driven by a TVP filter. +This page demonstrates two scenarios: a dashboard read with three result sets, and a read with two result sets driven by a TVP filter. ## SQL Server objects ```sql --- 3-set: dashboard summary (users + orders + per-user stats) +-- Dashboard summary with users, orders, and per-user statistics. CREATE PROCEDURE Users.usp_Get_Dashboard AS BEGIN @@ -35,7 +35,7 @@ BEGIN END GO --- 2-set: users + their orders, filtered by a TVP of user IDs +-- Users and their orders, filtered by a TVP of user IDs. CREATE PROCEDURE Users.usp_Get_Users_With_Orders_By_Tvp @tvp Types.tvp_Int READONLY AS @@ -79,12 +79,12 @@ public sealed partial record UserStatsDto( decimal TotalAmount); ``` -## 1. Three result sets — pure multi-RS +## 1. Read three result sets -A 3-tuple destructures the three sets directly at the call site. The DTO type at each position **must** match the columns of the corresponding `SELECT` (positional contract): +A 3-tuple destructures the three sets directly at the call site. The DTO type at each position **must** match the columns of the corresponding `SELECT`. ```csharp -public async Task GetDashboardAsync(CancellationToken ct) +public async ValueTask GetDashboardAsync(CancellationToken ct) { var sp = new StoredProcedureParametersBuilder( "Users", "usp_Get_Dashboard", ResultSetCapacity: 25) @@ -101,12 +101,12 @@ public async Task GetDashboardAsync(CancellationToken ct) `caerius.resultset.multi = true` ¡ `caerius.resultset.expected_count = 3` ::: -## 2. Two result sets — TVP + multi-RS +## 2. Read two result sets with a TVP -You can combine a TVP input with a multi-result-set output — still **one** round-trip and **one** span: +You can combine a TVP input with a multi-result-set output in **one** round trip and **one** span: ```csharp -public async Task<(IReadOnlyCollection Users, IReadOnlyCollection Orders)> +public async ValueTask<(IReadOnlyCollection Users, IReadOnlyCollection Orders)> GetUsersWithOrdersByTvpAsync( IReadOnlyCollection userIds, CancellationToken ct) @@ -131,7 +131,7 @@ public async Task<(IReadOnlyCollection Users, IReadOnlyCollection Users, IReadOnlyCollection` through `` | 2-5 | tuple of `ImmutableArray` | | `QueryMultipleIEnumerableAsync` through `` | 2-5 | tuple of `IEnumerable` | -The two-result-set signatures are the runtime source baseline; higher arities are emitted into the package by the CaeriusNet source generator during the package build. - ::: warning Result-set order is the contract -CaeriusNet maps result sets **positionally** — the first `SELECT` becomes `T1`, the second becomes `T2`, and so on. The DTO type passed at each position must match the columns of the corresponding `SELECT`. There is no runtime name-matching; misalignment produces an `InvalidCastException` (best case) or silently wrong values. +CaeriusNet maps result sets **positionally**. The first `SELECT` becomes `T1`, the second becomes `T2`, and so on. The DTO type passed at each position must match the columns of the corresponding `SELECT`. CaeriusNet does not match columns by name at runtime. ::: --- -**Next:** [Transactions](/examples/transactions) — commit, C#-side rollback, and SQL-side rollback. +**Next:** [Transactions](/examples/transactions) - commit, C#-side rollback, and SQL-side rollback. diff --git a/Documentations/docs/examples/stored-procedures.md b/Documentations/docs/examples/stored-procedures.md index 528f589..d121411 100644 --- a/Documentations/docs/examples/stored-procedures.md +++ b/Documentations/docs/examples/stored-procedures.md @@ -1,6 +1,6 @@ -# Stored Procedures +# Stored procedures -This page walks through calling Stored Procedures with CaeriusNet — from the simplest read to error handling with a graceful fallback. Every snippet uses the source generator so DTOs implement `ISpMapper` automatically. +This page walks through calling stored procedures with CaeriusNet, from the simplest read to error handling with a graceful fallback. Every snippet uses the source generator so DTOs implement `ISpMapper` automatically. ## SQL Server objects @@ -70,7 +70,7 @@ public sealed record UsersRepository( } ``` -## 1. Basic read — no cache +## 1. Basic read without cache ```csharp public async Task> GetAllUsersAsync(CancellationToken ct) @@ -121,9 +121,9 @@ return await DbContext.QueryAsReadOnlyCollectionAsync(sp, ct); On a cache hit, **no SQL command runs and no DB span is created** — only the `caerius.cache.lookups{hit=true}` counter ticks. The Aspire dashboard accurately shows the database was not contacted. ::: -## 3. Write — scalar return +## 3. Write with scalar return -`ExecuteScalarAsync` executes the SP and returns the first column of the first row — perfect for `SCOPE_IDENTITY()`, counts, or status codes: +`ExecuteScalarAsync` executes the stored procedure and returns the first column of the first row. Use it for `SCOPE_IDENTITY()`, counts, or status codes: ```csharp public async Task CreateUserAsync(string userName, CancellationToken ct) @@ -136,7 +136,7 @@ public async Task CreateUserAsync(string userName, CancellationToken ct) } ``` -## 4. Error handling — graceful fallback +## 4. Error handling with fallback Wrap calls in `try`/`catch` to degrade gracefully when a transient SQL error occurs. `CaeriusNetSqlException` always wraps the original `SqlException` in `InnerException`, and the active OTel span is already tagged `ActivityStatusCode.Error` before the exception bubbles up: @@ -163,21 +163,21 @@ public async Task> GetAllUsersSafeAsync(CancellationToken c `StoredProcedureParametersBuilder` is independent of the return type. Choose the collection that fits your scenario: ```csharp -// IEnumerable — lazy, forward-only, lowest allocation; null on empty. +// IEnumerable: materialized sequence; empty sequence on empty. var lazy = await DbContext.QueryAsIEnumerableAsync(sp, ct); -// IReadOnlyCollection — materialized list, indexable, immutable contract. +// IReadOnlyCollection: materialized list, indexable, immutable contract. var collection = await DbContext.QueryAsReadOnlyCollectionAsync(sp, ct); -// ImmutableArray — struct-wrapped array, ideal for cached / shared data. +// ImmutableArray: ideal for cached or shared data. var array = await DbContext.QueryAsImmutableArrayAsync(sp, ct); -// T? — single-row lookup; null when the result set is empty. +// T?: single-row lookup; null when the result set is empty. var first = await DbContext.FirstQueryAsync(sp, ct); ``` -See [Reading Data](/documentation/reading-data) for guidance on choosing between them. +See [Reading data](/documentation/reading-data) for guidance on choosing a result shape. --- -**Next:** [Table-Valued Parameters](/examples/tvp) — pass sets of identifiers into a SP without dynamic SQL. +**Next:** [table-valued parameters](/examples/tvp) - pass sets of identifiers into a stored procedure without dynamic SQL. diff --git a/Documentations/docs/examples/transactions.md b/Documentations/docs/examples/transactions.md index e748a4b..e0878f8 100644 --- a/Documentations/docs/examples/transactions.md +++ b/Documentations/docs/examples/transactions.md @@ -1,10 +1,10 @@ # Transactions -CaeriusNet wraps SQL Server transactions in an `ICaeriusNetTransaction` scope obtained from `BeginTransactionAsync`. Every command executed on the scope reuses the same connection and is enlisted in the same transaction. This page walks through the three transactional outcomes you will encounter in production: **commit**, **C#-side rollback**, and **SQL-side rollback** (when the SP wraps its own `BEGIN TRY / BEGIN CATCH`). +CaeriusNet wraps SQL Server transactions in an `ICaeriusNetTransaction` scope obtained from `BeginTransactionAsync`. Every command executed on the scope reuses the same connection and is enlisted in the same transaction. This page walks through the three transactional outcomes you will encounter in production: **commit**, **C#-side rollback**, and **SQL-side rollback** (when the stored procedure wraps its own `BEGIN TRY / BEGIN CATCH`). ## Tracing -Every scope emits a parent **`TX` span** (kind = Internal) that wraps all child SP spans. The trace stays a single cohesive workflow in the Aspire dashboard: +Every scope emits a parent **`TX` span** (kind = Internal) that wraps all child stored procedure spans. The trace stays a single cohesive workflow in the Aspire dashboard: ```text TX (caerius.tx.isolation_level=ReadCommitted, caerius.tx.outcome=committed) @@ -12,7 +12,7 @@ TX (caerius.tx.isolation_level=ReadCommitted, caerius.tx.outcome=committed) └── SP Users.usp_Create_Order (caerius.tx=true) ``` -Without the parent `TX` span, each child SP would appear as an orphaned trace. The TX activity is what stitches them into a single unit of work. +The parent `TX` span groups the child stored procedure spans into one unit of work in the Aspire dashboard. ## SQL Server objects @@ -49,7 +49,7 @@ BEGIN END GO --- Self-contained transactional SP using BEGIN TRY / BEGIN CATCH +-- Self-contained transactional stored procedure using BEGIN TRY / BEGIN CATCH CREATE PROCEDURE Users.usp_Create_User_Tx_Safe @UserName NVARCHAR(64), @ForceFailure BIT = 0 @@ -84,7 +84,7 @@ END GO ``` -## Scenario 1 — Commit +## Scenario 1: Commit Two writes are committed atomically. If anything fails before `CommitAsync`, `await using` disposes the scope and rolls back automatically. @@ -126,7 +126,7 @@ public async Task CreateUserWithFirstOrderAsync( `caerius.tx.outcome = committed` ::: -## Scenario 2 — C#-side rollback +## Scenario 2: C#-side rollback The application decides to discard the work after inspecting business rules: @@ -158,9 +158,9 @@ public async Task DemonstrateClientSideRollbackAsync( If neither `CommitAsync` nor `RollbackAsync` is called and the `await using` scope exits (even due to an exception), the transaction rolls back automatically in `DisposeAsync`. The TX outcome is `auto-rollback` (clean exit) or `poisoned-auto-rollback` (a command had already failed). ::: -## Scenario 3 — SQL-side rollback (`BEGIN CATCH`) +## Scenario 3: SQL-side rollback (`BEGIN CATCH`) -The Stored Procedure handles its own transaction. When `@ForceFailure = 1`, it rolls back inside `BEGIN CATCH` and re-throws. CaeriusNet wraps the resulting `SqlException` as `CaeriusNetSqlException` and marks the SP span with `ActivityStatusCode.Error`: +The stored procedure handles its own transaction. When `@ForceFailure = 1`, it rolls back inside `BEGIN CATCH` and re-throws. CaeriusNet wraps the resulting `SqlException` as `CaeriusNetSqlException` and marks the stored procedure span with `ActivityStatusCode.Error`: ```csharp public async Task DemonstrateServerSideRollbackAsync( @@ -172,7 +172,7 @@ public async Task DemonstrateServerSideRollbackAsync( .AddParameter("ForceFailure", true, SqlDbType.Bit) .Build(); - // This call throws CaeriusNetSqlException because the SP re-raises. + // This call throws CaeriusNetSqlException because the stored procedure re-raises. // The span is tagged ActivityStatusCode.Error — this is expected. await DbContext.ExecuteAsync(sp, ct); } @@ -198,7 +198,7 @@ catch (CaeriusNetSqlException ex) ``` ::: warning Error span in the dashboard -The `SP Users.usp_Create_User_Tx_Safe` trace appears in red (Error) in the Aspire **Traces** tab. This is **intentional** — the span accurately reflects that the SQL command failed. It is not a CaeriusNet defect. +The `SP Users.usp_Create_User_Tx_Safe` trace appears in red (Error) in the Aspire **Traces** tab. This is intentional. The span accurately reflects that the SQL command failed. ::: ## Commands available on `ICaeriusNetTransaction` @@ -207,16 +207,16 @@ The transaction scope exposes the same `Execute*` / `Query*` surface as `ICaeriu | Method | Description | |---|---| -| `ExecuteAsync` | Execute a SP and ignore the result | -| `ExecuteNonQueryAsync` | Execute a SP and return the affected-row count | -| `ExecuteScalarAsync` | Execute a SP and return the first column of the first row | +| `ExecuteAsync` | Execute a stored procedure and ignore the result | +| `ExecuteNonQueryAsync` | Execute a stored procedure and return the affected-row count | +| `ExecuteScalarAsync` | Execute a stored procedure and return the first column of the first row | | `FirstQueryAsync` | Read a single row and map it to a DTO | | `QueryAsIEnumerableAsync` | Read all rows into an `IEnumerable` | | `QueryAsReadOnlyCollectionAsync` | Read all rows into a `ReadOnlyCollection` | | `QueryAsImmutableArrayAsync` | Read all rows into an `ImmutableArray` | ::: danger Nested transactions are not supported -Calling `BeginTransactionAsync` on an `ICaeriusNetTransaction` throws `NotSupportedException`. SQL Server only supports one local transaction per connection — use `SAVEPOINT` inside the SP for partial-rollback semantics. +Calling `BeginTransactionAsync` on an `ICaeriusNetTransaction` throws `NotSupportedException`. SQL Server only supports one local transaction per connection. Use `SAVEPOINT` inside the stored procedure for partial-rollback semantics. ::: --- diff --git a/Documentations/docs/examples/tvp.md b/Documentations/docs/examples/tvp.md index ed53667..7035db0 100644 --- a/Documentations/docs/examples/tvp.md +++ b/Documentations/docs/examples/tvp.md @@ -1,6 +1,6 @@ -# Table-Valued Parameters +# Table-valued parameters -A Table-Valued Parameter lets you pass an entire **set of rows** into a Stored Procedure as a single typed temporary table. This avoids dynamic SQL, IN-list size limits, and the overhead of multiple round-trips. +A table-valued parameter lets you pass an entire **set of rows** into a stored procedure as a single typed temporary table. This avoids dynamic SQL, `IN`-list size limits, and the overhead of multiple round trips. This page walks through three TVP shapes — single-int, single-Guid, composite `(int, Guid)` — and a TVP combined with a scalar write. @@ -70,9 +70,9 @@ END GO ``` -## C# TVP types — source-generated +## Create TVP row types -CaeriusNet generates the `ITvpMapper` implementation when you apply `[GenerateTvp]`. The constructor parameters must match the SQL UDT column order and types exactly: +CaeriusNet generates the `ITvpMapper` implementation when you apply `[GenerateTvp]`. The constructor parameters must match the SQL table type column order and types exactly: ```csharp using CaeriusNet.Attributes.Tvp; @@ -97,7 +97,7 @@ public async Task> GetUsersByTvpIntAsync( var sp = new StoredProcedureParametersBuilder( "Users", "usp_Get_Users_From_TvpInt", ResultSetCapacity: 5) - .AddTvpParameter("tvp", ids) // matches @tvp in the SP (no leading @) + .AddTvpParameter("tvp", ids) // matches @tvp in the stored procedure, without @ .Build(); return await DbContext.QueryAsReadOnlyCollectionAsync(sp, ct); @@ -193,14 +193,14 @@ public async Task CreateBatchOrdersAsync( } ``` -## Notes & constraints +## Notes and constraints -- The TVP parameter name in `AddTvpParameter("tvp", â€Ļ)` must match the SP `@tvp` parameter name **without** the leading `@`. -- `READONLY` is **required** on TVP parameters in SQL Server Stored Procedures. -- TVP rows are streamed via `IEnumerable` directly into the TDS protocol — no `DataTable`, no boxing. +- The TVP parameter name in `AddTvpParameter("tvp", ...)` must match the stored procedure `@tvp` parameter name **without** the leading `@`. +- `READONLY` is **required** on TVP parameters in SQL Server stored procedures. +- CaeriusNet sends TVP rows without requiring a `DataTable`. - `caerius.sp.parameters` always shows `[TVP]` for the TVP entry, even when `CaptureParameterValues = true`. The actual rows are never inlined into a span. - `AddTvpParameter` throws `ArgumentException` for an empty collection — SQL Server rejects empty TVPs. Validate before calling. --- -**Next:** [Multi-Result Sets](/examples/multi-result-sets) — fetch multiple result sets in one round-trip. +**Next:** [Multi-result sets](/examples/multi-result-sets) - fetch multiple result sets in one round trip. diff --git a/Documentations/docs/index.md b/Documentations/docs/index.md index 8b4543f..914d00a 100644 --- a/Documentations/docs/index.md +++ b/Documentations/docs/index.md @@ -3,76 +3,86 @@ layout: home hero: name: "CaeriusNet" - text: "SQL Server stored procedures to C# DTOs" - tagline: A focused .NET data-access library for teams that standardize on SQL Server stored procedures, compile-time DTO mapping, and observable database calls. + text: "SQL Server stored procedure data access for .NET" + tagline: Build typed, observable, allocation-aware data access around SQL Server stored procedures, DTO mapping, table-valued parameters, caching, and transactions. image: - alt: CaeriusNet Logo + alt: CaeriusNet logo actions: - theme: brand - text: Start with the quickstart + text: Get started link: /quickstart/getting-started - theme: alt - text: What is CaeriusNet? + text: Learn the concepts link: /quickstart/what-is-caeriusnet + - theme: alt + text: View examples + link: /examples/ - theme: alt text: API reference link: /documentation/api - - theme: alt - text: GitHub - link: https://github.com/CaeriusNET/CaeriusNet features: - - icon: đŸ› ī¸ - title: SQL Server stored procedures only - details: CaeriusNet intentionally targets Microsoft SQL Server and stored procedures. It does not translate LINQ, generate SQL text, track entities, or abstract multiple database providers. - - icon: âš™ī¸ - title: Compile-time mapper generation - details: [GenerateDto] and [GenerateTvp] emit ISpMapper<T> and ITvpMapper<T> implementations at build time. Analyzer rules validate the sealed partial primary-constructor contract. - - icon: 📖 - title: Ordinal data-reader mapping - details: Result columns are read by index through SqlDataReader with SequentialAccess. Declared result-set capacity lets CaeriusNet pre-size collections for predictable materialization. - - icon: đŸ“Ļ - title: Table-valued parameters - details: Pass large identifier or composite-key sets with SQL Server TVPs. Generated TVP mappers stream SqlDataRecord values without requiring callers to build DataTable instances. - - icon: 🔀 - title: Multiple result sets - details: Read up to five typed result sets from one stored-procedure execution. Return shapes are available for IEnumerable, ReadOnlyCollection, and ImmutableArray. - - icon: 🧊 - title: Per-call caching - details: Choose a cache tier on the parameters builder when a call is safe to cache Frozen for process-lifetime reference data, InMemory for expiring local entries, or Redis for shared distributed cache. - - icon: 🔐 - title: Transaction scopes - details: BeginTransactionAsync returns an explicit async scope with commit, rollback, poisoned-state handling, and tracing that groups child stored-procedure calls under a transaction activity. - - icon: 📡 - title: Diagnostics and telemetry - details: Built-in ActivitySource, Meter, and source-generated logging expose stored-procedure metadata, durations, failures, cache events, and optional parameter-value capture. + - title: Stored procedures by design + details: CaeriusNet targets SQL Server stored procedures. It does not translate LINQ, generate ad hoc SQL, track entities, or abstract multiple database providers. + - title: Typed result mapping + details: Map result sets to DTOs with the ISpMapper<T> contract. Use [GenerateDto] to remove repetitive mapper code while keeping ordinal mapping explicit. + - title: Table-valued parameters + details: Send large sets of IDs, GUIDs, and composite rows through SQL Server TVPs. Use [GenerateTvp] for strongly typed TVP rows. + - title: Multiple result sets + details: Read two to five typed result sets from one stored procedure call. Choose tuple return shapes based on IEnumerable<T>, ReadOnlyCollection<T>, or ImmutableArray<T>. + - title: Per-call caching + details: Add Frozen, in-memory, or Redis caching to individual read calls from the same parameters builder you use for stored procedure inputs. + - title: Transactions + details: Use explicit async transaction scopes with commit, rollback, automatic rollback on dispose, poisoned-state handling, and transaction-level tracing. + - title: Observability + details: Built-in tracing, metrics, and structured logging expose stored procedure names, durations, row counts, failures, cache hits, and transaction context. + - title: Aspire-ready setup + details: Register SQL Server, Redis, tracing, and metrics with .NET Aspire-friendly builder methods while keeping the same runtime API. --- -::: info Important scope -CaeriusNet is for SQL Server stored procedures. Parameter names passed to StoredProcedureParametersBuilder methods use the identifier only, without the SQL @ prefix. +::: info Supported data-access model +CaeriusNet is for SQL Server stored procedures. It is not a LINQ provider, entity tracker, migration tool, or multi-provider ORM. ::: -::: tip Benchmarks -Benchmark pages document the suites and methodology. Treat any published numbers as environment-specific; SQL Server edition, hardware, network latency, schema, query plans, and payload shape can change results. -::: +## Start here + +Use this documentation to learn and apply the NuGet package in production applications. + +| Goal | Article | +|---|---| +| Understand the package and decide whether it fits your architecture | [What is CaeriusNet?](/quickstart/what-is-caeriusnet) | +| Install the package and run the first stored procedure | [Install and configure](/quickstart/getting-started) | +| Find the right guide for a task | [Usage overview](/documentation/usage) | +| Read rows from stored procedures | [Reading data](/documentation/reading-data) | +| Execute writes and scalar commands | [Writing data](/documentation/writing-data) | +| Send TVPs to SQL Server | [Table-valued parameters](/documentation/tvp) | +| Read multiple result sets from one call | [Multiple result sets](/documentation/multi-results) | +| Look up the public API | [API reference](/documentation/api) | + +## Documentation structure + +The documentation is organized by how you use the package: + +1. **Get started**: Learn what CaeriusNet is, install the package, configure dependency injection, and run a query. +2. **Use CaeriusNet**: Read data, write data, map DTOs, pass TVPs, use multiple result sets, cache reads, and run transactions. +3. **Advanced scenarios**: Use source generators, AutoContracts, logging, OpenTelemetry, Aspire integration, and combined patterns. +4. **Reference**: Look up public APIs, diagnostics, best practices, and benchmark methodology. -## Documentation map +## Recommended learning path -| Area | Start here | Use it for | -|---|---|---| -| Quickstart | [Installation & Setup](/quickstart/getting-started) | Install the package, register DI, and run the first stored-procedure query. | -| Guides | [Reading Data](/documentation/reading-data) | Learn DTO mapping, TVPs, result-set shapes, caching, transactions, logging, and Aspire integration. | -| Reference | [API Reference](/documentation/api) | Look up public builders, abstractions, commands, mapper contracts, and exception behavior. | -| Diagnostics | [Diagnostic rules](/diagnostics/) | Resolve analyzer errors and warnings emitted by the source generators. | -| Examples | [Examples overview](/examples/) | Follow end-to-end stored procedure, TVP, multi-result, and transaction scenarios. | -| Benchmarks | [Performance & Benchmarks](/benchmarks/) | Understand the benchmark suites, configuration, limitations, and local run commands. | +1. Read [What is CaeriusNet?](/quickstart/what-is-caeriusnet) to understand the data-access model. +2. Complete [Install and configure](/quickstart/getting-started) to register the package and execute your first stored procedure. +3. Choose a read shape in [Reading data](/documentation/reading-data). +4. Add DTO or TVP generation with [Source generators](/documentation/source-generators). +5. Review [Best practices](/documentation/best-practices) before you design production stored procedures. +6. Use [Examples](/examples/) for end-to-end scenarios.