Benchmarks
Socigy.OpenSource.DB delivers Dapper-class speed, hand-written-ADO.NET allocations, and full NativeAOT support — measured against Dapper and EF Core. Reproduce every number yourself.
Dapper-class. AOT-native. Zero ceremony.
Socigy.OpenSource.DB is a source-generated data layer — your queries become real C# at compile time, so there's no reflection, no runtime IL emit, and nothing to warm up. The result is performance that matches hand-written Dapper, with a fully typed API.
The headline, in three numbers (PostgreSQL, .NET 10, 1 000-row read)
- ⚡ Matches hand-written Dapper on speed (statistically tied at every size) — with a fully typed API instead of hand-written SQL.
- 🪶 ~120 KB allocated — on par with hand-written ADO.NET, 1.6× less than Dapper, and up to 8× less than EF Core.
- 🚀 Runs under NativeAOT with no performance penalty — something Dapper and EF Core cannot do at all.
You get the ergonomics of an ORM, micro-ORM-class speed, lower read allocations than Dapper, far less memory than EF Core, and the only typed query layer that survives a NativeAOT publish.
Why it's fast
| Socigy | Dapper | EF Core | |
|---|---|---|---|
| Row materialization | Source-generated | Reflection.Emit (runtime IL) |
Runtime codegen |
| LINQ→SQL translation | Compiled once, cached per shape | n/a (you write SQL) | Cached |
| Boxing on read | None (GetFieldValue<T>) |
None | Varies |
| NativeAOT | ✅ Runs | ❌ | ❌ |
Column ordinals are resolved once per result set and values are read with the allocation-free
GetFieldValue<T>, so materialization is as lean as a hand-written reader loop — by ordinal, no boxing.
Predicates are translated to SQL once per query shape and cached, so repeat calls skip translation
entirely and just re-bind the parameter values — no per-call reflection or string building. (That
first-time translation costs ~450 ns; after that the shape is served from cache.)
The numbers
Environment: BenchmarkDotNet v0.15.8 · Intel Core Ultra 9 275HX · Windows 11 · .NET 10.0.8. Each call opens a pooled connection (a realistic per-request pattern), so connection acquisition is part of every measurement. Run them yourself (below) for figures on your own hardware.
How to read these tables: the best result per metric in each group is highlighted in green (lower is better — less time, less memory), Socigy's own rows are tinted, and the bars show relative cost within a group. Within noise means two results' 99.9% confidence intervals overlap — the gap is smaller than normal run-to-run variance, so neither is reliably faster.
Reads — typed `Query`, vs Dapper & EF Core
| Method | Mean | Allocated | vs Socigy | |
|---|---|---|---|---|
| 1 row | Socigy (typed Query) | 351 µs | 4.6 KB | 1.00× |
| Dapper (raw SQL) | ✓340 µs | ✓3.1 KB | 0.97× | |
| EF Core (no tracking) | 424 µs | 56 KB | 1.21× | |
| EF Core (tracking) | 441 µs | 56 KB | 1.26× | |
| 100 rows | Socigy (typed Query) | 375 µs | ✓16.7 KB | 1.00× |
| Dapper (raw SQL) | ✓365 µs | 22.9 KB | 0.97× | |
| EF Core (no tracking) | 474 µs | 87 KB | 1.26× | |
| EF Core (tracking) | 527 µs | 148 KB | 1.41× | |
| 1000 rows | Socigy (typed Query) | 611 µs | ✓122 KB | 1.00× |
| Dapper (raw SQL) | ✓593 µs | 199 KB | 0.97× | |
| EF Core (no tracking) | 762 µs | 361 KB | 1.25× | |
| EF Core (tracking) | 1166 µs | 979 KB | 1.91× |
Socigy matches Dapper on speed — and beats it on memory. Queries are translated once and cached per
shape, so typed Query lands within ~3% of hand-written Dapper at every size (their confidence
intervals overlap — a statistical tie). It also allocates less than Dapper throughout — at 1 000
rows 122 KB vs Dapper's 199 KB (1.6× less), and 8× less than EF Core tracking's 979 KB — while
running up to 1.9× faster than EF Core.
Reads — `.sql` procedure, vs Dapper & EF Core
A fixed-SQL Socigy procedure (no LINQ translation at all) against Dapper raw SQL and EF Core FromSqlRaw.
| Method | Mean | Allocated | vs Socigy | |
|---|---|---|---|---|
| 1 row | Socigy (.sql procedure) | 314 µs | 3.5 KB | 1.00× |
| Dapper (raw SQL) | ✓304 µs | ✓3.1 KB | 0.97× | |
| EF Core (FromSqlRaw, no tracking) | 408 µs | 59 KB | 1.30× | |
| 100 rows | Socigy (.sql procedure) | 384 µs | ✓15.6 KB | 1.00× |
| Dapper (raw SQL) | ✓371 µs | 22.9 KB | 0.97× | |
| EF Core (FromSqlRaw, no tracking) | 506 µs | 90 KB | 1.32× | |
| 1000 rows | Socigy (.sql procedure) | 649 µs | ✓121 KB | 1.00× |
| Dapper (raw SQL) | ✓593 µs | 199 KB | 0.91× | |
| EF Core (FromSqlRaw, no tracking) | 803 µs | 364 KB | 1.24× |
Within ~3–9% of Dapper, and well ahead of EF Core — at the same allocation as the cached typed
Query (≈121 KB at 1 000 rows) and ~3× lighter than EF's FromSqlRaw.
Writes — INSERT, vs Dapper & EF Core
| Method | Mean | Allocated | vs Socigy |
|---|---|---|---|
| Socigy (insert builder) | ✓2.03 ms | 4.2 KB | 1.00× |
| Dapper (ExecuteAsync) | 2.06 ms | ✓3.7 KB | 1.01× |
| EF Core (Add + SaveChanges) | 2.74 ms | 66 KB | 1.35× |
Single-row writes are dominated by the database commit (fsync), so times are close and noisy — Socigy and Dapper are tied within noise. Socigy's insert builder caches its statement and column plan, so it allocates just 4.2 KB — a hair above Dapper's 3.7 KB and ~16× less than EF Core's 66 KB.
Writes — bulk INSERT (`InsertMultipleAsync`), vs a per-row loop, Dapper & EF Core
InsertMultipleAsync batches a whole collection into one multi-row
INSERT … VALUES (…),(…) command (auto-chunked under PostgreSQL's 65,535-parameter limit) instead of a
command per row.
| Method | Mean | Allocated | vs Socigy | |
|---|---|---|---|---|
| 100 rows | Socigy (InsertMultipleAsync) | ✓2.88 ms | ✓136 KB | 1.00× |
| Socigy (per-row loop, 1 tx) | 30.9 ms | 233 KB | 10.81× | |
| Dapper (ExecuteAsync over list) | 141.8 ms | 154 KB | 49.68× | |
| EF Core (AddRange + SaveChanges) | 7.0 ms | 707 KB | 2.45× | |
| 1000 rows | Socigy (InsertMultipleAsync) | ✓6.67 ms | ✓1432 KB | 1.00× |
| Socigy (per-row loop, 1 tx) | 262 ms | 2314 KB | 39.54× | |
| Dapper (ExecuteAsync over list) | 1527 ms | 1525 KB | 230.23× | |
| EF Core (AddRange + SaveChanges) | 23.9 ms | 6295 KB | 3.60× |
Batched multi-row INSERT is in a different league. Because the whole batch becomes a single command,
InsertMultipleAsync is 10–40× faster than a per-row loop and far ahead of anything that issues one
command per row — Dapper's ExecuteAsync over a list does exactly that (N round-trips), landing
~50–230× slower. EF Core batches its inserts so it stays within 2–4×, but allocates ~5× more
(707 KB / 6.3 MB vs Socigy's 136 KB / 1.4 MB). Socigy is both the fastest and the leanest at every size.
Writes — UPDATE (by primary key), vs Dapper & EF Core
| Method | Mean | Allocated | vs Socigy |
|---|---|---|---|
| Socigy (update builder) | ✓1.57 ms | 6.0 KB | 1.00× |
| Dapper (ExecuteAsync) | 1.58 ms | ✓3.2 KB | 1.01× |
| EF Core (ExecuteUpdate) | 1.66 ms | 58 KB | 1.06× |
Dead-even with Dapper, both fsync-bound; EF Core trails slightly and allocates ~10× more.
NativeAOT: the one nobody else can match 🚀
Publish your app with NativeAOT and Socigy just works — and runs as fast as it does under the JIT:
| Socigy workload | JIT | NativeAOT | Delta |
|---|---|---|---|
typed Query — 1 000 rows |
616 µs | 641 µs | within noise |
typed Query — 1 row |
358 µs | 322 µs | NativeAOT faster |
.sql procedure — 1 000 rows |
586 µs | 643 µs | within noise |
| INSERT — 1 row | 2.15 ms | 1.91 ms | NativeAOT faster |
Allocations are identical across JIT and NativeAOT (≈122 KB at 1 000 rows). The parity is no accident: materialization is entirely source-generated, so there's no runtime codegen to fall back to an interpreter.
And here's the floor — under NativeAOT, Socigy against a hand-written, ordinal-based ADO.NET reader
loop (GetInt32/GetGuid by ordinal, no abstraction). This is as fast as managed data access gets:
| Workload (NativeAOT) | Socigy Mean | ADO.NET Mean | Socigy Alloc | ADO.NET Alloc |
|---|---|---|---|---|
typed Query — 1 row |
322 µs | 301 µs | 4.9 KB | 2.6 KB |
typed Query — 1 000 rows |
641 µs | 600 µs | 123 KB | 120 KB |
.sql procedure — 1 000 rows |
643 µs | 591 µs | 121 KB | 120 KB |
| INSERT — 1 row | 1.91 ms | 1.87 ms | 4.7 KB | 3.2 KB |
Raw ADO.NET is the theoretical floor — fastest on every row (the green cells) — yet Socigy stays within ~9% of it while giving you a fully typed API, and matches it on allocations at scale (≈121 KB vs 120 KB at 1 000 rows; the per-row gap is essentially gone).
No other typed library even gets to play this game — the alternatives don't run under NativeAOT at all:
| Library | NativeAOT | Why |
|---|---|---|
| Socigy.OpenSource.DB | ✅ Runs | Source-generated materialization — no runtime codegen |
| Dapper | ❌ Fails | Materializes via System.Reflection.Emit (runtime IL) |
| EF Core | ❌ Fails | Query pipeline relies on runtime code generation |
In an AOT-published service, Socigy is the typed data layer — because it's the only one that boots.
Run it yourself
Don't take our word for it — every number above is reproducible in minutes.
1. Prerequisites
- .NET 10 SDK (
dotnet --version≥ 10). - A PostgreSQL instance for every suite except
ParseBenchmarks. - NativeAOT only: a C/C++ toolchain for the native link step — on Windows the Desktop development
with C++ workload (or Build Tools); on Linux
clang+zlib. See the NativeAOT prerequisites. BenchmarkDotNet pulls theMicrosoft.DotNet.ILCompilerpackage automatically.
net10.0 target — older
versions fail with Invalid TFM: 'net10.0'. The repo pins a compatible version, so a fresh clone works.2. Start a PostgreSQL (example: Docker)
docker run --rm -d --name socigy-bench -p 5432:5432 \
-e POSTGRES_PASSWORD=1234 -e POSTGRES_DB=postgres postgres:163. Point the benchmarks at it
The connection string is read from the BENCH_DB environment variable; if unset it defaults to
Host=localhost;Port=5432;Username=postgres;Password=1234;Database=postgres.
# PowerShell
$env:BENCH_DB = "Host=localhost;Port=5432;Username=postgres;Password=1234;Database=postgres"# bash
export BENCH_DB="Host=localhost;Port=5432;Username=postgres;Password=1234;Database=postgres"The bench_users (1000 rows) and bench_writes tables are created and seeded automatically on the first
run — no manual SQL needed.
4. Run a suite (Release only)
# Socigy vs Dapper vs EF Core
dotnet run -c Release --project Benchmarks # everything
dotnet run -c Release --project Benchmarks -- --filter *QueryBenchmarks*
dotnet run -c Release --project Benchmarks -- --filter *ProcedureBenchmarks*
dotnet run -c Release --project Benchmarks -- --filter *InsertBenchmarks*
dotnet run -c Release --project Benchmarks -- --filter *UpdateBenchmarks*
dotnet run -c Release --project Benchmarks -- --filter *ParseBenchmarks* # no database needed5. Run the NativeAOT comparison
Publishes a native build per benchmark and runs the suites under JIT and NativeAOT side by side (Socigy vs raw ADO.NET):
dotnet run -c Release --project Benchmarks.Aot6. Find the results
BenchmarkDotNet prints a summary table and writes report files to BenchmarkResults/results/ next
to the project — GitHub-flavoured Markdown (*-report-github.md), JSON (*-report-full.json), CSV and
HTML.
Benchmarks/ and Benchmarks.Aot/ projects, each with a
README.md documenting every suite.