Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,14 @@ dotnet_diagnostic.CA1859.severity = suggestion
dotnet_diagnostic.CA1860.severity = suggestion
dotnet_diagnostic.CA1861.severity = suggestion
dotnet_diagnostic.CA1862.severity = suggestion

# Checked-in API files are the approval record for shipped package surfaces.
dotnet_public_api_analyzer.require_api_files = true
dotnet_public_api_analyzer.enable_nullability = true
dotnet_diagnostic.RS0016.severity = error
dotnet_diagnostic.RS0017.severity = error
dotnet_diagnostic.RS0024.severity = error
dotnet_diagnostic.RS0025.severity = error
# Existing overload families intentionally retain their historical optional
# parameters; snapshots, rather than RS0026, govern future changes.
dotnet_diagnostic.RS0026.severity = none
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ jobs:
shell: pwsh
run: ./eng/test-release-governance.ps1

- name: Validate compatibility contract consistency
shell: pwsh
run: ./eng/test-compatibility-consistency.ps1

- name: Validate Dependabot configuration
run: check-jsonschema --builtin-schema vendor.dependabot .github/dependabot.yml

Expand Down Expand Up @@ -128,6 +132,35 @@ jobs:
run: >-
dotnet ./test/Dapper.FluentMap.ProviderCompatibility.Tests/bin/Release/net10.0/Dapper.FluentMap.ProviderCompatibility.Tests.dll

public-api:
name: Public API and nullable contracts
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 1
persist-credentials: false
show-progress: false

- name: Setup .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
global-json-file: global.json

- name: Restore
run: dotnet restore ./Dapper.FluentMap.slnx

- name: Validate checked-in API baselines and nullable annotations
run: |
dotnet build ./src/Dapper.FluentMap/Dapper.FluentMap.csproj --configuration Release --no-restore
dotnet build ./src/Dapper.FluentMap.Dommel/Dapper.FluentMap.Dommel.csproj --configuration Release --no-restore
dotnet build ./src/Dapper.FluentMap.DependencyInjection/Dapper.FluentMap.DependencyInjection.csproj --configuration Release --no-restore
dotnet build ./src/Dapper.FluentMap.Analyzers/Dapper.FluentMap.Analyzers.csproj --configuration Release --no-restore
dotnet build ./src/Dapper.FluentMap.Generators/Dapper.FluentMap.Generators.csproj --configuration Release --no-restore

roslyn-components:
name: Analyzer and generator compatibility
runs-on: ubuntu-latest
Expand Down Expand Up @@ -376,6 +409,12 @@ jobs:
$ErrorActionPreference = 'Stop'
./eng/validate-package-metadata.ps1 -PackageDirectory './artifacts/packages'

- name: Validate packaged compatibility contract
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
./eng/validate-compatibility-consistency.ps1 -PackageDirectory './artifacts/packages'

