SQL Server

Benchmarks

How ARX compares with other ways of doing the same work on SQL Server, measured with BenchmarkDotNet.

A living summary of the SQL Server benchmarks: how ARX compares with raw Microsoft.Data.SqlClient, Dapper and EF Core. Update it whenever a benchmark is rerun or an operation is added.

Last updated 2026-11-09: first full run of every operation (108 benchmarks), then Delete rerun after the JSON-list change
Operations measured Create, CreateMultiple, Update, UpdateMultiple, Delete, DeleteMultiple, Retrieve, RetrieveMultiple (rows, a page with the total), Execute (a procedure with result rows and OUTPUT values, a scalar function)
Machine Intel Core i7-8750H, Windows 11, .NET 10.0.14, SQL Server 2022 (16.0.4225.2, mcr.microsoft.com/mssql/server:2022-latest) in Docker Desktop, Microsoft.Data.SqlClient 7.1.1, BenchmarkDotNet 0.15.8, job Standard (8 iterations, 3 warm-ups)
Compared with Raw SqlClient (hand-written, typed parameters), Dapper 2.1.89, EF Core 10 (Microsoft.EntityFrameworkCore.SqlServer 10.0.0), SqlBulkCopy
Reports The full run: results/2026-11-09-full-run/. Delete after the change: results/2026-11-09-delete-after-json-list.md. The IN-list probe: results/probes/in-list-vs-openjson.md. After READ COMMITTED on writes (E16): Update, Delete (ARX and raw only).

In one line: ARX is level with hand-written SqlClient for single-row work, level with the best hand-written form for many-row writes (and far ahead of the usual one and of EF Core), level with Dapper and EF Core for reads, and ahead of everyone for deleting lists of 100 to 1,000 ids. Raw typed readers are faster than ARX's dictionaries for large reads.

Read the ratios, not the absolute times, which depend on the machine. Differences under about 10% on a single run are inside the noise here. One run of each benchmark so far: treat close calls as level.

Every operation at a glance (2026-11-09)

Mean times. "Usual" is the hand-written form most code uses; "best" is the fastest hand-written form found.

Operation ARX Raw SqlClient Dapper EF Core ARX vs raw
Create, one row 2.65 ms 2.57 ms 2.58 ms 3.83 ms level (1.03×)
CreateMultiple, 100 rows, ids back 6.28 ms usual 24.5 ms, best 8.18 ms – 24.0 ms 3.9× faster than usual, 0.77× best
CreateMultiple, 1,000 rows, ids back 35.5 ms usual 437 ms, best 31.8 ms – 286 ms 12× faster than usual, 1.12× best
CreateMultiple, 10,000 rows, ids back 339 ms usual 4.43 s, best 345 ms – 1.85 s level with best
CreateMultiple, 10,000 rows, no ids needed 365 ms (still returns ids) SqlBulkCopy 111 ms one statement a row: 27.2 s – 3.3× SqlBulkCopy
Update, one row 2.67–2.86 ms 2.47–2.78 ms 2.76 ms 3.72 ms 0.96–1.16× (two runs)
UpdateMultiple, 100 rows 6.23 ms 5.73 ms 273 ms 25.0 ms 1.09×
UpdateMultiple, 1,000 rows 27.8 ms 23.2 ms 2.52 s 262 ms 1.20×
UpdateMultiple, 10,000 rows 269–279 ms 219–261 ms 25.7 s 1.96 s 1.07–1.23× (two runs)
Delete, one row 4.38–4.74 ms 4.20–4.66 ms 4.14 ms 12.4 ms 0.96–1.12× (three runs)
DeleteMultiple, 100 ids 4.87 ms IN list 5.89 ms 6.12 ms 15.1 ms 0.83×
DeleteMultiple, 1,000 ids 10.5–11.5 ms IN list 180 ms 174 ms 264 ms 16× faster
DeleteMultiple, 10,000 ids 52.3 ms OPENJSON 48.5 ms 2.27 s 61.4 ms level
Retrieve, one row by id 1.60 ms 1.47 ms 1.44 ms 2.12 ms 1.09×
RetrieveMultiple, 100 rows 1.88 ms 1.97 ms 2.76 ms 2.83 ms level
RetrieveMultiple, 1,000 rows 5.82 ms 4.01 ms 4.97 ms 5.15 ms 1.45× (1.17× Dapper)
RetrieveMultiple, 10,000 rows 62.8 ms typed 34.3 ms 64.4 ms 61.0 ms 1.83× typed; level with Dapper and EF Core
A page of 50 with the total 1.76–2.88 ms 2.68–2.73 ms – – level
Execute: a procedure, 1 row + OUTPUT values 3.24 ms 3.25 ms 2.96 ms no way to read OUTPUT values level
Execute: a procedure, 1,000 rows 4.78 ms 4.06 ms 4.04 ms – 1.18×
Execute: a scalar function 1.47–1.81 ms 1.43–1.51 ms – – 1.03–1.20×