- name: Validate package artifact set
shell: pwsh
run: |
Expand Down Expand Up @@ -457,6 +496,7 @@ jobs:
needs:
- repository-configuration
- compatibility
- public-api
- roslyn-components
- provider-compatibility
if: ${{ vars.SONAR_CI_ENABLED == 'true' && ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch') }}
Expand Down Expand Up @@ -763,6 +803,7 @@ jobs:
needs:
- repository-configuration
- compatibility
- public-api
- roslyn-components
- provider-compatibility
- native-aot
Expand All @@ -775,6 +816,7 @@ jobs:
run: |
echo "repository-configuration=${{ needs.repository-configuration.result }}"
echo "compatibility=${{ needs.compatibility.result }}"
echo "public-api=${{ needs.public-api.result }}"
echo "roslyn-components=${{ needs.roslyn-components.result }}"
echo "provider-compatibility=${{ needs.provider-compatibility.result }}"
echo "native-aot=${{ needs.native-aot.result }}"
Expand All @@ -783,6 +825,7 @@ jobs:

if [[ "${{ needs.repository-configuration.result }}" != "success" || \
"${{ needs.compatibility.result }}" != "success" || \
"${{ needs.public-api.result }}" != "success" || \
"${{ needs.provider-compatibility.result }}" != "success" || \
"${{ needs.native-aot.result }}" != "success" || \
"${{ needs.roslyn-components.result }}" != "success" || \
Expand Down
152 changes: 152 additions & 0 deletions .github/workflows/compatibility-canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
name: Compatibility Canary

on:
schedule:
- cron: "43 7 * * 2"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: compatibility-canary-${{ github.repository }}
cancel-in-progress: false

env:
DOTNET_CLI_TELEMETRY_OPTOUT: "true"
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: "true"

jobs:
supported-dapper:
name: Supported evidence (${{ matrix.label }}, Dapper ${{ matrix.version }})
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
include:
- label: minimum-supported
version: 2.1.79
- label: latest-supported
version: 2.1.89
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false

- name: Setup .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
global-json-file: global.json

- name: Restore supported lane
run: dotnet restore ./Dapper.FluentMap.slnx -p:DapperPackageVersion=${{ matrix.version }}

- name: Build supported lane
run: dotnet build ./Dapper.FluentMap.slnx --configuration Release --no-restore -p:DapperPackageVersion=${{ matrix.version }}

- name: Test supported lane
run: >-
dotnet test ./test/Dapper.FluentMap.Tests/Dapper.FluentMap.Tests.csproj
--configuration Release --no-build

forward-dapper:
name: Experimental forward Dapper
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false

- name: Setup .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
global-json-file: global.json

- name: Resolve forward-looking Dapper version
id: resolve
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$supported = '2.1.89'
$index = Invoke-RestMethod 'https://api.nuget.org/v3-flatcontainer/dapper/index.json'
$candidates = @(
$index.versions |
Where-Object { [version](($_ -split '-', 2)[0]) -gt [version]$supported } |
Sort-Object { [version](($_ -split '-', 2)[0]) }
)
$candidate = if ($candidates.Count -eq 0) { $null } else { $candidates[-1] }
$available = -not [string]::IsNullOrWhiteSpace($candidate)
"available=$($available.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
"version=$candidate" >> $env:GITHUB_OUTPUT
if ($available) {
"### Experimental Dapper candidate: $candidate" >> $env:GITHUB_STEP_SUMMARY
} else {
'### No newer Dapper stable/prerelease is currently resolvable.' >> $env:GITHUB_STEP_SUMMARY
}

- name: Restore experimental lane
if: ${{ steps.resolve.outputs.available == 'true' }}
run: dotnet restore ./Dapper.FluentMap.slnx -p:DapperPackageVersion=${{ steps.resolve.outputs.version }}

- name: Build experimental lane
if: ${{ steps.resolve.outputs.available == 'true' }}
run: dotnet build ./Dapper.FluentMap.slnx --configuration Release --no-restore -p:DapperPackageVersion=${{ steps.resolve.outputs.version }}

- name: Test experimental lane
if: ${{ steps.resolve.outputs.available == 'true' }}
run: >-
dotnet test ./test/Dapper.FluentMap.Tests/Dapper.FluentMap.Tests.csproj
--configuration Release --no-build

provider-clients:
name: Experimental latest provider clients
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false

- name: Setup .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
global-json-file: global.json

- name: Restore latest stable provider clients
run: >-
dotnet restore ./test/Dapper.FluentMap.ProviderCompatibility.Tests/Dapper.FluentMap.ProviderCompatibility.Tests.csproj
-p:SqlClientPackageVersion=*
-p:SqliteClientPackageVersion=*
-p:MySqlConnectorPackageVersion=*
-p:NpgsqlPackageVersion=*

- name: Report resolved experimental clients
shell: pwsh
run: |
$assets = Get-Content -Raw './test/Dapper.FluentMap.ProviderCompatibility.Tests/obj/project.assets.json' | ConvertFrom-Json
$packages = @('Microsoft.Data.SqlClient/', 'Microsoft.Data.Sqlite/', 'MySqlConnector/', 'Npgsql/')
foreach ($prefix in $packages) {
$resolved = @($assets.libraries.PSObject.Properties.Name | Where-Object { $_ -like "$prefix*" })
"- experimental: $($resolved -join ', ')" >> $env:GITHUB_STEP_SUMMARY
}

- name: Build against experimental clients
run: >-
dotnet build ./test/Dapper.FluentMap.ProviderCompatibility.Tests/Dapper.FluentMap.ProviderCompatibility.Tests.csproj
--configuration Release --no-restore
-p:SqlClientPackageVersion=*
-p:SqliteClientPackageVersion=*
-p:MySqlConnectorPackageVersion=*
-p:NpgsqlPackageVersion=*

- name: Run always-available SQLite evidence
env:
DFM_PROVIDER_FILTER: SQLite
run: >-
dotnet ./test/Dapper.FluentMap.ProviderCompatibility.Tests/bin/Release/net10.0/Dapper.FluentMap.ProviderCompatibility.Tests.dll
-failSkips -parallelMode none
102 changes: 102 additions & 0 deletions .github/workflows/performance-report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Performance Regression Report

on:
schedule:
- cron: "19 9 * * 3"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: performance-report-${{ github.repository }}
cancel-in-progress: false

env:
DOTNET_CLI_TELEMETRY_OPTOUT: "true"
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: "true"

jobs:
materialization:
name: Materialization hot paths (report only)
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false

- name: Setup .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
global-json-file: global.json

- name: Restore benchmark project
run: dotnet restore ./benchmarks/Dapper.FluentMap.Benchmarks/Dapper.FluentMap.Benchmarks.csproj

- name: Build benchmark project
run: dotnet build ./benchmarks/Dapper.FluentMap.Benchmarks/Dapper.FluentMap.Benchmarks.csproj --configuration Release --no-restore

- name: Run materialization benchmark report
run: >-
dotnet run --project ./benchmarks/Dapper.FluentMap.Benchmarks/Dapper.FluentMap.Benchmarks.csproj
--configuration Release --no-build --
--filter "*DapperWithFluentMapRootMapping*" "*QueryMappedSimple" "*RuntimeQueryMappedSimple"
"*RuntimeQueryGeneratedSimpleExtraColumn*" "*QueryMappedGeneratedPropertyConverter*"
"*QueryMappedTwoSegments*" "*QueryMappedThreeSegments*"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
--exporters fulljson

- name: Capture benchmark run metadata
shell: pwsh
env:
BENCHMARK_COMMAND: >-
dotnet run --project ./benchmarks/Dapper.FluentMap.Benchmarks/Dapper.FluentMap.Benchmarks.csproj
--configuration Release --no-build --
--filter "*DapperWithFluentMapRootMapping*" "*QueryMappedSimple" "*RuntimeQueryMappedSimple"
"*RuntimeQueryGeneratedSimpleExtraColumn*" "*QueryMappedGeneratedPropertyConverter*"
"*QueryMappedTwoSegments*" "*QueryMappedThreeSegments*"
--exporters fulljson
COMMIT_SHA: "${{ github.sha }}"
RUNNER_NAME: "${{ runner.name }}"
RUNNER_OS: "${{ runner.os }}"
RUNNER_ARCH: "${{ runner.arch }}"
run: |
New-Item -ItemType Directory -Force -Path './artifacts/benchmark-report' | Out-Null
$metadata = [ordered]@{
command = $env:BENCHMARK_COMMAND
commit = $env:COMMIT_SHA
runner = [ordered]@{
name = $env:RUNNER_NAME
os = $env:RUNNER_OS
architecture = $env:RUNNER_ARCH
}
dotnetInfo = ((dotnet --info) -join [Environment]::NewLine)
}
$metadata | ConvertTo-Json -Depth 4 |
Set-Content -LiteralPath './artifacts/benchmark-report/run-metadata.json' -Encoding utf8

- name: Compare with checked-in baseline
shell: pwsh
run: |
$result = Get-ChildItem './.tmp/benchmarks/BenchmarkDotNet.Artifacts/results/*-report-full.json' |
Select-Object -First 1
if ($null -eq $result) {
throw 'BenchmarkDotNet JSON output was not produced.'
}
./eng/compare-benchmark-results.ps1 `
-BaselinePath './benchmarks/baselines/materialization-windows-net10.json' `
-CurrentPath $result.FullName `
-OutputDirectory './artifacts/benchmark-report'
Get-Content './artifacts/benchmark-report/benchmark-comparison.md' >> $env:GITHUB_STEP_SUMMARY

- name: Upload benchmark evidence
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: materialization-benchmark-report
path: |
./.tmp/benchmarks/BenchmarkDotNet.Artifacts/results/*
./artifacts/benchmark-report/*
if-no-files-found: error
retention-days: 90
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ The historical archived package history is not reconstructed here. This changelo

## [Unreleased]

### Added

- Added reflection-free `GeneratedParameters` for parameterized strict-generated queries, including provider-matrix and Native AOT smoke coverage.
- Added Dommel `InsertMapped*`/`UpdateMapped*` operations that execute per-property write converters while preserving persistence exclusions and Dapper type-handler precedence.
- Added three-input FluentMap-controlled multi-mapping with multiple validated `splitOn` boundaries, per-segment profiles, async execution, null-child semantics and isolated-runtime APIs.
- Added explicit `ConstructUsing(...)` factory strategies for one to four mapped values, with configuration validation and deterministic generated/strict boundaries.
- Added analyzer diagnostic `DFM016` for duplicate explicit construction strategies.
- Added checked-in nullable public API baselines for every shipped assembly, enforced by CI.
- Added report-only scheduled/manual compatibility canaries and materialization benchmark regression reporting with machine-readable artifacts.

### Changed

- Generated materializers now accept safe additional reader columns that do not resolve to explicit FluentMap members and provide specific strict diagnostics for missing, duplicate/ambiguous and explicitly mapped additional columns; Dapper convention-only discovery remains a non-strict behavior.
- Two- and three-input multi-mapping now share a single segment materialization pipeline.
- Public projects now emit deliberate nullable reference annotations using staged `Nullable=annotations`; optional query inputs, metadata and multi-mapping null-child delegates reflect actual behavior without changing CLR signatures.
- Generated value-conversion failures now surface as contextual `FluentMapConfigurationException` instances instead of leaking provider-dependent `FormatException` or `InvalidCastException` exceptions.

## [3.4.0] - 2026-09-27

### Added
Expand Down
Loading
Loading