"Usual" CreateMultiple is a multi-row INSERT … OUTPUT of up to 2,000 parameters a statement (ids unordered); "best" is a hand-written MERGE from one OPENJSON parameter returning ids in list order, which is what ARX sends for groups of 20 rows or more. The Dapper CreateMultiple and UpdateMultiple columns are ExecuteAsync(sql, list), one statement per row: what Dapper does with a list.

What the numbers say

  • Single-row calls are level with raw SqlClient and Dapper (0.96–1.16× across the runs), and 1.3–2.6× faster than EF Core. Getting Create there needed the plain OUTPUT change: the table variable cost about 2 ms a call.
  • Many-row writes are level with the best hand-written SQL (0.77–1.20×): the JSON row source sends one parameter for any number of rows, so SQL Server compiles one plan and there is no 2,100-parameter limit to split around. Against the usual hand-written SQL (multi-row VALUES) ARX is 4–13× faster, and 4–9× faster than EF Core.
  • SqlBulkCopy is 3.3× faster for 10,000 rows when ids aren't needed. ARX always returns ids; a bulk path without them would be a new option.
  • Lists of ids: SQL Server's cost for an IN list climbs faster than the list (a SELECT by 1,000 ids is 28 ms as an IN list, 5 ms as one OPENJSON parameter, with a cached plan). ARX sends lists from 50 values as OPENJSON, so it beats every IN-list approach from 100 ids up (16× at 1,000).
  • Reads are level with Dapper and EF Core. A raw typed reader is faster for large results (1.8× at 10,000 rows): ARX builds a dictionary per row, as on PostgreSQL, and allocates 2.3× what the raw reader does.

The reports

BenchmarkDotNet's own output for the runs the summary cites, as committed: every number above comes from one of these.

2026-11-09: full run Create.CreateBenchmarks

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]   : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  Standard : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=Standard  IterationCount=8  LaunchCount=1  
WarmupCount=3  

Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
'Raw INSERT … OUTPUT inserted.id' 2.573 ms 0.1122 ms 0.0498 ms 1.00 0.03 - 9.34 KB 1.00
'Dapper QuerySingle (INSERT … OUTPUT)' 2.583 ms 0.1089 ms 0.0484 ms 1.00 0.03 - 8.05 KB 0.86
'EF Core Add + SaveChanges' 3.832 ms 0.5485 ms 0.2869 ms 1.49 0.11 15.6250 94.76 KB 10.15
'ARX CreateAsync' 2.649 ms 0.2446 ms 0.1279 ms 1.03 0.05 - 17.84 KB 1.91
2026-11-09: full run Create.CreateMultipleBenchmarks

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]   : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  Standard : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=Standard  IterationCount=8  LaunchCount=1  
WarmupCount=3  

Method Categories Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
'Raw SqlBulkCopy' CreateMultiple, ids not needed 1 11.092 ms 0.5198 ms 0.2308 ms 1.00 0.03 - - - 42.25 KB 1.00
'Dapper ExecuteAsync(sql, list): one statement per row' CreateMultiple, ids not needed 1 2.662 ms 0.2223 ms 0.1163 ms 0.24 0.01 - - - 7.74 KB 0.18
'ARX CreateMultipleAsync (still returns ids)' CreateMultiple, ids not needed 1 2.873 ms 0.5769 ms 0.3018 ms 0.26 0.03 - - - 16.94 KB 0.40
'Raw SqlBulkCopy' CreateMultiple, ids not needed 10 11.927 ms 0.5242 ms 0.2328 ms 1.00 0.03 - - - 50.31 KB 1.00
'Dapper ExecuteAsync(sql, list): one statement per row' CreateMultiple, ids not needed 10 30.454 ms 5.7719 ms 3.0188 ms 2.55 0.24 - - - 46.55 KB 0.93
'ARX CreateMultipleAsync (still returns ids)' CreateMultiple, ids not needed 10 4.220 ms 0.9844 ms 0.5149 ms 0.35 0.04 7.8125 - - 55.35 KB 1.10
'Raw SqlBulkCopy' CreateMultiple, ids not needed 100 14.761 ms 1.3903 ms 0.7271 ms 1.00 0.07 - - - 135.44 KB 1.00
'Dapper ExecuteAsync(sql, list): one statement per row' CreateMultiple, ids not needed 100 260.672 ms 35.9391 ms 15.9572 ms 17.70 1.30 - - - 434.76 KB 3.21
'ARX CreateMultipleAsync (still returns ids)' CreateMultiple, ids not needed 100 7.571 ms 2.8674 ms 1.4997 ms 0.51 0.10 15.6250 - - 141.11 KB 1.04
'Raw SqlBulkCopy' CreateMultiple, ids not needed 1000 21.389 ms 2.5601 ms 1.1367 ms 1.00 0.07 218.7500 62.5000 - 1136.49 KB 1.00
'Dapper ExecuteAsync(sql, list): one statement per row' CreateMultiple, ids not needed 1000 2,594.951 ms 240.9890 ms 126.0419 ms 121.60 7.96 - - - 4316.01 KB 3.80
'ARX CreateMultipleAsync (still returns ids)' CreateMultiple, ids not needed 1000 30.811 ms 1.9229 ms 1.0057 ms 1.44 0.08 156.2500 156.2500 156.2500 1251.34 KB 1.10
'Raw SqlBulkCopy' CreateMultiple, ids not needed 10000 111.099 ms 22.7953 ms 11.9224 ms 1.01 0.15 1750.0000 750.0000 - 12168.69 KB 1.00
'Dapper ExecuteAsync(sql, list): one statement per row' CreateMultiple, ids not needed 10000 27,243.522 ms 1,575.8506 ms 699.6874 ms 247.75 26.18 9000.0000 - - 43131.83 KB 3.54
'ARX CreateMultipleAsync (still returns ids)' CreateMultiple, ids not needed 10000 364.627 ms 35.9055 ms 18.7793 ms 3.32 0.38 1000.0000 - - 12484.52 KB 1.03
'Raw multi-row INSERT … OUTPUT (ids unordered)' CreateMultiple, returns ids 1 2.573 ms 0.1460 ms 0.0648 ms 1.00 0.03 - - - 9.92 KB 1.00
'Raw MERGE from OPENJSON, ids in order' CreateMultiple, returns ids 1 5.306 ms 0.7502 ms 0.3331 ms 2.06 0.13 - - - 8.86 KB 0.89
'EF Core AddRange (typed)' CreateMultiple, returns ids 1 3.869 ms 0.5354 ms 0.2800 ms 1.50 0.11 15.6250 - - 94.75 KB 9.55
'ARX CreateMultipleAsync' CreateMultiple, returns ids 1 2.756 ms 0.2904 ms 0.1289 ms 1.07 0.05 - - - 16.94 KB 1.71
'Raw multi-row INSERT … OUTPUT (ids unordered)' CreateMultiple, returns ids 10 3.662 ms 0.5913 ms 0.2626 ms 1.00 0.09 7.8125 - - 48.03 KB 1.00
'Raw MERGE from OPENJSON, ids in order' CreateMultiple, returns ids 10 5.377 ms 0.2418 ms 0.1073 ms 1.47 0.10 - - - 12.71 KB 0.26
'EF Core AddRange (typed)' CreateMultiple, returns ids 10 4.584 ms 0.7304 ms 0.3820 ms 1.26 0.13 39.0625 7.8125 - 209.63 KB 4.36
'ARX CreateMultipleAsync' CreateMultiple, returns ids 10 3.480 ms 0.6085 ms 0.2702 ms 0.95 0.09 7.8125 - - 55.35 KB 1.15
'Raw multi-row INSERT … OUTPUT (ids unordered)' CreateMultiple, returns ids 100 24.499 ms 2.0911 ms 1.0937 ms 1.00 0.06 83.3333 - - 464.07 KB 1.00
'Raw MERGE from OPENJSON, ids in order' CreateMultiple, returns ids 100 8.176 ms 0.5162 ms 0.2700 ms 0.33 0.02 - - - 56.54 KB 0.12
'EF Core AddRange (typed)' CreateMultiple, returns ids 100 24.026 ms 2.1986 ms 0.9762 ms 0.98 0.06 200.0000 66.6667 - 1305.03 KB 2.81
'ARX CreateMultipleAsync' CreateMultiple, returns ids 100 6.275 ms 0.1607 ms 0.0573 ms 0.26 0.01 23.4375 - - 141.1 KB 0.30
'Raw multi-row INSERT … OUTPUT (ids unordered)' CreateMultiple, returns ids 1000 436.951 ms 44.2340 ms 23.1353 ms 1.00 0.07 1000.0000 - - 4874.8 KB 1.00
'Raw MERGE from OPENJSON, ids in order' CreateMultiple, returns ids 1000 31.783 ms 3.9694 ms 1.7624 ms 0.07 0.01 71.4286 71.4286 71.4286 514.59 KB 0.11
'EF Core AddRange (typed)' CreateMultiple, returns ids 1000 285.501 ms 119.1096 ms 62.2966 ms 0.65 0.14 2000.0000 1000.0000 - 12381.92 KB 2.54
'ARX CreateMultipleAsync' CreateMultiple, returns ids 1000 35.515 ms 5.0961 ms 2.2627 ms 0.08 0.01 142.8571 142.8571 142.8571 1251.39 KB 0.26
'Raw multi-row INSERT … OUTPUT (ids unordered)' CreateMultiple, returns ids 10000 4,431.695 ms 300.4521 ms 157.1423 ms 1.00 0.05 8000.0000 7000.0000 - 48718.85 KB 1.00
'Raw MERGE from OPENJSON, ids in order' CreateMultiple, returns ids 10000 345.094 ms 74.0173 ms 38.7125 ms 0.08 0.01 - - - 5203.79 KB 0.11
'EF Core AddRange (typed)' CreateMultiple, returns ids 10000 1,854.737 ms 134.8789 ms 59.8871 ms 0.42 0.02 19000.0000 6000.0000 - 121456.69 KB 2.49
'ARX CreateMultipleAsync' CreateMultiple, returns ids 10000 339.302 ms 77.3277 ms 34.3340 ms 0.08 0.01 1000.0000 - - 12480.3 KB 0.26
2026-11-09: full run Delete.DeleteBenchmarks

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]          : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  SingleOperation : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=SingleOperation  InvocationCount=1  IterationCount=12  
LaunchCount=1  RunStrategy=Monitoring  UnrollFactor=1  
WarmupCount=1  

Method Rows Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 1 4.660 ms 0.7311 ms 0.5708 ms 4.649 ms 1.01 0.17 - 4.3 KB 1.00
'Dapper ExecuteAsync(DELETE … IN @ids)' 1 4.448 ms 1.9072 ms 1.4891 ms 4.160 ms 0.97 0.33 - 9.84 KB 2.29
'EF Core ExecuteDeleteAsync (Where ids.Contains)' 1 13.717 ms 31.9640 ms 24.9554 ms 6.083 ms 2.98 5.26 - 143.98 KB 33.51
'ARX DeleteAsync / DeleteMultipleAsync' 1 4.461 ms 0.6887 ms 0.5377 ms 4.333 ms 0.97 0.16 - 8.49 KB 1.98
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 100 6.089 ms 0.7223 ms 0.5639 ms 6.027 ms 1.01 0.12 - 43.22 KB 1.00
'Dapper ExecuteAsync(DELETE … IN @ids)' 100 7.240 ms 3.6444 ms 2.8453 ms 6.319 ms 1.20 0.46 - 46.21 KB 1.07
'EF Core ExecuteDeleteAsync (Where ids.Contains)' 100 19.187 ms 27.1563 ms 21.2019 ms 13.946 ms 3.17 3.39 - 962.21 KB 22.26
'ARX DeleteAsync / DeleteMultipleAsync' 100 8.034 ms 7.6499 ms 5.9726 ms 6.338 ms 1.33 0.96 - 45.91 KB 1.06
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 1000 261.626 ms 50.1401 ms 39.1461 ms 262.899 ms 1.02 0.21 - 429.41 KB 1.00
'Dapper ExecuteAsync(DELETE … IN @ids)' 1000 199.019 ms 32.7524 ms 25.5709 ms 192.120 ms 0.78 0.15 - 389.53 KB 0.91
'EF Core ExecuteDeleteAsync (Where ids.Contains)' 1000 293.277 ms 81.1122 ms 63.3271 ms 280.950 ms 1.14 0.29 15000.0000 71562.17 KB 166.65
'ARX DeleteAsync / DeleteMultipleAsync' 1000 197.567 ms 27.5715 ms 21.5260 ms 190.362 ms 0.77 0.14 - 390.95 KB 0.91
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 10000 57.818 ms 12.8123 ms 10.0030 ms 53.644 ms 1.02 0.22 - 211.43 KB 1.00
'Dapper ExecuteAsync(DELETE … IN @ids)' 10000 2,638.094 ms 249.4983 ms 194.7919 ms 2,580.032 ms 46.68 7.29 - 3762.84 KB 17.80
'EF Core ExecuteDeleteAsync (Where ids.Contains)' 10000 62.968 ms 38.6487 ms 30.1744 ms 52.352 ms 1.11 0.54 - 715.09 KB 3.38
'ARX DeleteAsync / DeleteMultipleAsync' 10000 55.393 ms 7.0285 ms 5.4874 ms 54.809 ms 0.98 0.17 - 575.36 KB 2.72
2026-11-09: full run Execute.ExecuteBenchmarks

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]   : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  Standard : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=Standard  IterationCount=8  LaunchCount=1  
WarmupCount=3  

Method Categories Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
'Raw CommandType.StoredProcedure, reader + OUTPUT' Procedure 1 3.246 ms 0.6506 ms 0.3403 ms 1.01 0.14 - - 4.88 KB 1.00
'Dapper QueryAsync + DynamicParameters output' Procedure 1 2.963 ms 0.1834 ms 0.0814 ms 0.92 0.09 - - 6.39 KB 1.31
'ARX ExecuteAsync (procedure)' Procedure 1 3.242 ms 0.5159 ms 0.2291 ms 1.01 0.12 - - 12.34 KB 2.53
'Raw CommandType.StoredProcedure, reader + OUTPUT' Procedure 100 3.024 ms 0.7453 ms 0.3309 ms 1.01 0.14 - - 9.02 KB 1.00
'Dapper QueryAsync + DynamicParameters output' Procedure 100 2.964 ms 0.2397 ms 0.1064 ms 0.99 0.09 3.9063 - 24.69 KB 2.74
'ARX ExecuteAsync (procedure)' Procedure 100 3.280 ms 0.5275 ms 0.2759 ms 1.09 0.13 7.8125 - 43.98 KB 4.87
'Raw CommandType.StoredProcedure, reader + OUTPUT' Procedure 1000 4.063 ms 0.5413 ms 0.2831 ms 1.00 0.09 15.6250 - 71.4 KB 1.00
'Dapper QueryAsync + DynamicParameters output' Procedure 1000 4.044 ms 0.2918 ms 0.1295 ms 1.00 0.07 39.0625 7.8125 186.45 KB 2.61
'ARX ExecuteAsync (procedure)' Procedure 1000 4.776 ms 1.0221 ms 0.5346 ms 1.18 0.15 62.5000 15.6250 352.67 KB 4.94
'Raw SELECT dbo.fn(@a, @b)' Scalar function 1 1.508 ms 0.3528 ms 0.1845 ms 1.01 0.16 - - 4.96 KB 1.00
'ARX ExecuteAsync (scalar function, known scalar)' Scalar function 1 1.473 ms 0.0946 ms 0.0337 ms 0.99 0.11 - - 14.76 KB 2.98
'Raw SELECT dbo.fn(@a, @b)' Scalar function 100 1.430 ms 0.0934 ms 0.0415 ms 1.00 0.04 - - 4.96 KB 1.00
'ARX ExecuteAsync (scalar function, known scalar)' Scalar function 100 1.810 ms 0.6285 ms 0.3287 ms 1.27 0.22 - - 14.76 KB 2.98
'Raw SELECT dbo.fn(@a, @b)' Scalar function 1000 1.447 ms 0.1138 ms 0.0595 ms 1.00 0.05 - - 4.96 KB 1.00
'ARX ExecuteAsync (scalar function, known scalar)' Scalar function 1000 1.713 ms 0.6749 ms 0.3530 ms 1.19 0.23 - - 14.76 KB 2.98
2026-11-09: full run Retrieve.RetrieveBenchmarks

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]   : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  Standard : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=Standard  IterationCount=8  LaunchCount=1  
WarmupCount=3  

Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
'Raw SqlDataReader by id' 1.465 ms 0.1485 ms 0.0659 ms 1.00 0.06 - 7.48 KB 1.00
'Dapper QuerySingle<T>' 1.435 ms 0.1083 ms 0.0567 ms 0.98 0.05 - 7.91 KB 1.06
'EF Core AsNoTracking FirstAsync' 2.119 ms 0.4622 ms 0.2417 ms 1.45 0.17 15.6250 83.04 KB 11.11
'ARX RetrieveAsync by id' 1.600 ms 0.2123 ms 0.1111 ms 1.09 0.08 - 10.45 KB 1.40
2026-11-09: full run Retrieve.RetrieveMultipleBenchmarks

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]   : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  Standard : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=Standard  IterationCount=8  LaunchCount=1  
WarmupCount=3  

Method Categories Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
'Raw SqlDataReader, id range' **** 100 1.965 ms 0.4714 ms 0.2465 ms 1.01 0.17 7.8125 - - 36 KB 1.00
'Dapper Query<T>' 100 2.762 ms 1.4621 ms 0.7647 ms 1.42 0.41 7.8125 - - 47.77 KB 1.33
'EF Core AsNoTracking ToListAsync' 100 2.826 ms 0.7908 ms 0.4136 ms 1.46 0.26 27.3438 3.9063 - 135.79 KB 3.77
'ARX RetrieveMultipleAsync' 100 1.883 ms 0.2559 ms 0.1339 ms 0.97 0.13 15.6250 - - 85.48 KB 2.37
'Raw SqlDataReader, id range' **** 1000 4.005 ms 0.2886 ms 0.1029 ms 1.00 0.03 62.5000 15.6250 - 307.51 KB 1.00
'Dapper Query<T>' 1000 4.967 ms 1.3939 ms 0.7291 ms 1.24 0.17 85.9375 23.4375 - 406.45 KB 1.32
'EF Core AsNoTracking ToListAsync' 1000 5.154 ms 0.8229 ms 0.3654 ms 1.29 0.09 125.0000 31.2500 - 581.63 KB 1.89
'ARX RetrieveMultipleAsync' 1000 5.822 ms 1.6390 ms 0.7277 ms 1.45 0.17 132.8125 70.3125 - 729.66 KB 2.37
'Raw SqlDataReader, id range' **** 10000 34.337 ms 2.9009 ms 1.2880 ms 1.00 0.05 500.0000 285.7143 142.8571 3176.05 KB 1.00
'Dapper Query<T>' 10000 64.421 ms 27.0459 ms 12.0086 ms 1.88 0.33 666.6667 166.6667 - 4101.41 KB 1.29
'EF Core AsNoTracking ToListAsync' 10000 61.003 ms 10.7037 ms 5.5983 ms 1.78 0.17 888.8889 333.3333 111.1111 5187.86 KB 1.63
'ARX RetrieveMultipleAsync' 10000 62.835 ms 11.5098 ms 6.0199 ms 1.83 0.18 1000.0000 333.3333 - 7325.21 KB 2.31
'Raw page of 50 + COUNT_BIG in one command' Page 100 2.728 ms 0.6278 ms 0.3284 ms ? ? - - - 11.11 KB ?
'ARX page of 50 with the total' Page 100 2.696 ms 0.6224 ms 0.3255 ms ? ? 7.8125 - - 48.59 KB ?
'Raw page of 50 + COUNT_BIG in one command' Page 1000 2.675 ms 0.9820 ms 0.5136 ms ? ? - - - 11.18 KB ?
'ARX page of 50 with the total' Page 1000 1.760 ms 0.1474 ms 0.0654 ms ? ? 7.8125 - - 49.09 KB ?
'Raw page of 50 + COUNT_BIG in one command' Page 10000 2.676 ms 0.1706 ms 0.0758 ms ? ? - - - 11.18 KB ?
'ARX page of 50 with the total' Page 10000 2.882 ms 0.1991 ms 0.0884 ms ? ? 7.8125 - - 49.09 KB ?
2026-11-09: full run Update.UpdateBenchmarks

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]   : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  Standard : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=Standard  IterationCount=8  LaunchCount=1  
WarmupCount=3  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
'Raw UPDATE … FROM OPENJSON (one row: plain UPDATE)' 1 2.776 ms 0.2486 ms 0.1104 ms 1.00 0.05 - - - 7.44 KB 1.00
'Dapper ExecuteAsync(sql, list): one statement per row' 1 2.758 ms 0.6379 ms 0.3336 ms 0.99 0.12 - - - 7.88 KB 1.06
'EF Core Attach, all modified, SaveChanges' 1 3.721 ms 0.7511 ms 0.3928 ms 1.34 0.14 15.6250 - - 93.45 KB 12.56
'ARX UpdateAsync / UpdateMultipleAsync' 1 2.667 ms 0.2983 ms 0.1560 ms 0.96 0.06 3.9063 - - 20.82 KB 2.80
'Raw UPDATE … FROM OPENJSON (one row: plain UPDATE)' 100 5.730 ms 0.6723 ms 0.2985 ms 1.00 0.07 7.8125 - - 55.44 KB 1.00
'Dapper ExecuteAsync(sql, list): one statement per row' 100 272.551 ms 50.2837 ms 26.2993 ms 47.68 4.93 - - - 455.01 KB 8.21
'EF Core Attach, all modified, SaveChanges' 100 25.031 ms 2.8022 ms 1.4656 ms 4.38 0.32 281.2500 156.2500 - 1484.94 KB 26.78
'ARX UpdateAsync / UpdateMultipleAsync' 100 6.227 ms 0.5707 ms 0.2534 ms 1.09 0.07 39.0625 - - 197.25 KB 3.56
'Raw UPDATE … FROM OPENJSON (one row: plain UPDATE)' 1000 23.225 ms 1.1770 ms 0.5226 ms 1.00 0.03 125.0000 125.0000 125.0000 505.06 KB 1.00
'Dapper ExecuteAsync(sql, list): one statement per row' 1000 2,519.392 ms 297.9777 ms 155.8481 ms 108.53 6.73 - - - 4519.06 KB 8.95
'EF Core Attach, all modified, SaveChanges' 1000 262.143 ms 20.6203 ms 9.1555 ms 11.29 0.44 2000.0000 1000.0000 - 14333.52 KB 28.38
'ARX UpdateAsync / UpdateMultipleAsync' 1000 27.762 ms 1.9036 ms 0.8452 ms 1.20 0.04 218.7500 187.5000 187.5000 1624.64 KB 3.22
'Raw UPDATE … FROM OPENJSON (one row: plain UPDATE)' 10000 260.837 ms 43.1129 ms 22.5489 ms 1.01 0.12 - - - 5095 KB 1.00
'Dapper ExecuteAsync(sql, list): one statement per row' 10000 25,701.101 ms 1,345.6910 ms 703.8224 ms 99.17 8.36 9000.0000 - - 45163 KB 8.86
'EF Core Attach, all modified, SaveChanges' 10000 1,958.254 ms 43.1085 ms 22.5466 ms 7.56 0.61 22000.0000 6000.0000 - 140732.24 KB 27.62
'ARX UpdateAsync / UpdateMultipleAsync' 10000 279.359 ms 55.8392 ms 29.2050 ms 1.08 0.14 1000.0000 - - 15764.65 KB 3.09
2026-11-09: delete after json list

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]          : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  SingleOperation : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=SingleOperation  InvocationCount=1  IterationCount=12  
LaunchCount=1  RunStrategy=Monitoring  UnrollFactor=1  
WarmupCount=1  

Method Rows Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 1 4.227 ms 0.4015 ms 0.3135 ms 4.159 ms 1.00 0.10 - 4.3 KB 1.00
'Dapper ExecuteAsync(DELETE … IN @ids)' 1 4.141 ms 0.5743 ms 0.4484 ms 4.224 ms 0.98 0.12 - 9.84 KB 2.29
'EF Core ExecuteDeleteAsync (Where ids.Contains)' 1 12.394 ms 29.5451 ms 23.0669 ms 5.743 ms 2.95 5.29 - 143.87 KB 33.48
'ARX DeleteAsync / DeleteMultipleAsync' 1 4.741 ms 2.7870 ms 2.1759 ms 4.316 ms 1.13 0.50 - 8.49 KB 1.98
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 100 5.893 ms 0.6621 ms 0.5169 ms 5.742 ms 1.01 0.11 - 43.22 KB 1.00
'Dapper ExecuteAsync(DELETE … IN @ids)' 100 6.117 ms 0.5990 ms 0.4677 ms 6.223 ms 1.04 0.11 - 46.21 KB 1.07
'EF Core ExecuteDeleteAsync (Where ids.Contains)' 100 15.082 ms 29.3712 ms 22.9311 ms 8.494 ms 2.58 3.78 - 957.52 KB 22.16
'ARX DeleteAsync / DeleteMultipleAsync' 100 4.871 ms 0.5796 ms 0.4525 ms 5.036 ms 0.83 0.10 - 30.48 KB 0.71
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 1000 179.936 ms 37.3685 ms 29.1749 ms 171.849 ms 1.02 0.21 - 429.41 KB 1.00
'Dapper ExecuteAsync(DELETE … IN @ids)' 1000 173.736 ms 23.5722 ms 18.4036 ms 167.053 ms 0.99 0.16 - 389.53 KB 0.91
'EF Core ExecuteDeleteAsync (Where ids.Contains)' 1000 264.382 ms 73.7299 ms 57.5634 ms 255.136 ms 1.50 0.37 15000.0000 71562.17 KB 166.65
'ARX DeleteAsync / DeleteMultipleAsync' 1000 11.450 ms 3.5477 ms 2.7698 ms 10.397 ms 0.06 0.02 - 86.73 KB 0.20
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 10000 48.520 ms 5.7111 ms 4.4588 ms 46.869 ms 1.01 0.12 - 211.43 KB 1.00
'Dapper ExecuteAsync(DELETE … IN @ids)' 10000 2,271.462 ms 168.7610 ms 131.7575 ms 2,277.816 ms 47.15 4.69 - 3762.84 KB 17.80
'EF Core ExecuteDeleteAsync (Where ids.Contains)' 10000 61.437 ms 40.4206 ms 31.5578 ms 50.312 ms 1.28 0.64 - 715.09 KB 3.38
'ARX DeleteAsync / DeleteMultipleAsync' 10000 52.294 ms 5.3645 ms 4.1882 ms 51.580 ms 1.09 0.12 - 575.36 KB 2.72
probes in list vs openjson

Probe: an IN list of parameters against one OPENJSON parameter (2026-11-09)

The source is in-list-vs-openjson.cs.txt (a console program with Microsoft.Data.SqlClient 7.1.1, run against arx_bench on arx-mssql). A 10,000-row table keyed by bigint; ids every third row. Medians of 10 runs after 2 warm-ups. SELECT runs with a cached plan; each DELETE follows a TRUNCATE and reload (as the benchmark's iteration setup), so its plan is recompiled, as it is when a table's statistics change.

Values SELECT, IN list SELECT, OPENJSON DELETE, IN list DELETE, OPENJSON
20 2.09 ms 1.99 ms 3.71 ms 4.07 ms
50 2.45 ms 2.33 ms 5.29 ms 4.96 ms
100 2.97 ms 2.76 ms 4.79 ms 4.00 ms
200 4.62 ms 2.85 ms 6.06 ms 4.73 ms
500 11.10 ms 4.38 ms 67.85 ms 6.22 ms
1,000 28.09 ms 5.07 ms 162.87 ms 9.39 ms
2,000 116.67 ms 9.56 ms 507.79 ms 13.53 ms

Level at 20 to 50 values; past that the IN list's cost climbs much faster than the list. Decision: lists from 50 values go as OPENJSON (JSON_MIN_LIST), for DeleteMultiple's ids and for Retrieve's In conditions. Confirmed by the Delete benchmark: 2026-11-09-delete-after-json-list.md.

2026-11-09: update after read committed

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]   : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  Standard : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=Standard  IterationCount=8  LaunchCount=1  
WarmupCount=3  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
'Raw UPDATE … FROM OPENJSON (one row: plain UPDATE)' 1 2.467 ms 0.1942 ms 0.0862 ms 1.00 0.05 - - - 7.44 KB 1.00
'ARX UpdateAsync / UpdateMultipleAsync' 1 2.862 ms 0.4251 ms 0.2223 ms 1.16 0.09 3.9063 - - 21.45 KB 2.88
'Raw UPDATE … FROM OPENJSON (one row: plain UPDATE)' 100 5.644 ms 0.3997 ms 0.1775 ms 1.00 0.04 7.8125 - - 55.43 KB 1.00
'ARX UpdateAsync / UpdateMultipleAsync' 100 6.137 ms 0.1583 ms 0.0828 ms 1.09 0.03 39.0625 - - 199.34 KB 3.60
'Raw UPDATE … FROM OPENJSON (one row: plain UPDATE)' 1000 24.207 ms 4.3628 ms 2.2818 ms 1.01 0.12 125.0000 125.0000 125.0000 504.79 KB 1.00
'ARX UpdateAsync / UpdateMultipleAsync' 1000 27.591 ms 1.6681 ms 0.8725 ms 1.15 0.10 218.7500 187.5000 187.5000 1626.76 KB 3.22
'Raw UPDATE … FROM OPENJSON (one row: plain UPDATE)' 10000 219.250 ms 19.6629 ms 8.7304 ms 1.00 0.05 - - - 5094.51 KB 1.00
'ARX UpdateAsync / UpdateMultipleAsync' 10000 268.612 ms 46.0340 ms 24.0767 ms 1.23 0.11 1000.0000 - - 15759.68 KB 3.09
2026-11-09: delete after read committed

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2)
Intel Core i7-8750H CPU 2.20GHz (Max: 2.21GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.403
  [Host]          : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
  SingleOperation : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=SingleOperation  InvocationCount=1  IterationCount=12  
LaunchCount=1  RunStrategy=Monitoring  UnrollFactor=1  
WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Allocated Alloc Ratio
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 1 4.200 ms 0.8484 ms 0.6624 ms 1.02 0.23 3.77 KB 1.00
'ARX DeleteAsync / DeleteMultipleAsync' 1 4.375 ms 1.6954 ms 1.3236 ms 1.07 0.36 8.73 KB 2.31
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 100 6.209 ms 0.6483 ms 0.5062 ms 1.01 0.11 39.65 KB 1.00
'ARX DeleteAsync / DeleteMultipleAsync' 100 5.224 ms 0.5193 ms 0.4054 ms 0.85 0.09 14.79 KB 0.37
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 1000 174.962 ms 29.0603 ms 22.6884 ms 1.01 0.17 397.77 KB 1.00
'ARX DeleteAsync / DeleteMultipleAsync' 1000 10.482 ms 1.3902 ms 1.0854 ms 0.06 0.01 71.05 KB 0.18
'Raw DELETE … IN (…) (past 2,000: OPENJSON)' 10000 50.426 ms 7.5546 ms 5.8981 ms 1.01 0.15 99.36 KB 1.00
'ARX DeleteAsync / DeleteMultipleAsync' 10000 58.566 ms 18.8074 ms 14.6836 ms 1.17 0.31 463.63 KB 4.67

Run them yourself

The benchmark code is published, so you can check these numbers on your own machine and database: arx-benchmark-mssql.zip. It builds against the ARX packages on nuget.org; HOW-TO-RUN.md inside says how to point it at a database and run it. Compare the ratios, not the absolute times.