Dapper · Dapper.SimpleCRUD · EF Core

The same table and the same operations on every database: first the libraries on their own, then through an instant OData/REST API built on each. Lower is better. Generated 2026-09-30 21:50 UTC.

OS macOS 26.6.2arch Arm64cores 16.NET .NET 10.0.1Dapper 2.0.78Dapper.SimpleCRUD 2.3.0EF Core 10.0.0ez-odata-api 1.0.7EzOdata.SimpleCrud 2.0.1drivers Npgsql 10.0.0, MySqlConnector 2.3.7, SqlClient 6.1.1, Microsoft.Data.Sqlite 10.0.0databases SQLite, PostgreSQL, MySQL, SQL Server

The libraries on their own

No HTTP and no ez-odata: each library does the operation the way its documentation recommends, opening a pooled connection per operation as a web request would. Bars are the mean, whiskers the 99.9% confidence interval; on the right, the mean (with the ratio to Dapper) and the bytes allocated. Hover a bar for the median and sample count.

Dapper, hand-written SQLDapper.SimpleCRUD (EzOdata facade)Dapper.SimpleCRUD (static API)EF CoreEF Core, AsNoTracking

Get by id

SQLite
Dapper5.1 µs
3.1 KB
SimpleCRUD12.0 µs 2.35×
12.9 KB
SimpleCRUD static11.9 µs 2.32×
12.9 KB
EF Core18.3 µs 3.59×
12.1 KB
EF no-tracking18.6 µs 3.64×
12.6 KB
PostgreSQL
Dapper324 µs
3.5 KB
SimpleCRUD342 µs 1.06×
13.5 KB
EF Core358 µs 1.11×
9.3 KB
EF no-tracking349 µs 1.08×
9.8 KB
MySQL
Dapper606 µs
8.0 KB
SimpleCRUD584 µs 0.96×
17.5 KB
SQL Server
Dapper703 µs
8.0 KB
SimpleCRUD704 µs 1.00×
17.9 KB
EF Core724 µs 1.03×
14.1 KB
EF no-tracking741 µs 1.05×
14.6 KB

Filtered page (20 rows)

SQLite
Dapper18.1 µs
8.1 KB
SimpleCRUD26.0 µs 1.44×
19.2 KB
SimpleCRUD static25.7 µs 1.43×
19.2 KB
EF Core41.8 µs 2.32×
32.0 KB
EF no-tracking37.0 µs 2.05×
23.7 KB
PostgreSQL
Dapper418 µs
8.5 KB
SimpleCRUD413 µs 0.99×
19.7 KB
EF Core478 µs 1.14×
29.3 KB
EF no-tracking455 µs 1.09×
21.0 KB
MySQL
Dapper676 µs
13.0 KB
SimpleCRUD671 µs 0.99×
23.4 KB
SQL Server
Dapper750 µs
13.2 KB
SimpleCRUD788 µs 1.05×
25.7 KB
EF Core793 µs 1.06×
35.1 KB
EF no-tracking832 µs 1.11×
26.8 KB

Insert one

SQLite
Dapper44.5 µs
4.9 KB
SimpleCRUD50.5 µs 1.13×
8.4 KB
SimpleCRUD static49.5 µs 1.11×
8.4 KB
EF Core62.5 µs 1.41×
16.1 KB
PostgreSQL
Dapper335 µs
4.0 KB
SimpleCRUD344 µs 1.03×
7.2 KB
EF Core414 µs 1.23×
14.1 KB
MySQL
Dapper1.12 ms
6.6 KB
SimpleCRUD1.08 ms 0.97×
10.2 KB
SQL Server
Dapper1.12 ms
8.4 KB
SimpleCRUD1.16 ms 1.03×
11.9 KB
EF Core1.14 ms 1.02×
19.6 KB

Update one

SQLite
Dapper37.1 µs
3.7 KB
SimpleCRUD44.8 µs 1.21×
11.6 KB
SimpleCRUD static47.5 µs 1.28×
11.6 KB
EF Core55.0 µs 1.48×
16.0 KB
PostgreSQL
Dapper352 µs
4.4 KB
SimpleCRUD386 µs 1.10×
12.4 KB
EF Core385 µs 1.09×
14.0 KB
MySQL
Dapper1.07 ms
6.4 KB
SimpleCRUD1.11 ms 1.04×
14.1 KB
SQL Server
Dapper1.08 ms
7.3 KB
SimpleCRUD1.16 ms 1.08×
15.3 KB
EF Core1.14 ms 1.06×
19.3 KB

Insert + delete

SQLite
Dapper82.4 µs
6.7 KB
SimpleCRUD86.9 µs 1.05×
12.9 KB
SimpleCRUD static89.0 µs 1.08×
12.9 KB
EF Core109 µs 1.32×
29.4 KB
PostgreSQL
Dapper669 µs
6.3 KB
SimpleCRUD713 µs 1.07×
12.2 KB
EF Core721 µs 1.08×
24.6 KB
MySQL
Dapper2.06 ms
11.7 KB
SimpleCRUD2.07 ms 1.00×
18.0 KB
SQL Server
Dapper2.13 ms
13.5 KB
SimpleCRUD2.32 ms 1.09×
19.5 KB
EF Core2.28 ms 1.07×
35.0 KB

Insert 100 (one transaction)

SQLite
Dapper318 µs
185.7 KB
SimpleCRUD979 µs 3.08×
750.7 KB
SimpleCRUD static975 µs 3.06×
744.4 KB
EF Core1.10 ms 3.46×
923.1 KB
PostgreSQL
Dapper31.40 ms
197.4 KB
SimpleCRUD33.44 ms 1.06×
553.0 KB
EF Core2.63 ms 0.08×
768.7 KB
MySQL
Dapper23.70 ms
262.7 KB
SimpleCRUD24.91 ms 1.05×
762.3 KB
SQL Server
Dapper64.20 ms
404.0 KB
SimpleCRUD67.99 ms 1.06×
1.04 MB
EF Core6.02 ms 0.09×
780.3 KB

Through the HTTP API

An in-memory TestServer per engine: stock ez-odata (untyped), the SimpleCRUD engine and the EF Core engine (typed entities, hooks, a transaction per write). For each database and scenario the engines take turns request by request after a shared warm-up. Median / p95 in ms; bytes allocated per request across the whole process.

stock ez-odataSimpleCRUD engineEF Core engine

SQLite

scenariostocksimplecrudefcore
post0.53 / 0.74
56.5 KB
0.56 / 0.76
64.8 KB
0.76 / 1.06
128.8 KB
get-by-key0.14 / 0.21
53.1 KB
0.14 / 0.18
54.9 KB
0.15 / 0.19
55.2 KB
list0.25 / 0.33
98.2 KB
0.26 / 0.34
99.0 KB
0.25 / 0.33
99.1 KB
expand0.22 / 0.42
80.7 KB
0.23 / 0.44
83.0 KB
0.22 / 0.36
82.6 KB
patch0.49 / 0.94
55.8 KB
0.55 / 0.97
79.9 KB
0.80 / 1.38
141.0 KB
put0.19 / 0.26
58.3 KB
0.22 / 0.30
80.1 KB
0.35 / 0.49
132.7 KB
delete-post0.87 / 1.40
89.3 KB
0.93 / 1.42
117.1 KB
1.31 / 2.36
243.4 KB
concurrent-161659.49 / 2114.41
921.7 KB
1516.28 / 2113.67
1.02 MB
1667.12 / 2115.38
2.00 MB
hook-audit—0.30 / 0.45
68.0 KB
0.38 / 0.56
137.5 KB
direct-insert—0.31 / 0.39
7.8 KB
0.35 / 0.47
70.5 KB

PostgreSQL

scenariostocksimplecrudefcore
post1.00 / 1.26
60.8 KB
1.41 / 1.86
69.5 KB
2.22 / 2.81
131.1 KB
get-by-key0.56 / 0.70
59.3 KB
0.57 / 0.75
61.5 KB
0.57 / 0.72
61.0 KB
list0.72 / 0.83
103.8 KB
0.73 / 0.84
105.1 KB
0.72 / 0.85
105.6 KB
expand1.09 / 1.74
91.3 KB
1.09 / 1.82
93.3 KB
1.08 / 1.66
92.7 KB
patch0.89 / 1.36
59.9 KB
1.65 / 2.63
84.2 KB
2.58 / 3.86
142.4 KB
put0.79 / 0.97
62.1 KB
1.49 / 1.88
84.9 KB
1.31 / 1.63
129.9 KB
delete-post1.55 / 1.89
98.5 KB
2.28 / 2.94
125.5 KB
3.72 / 4.42
242.3 KB
concurrent-161.91 / 3.65
1.11 MB
2.60 / 4.64
1.19 MB
4.62 / 7.60
2.11 MB
hook-audit—1.95 / 3.25
71.1 KB
3.62 / 6.23
142.1 KB
direct-insert—0.39 / 0.53
6.2 KB
0.44 / 0.62
62.4 KB

MySQL

scenariostocksimplecrudefcore
post1.98 / 2.43
69.6 KB
2.04 / 2.48
73.2 KB
—
get-by-key0.82 / 0.97
61.0 KB
0.83 / 0.97
62.5 KB
—
list1.03 / 1.43
105.9 KB
1.04 / 1.46
106.3 KB
—
expand1.58 / 1.87
93.2 KB
1.59 / 1.91
96.2 KB
—
patch1.87 / 3.19
67.8 KB
2.24 / 3.88
90.1 KB
—
put1.55 / 2.24
70.0 KB
1.90 / 2.96
90.5 KB
—
delete-post3.52 / 7.14
106.9 KB
3.97 / 9.21
133.5 KB
—
concurrent-164.62 / 6.80
1.13 MB
4.65 / 7.15
1.21 MB
—
hook-audit—2.63 / 3.41
76.3 KB
—
direct-insert—1.11 / 1.43
9.1 KB
—

SQL Server

scenariostocksimplecrudefcore
post2.61 / 4.99
64.4 KB
3.30 / 6.22
75.5 KB
4.27 / 7.69
173.2 KB
get-by-key0.82 / 1.34
59.8 KB
0.83 / 1.45
61.5 KB
0.84 / 1.40
60.9 KB
list0.97 / 1.27
104.5 KB
0.99 / 1.17
104.2 KB
0.98 / 1.28
104.9 KB
expand1.52 / 1.77
92.1 KB
1.51 / 1.79
94.1 KB
1.52 / 1.84
93.9 KB
patch2.47 / 2.89
63.4 KB
3.81 / 4.41
94.8 KB
4.77 / 5.50
187.6 KB
put2.50 / 3.59
65.4 KB
3.75 / 5.53
95.4 KB
2.90 / 3.90
170.5 KB
delete-post5.10 / 6.49
101.8 KB
6.60 / 8.94
137.9 KB
8.50 / 10.81
326.2 KB
concurrent-168.97 / 16.03
1.07 MB
11.62 / 20.28
1.23 MB
14.77 / 26.40
2.72 MB
hook-audit—4.23 / 5.39
82.7 KB
5.85 / 7.67
189.9 KB
direct-insert—1.22 / 1.45
10.9 KB
1.32 / 1.58
101.6 KB

Tests

grouppassedfailedskippedtime
EF Core engine suites18002.2 s
facade, engines and API35003.7 s
SimpleCRUD engine suites18001.9 s
Every test
EzOdata.Entities.AspNetCore.Tests.EfCore_CompositeKeyTests.Composite_keys_work_end_to_endPassed215 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_MultiDialectTests.Every_dialect_behind_one_api_in_one_processPassed1539 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_PropertyNameTests.Entity_property_names_become_the_api_contractPassed209 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_PropertyNameTests.Row_filters_and_expand_follow_the_renamed_columnsPassed2 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Columns_the_entity_does_not_write_are_read_only_in_the_apiPassed5 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Database_constraint_violations_map_to_the_engines_error_codesPassed4 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Delete_can_be_overridden_as_a_soft_deletePassed74 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Expand_and_after_read_hooks_work_on_extended_tablesPassed2 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Handlers_can_forbid_an_update_based_on_the_original_rowPassed5 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Hook_rejection_is_a_400_and_nothing_is_writtenPassed2 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Insert_outside_the_callers_row_filter_is_403_and_rolled_backPassed2 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Insert_runs_through_the_engine_and_the_handlerPassed12 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.OnCommitted_runs_once_after_commit_and_never_after_rollbackPassed86 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Patch_changes_only_what_was_sentPassed3 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Reads_apply_role_row_filters_and_handler_filters_togetherPassed2 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Row_filters_protect_updates_and_deletesPassed11 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Side_writes_in_hooks_roll_back_with_the_api_writePassed5 ms
EzOdata.Entities.AspNetCore.Tests.EfCore_SqliteEndToEndTests.Tables_without_an_entity_keep_the_stock_instant_apiPassed4 ms
EzOdata.Entities.AspNetCore.Tests.EfCoreGuardTests.Inheritance_mapping_is_rejectedPassed16 ms
EzOdata.Entities.AspNetCore.Tests.EfCoreGuardTests.Owned_types_are_rejectedPassed75 ms
EzOdata.Entities.AspNetCore.Tests.EfCoreGuardTests.Retrying_execution_strategy_is_rejectedPassed559 ms
EzOdata.Entities.AspNetCore.Tests.EfCoreGuardTests.Unsupported_key_types_are_rejectedPassed6 ms
EzOdata.Entities.AspNetCore.Tests.EfCoreGuardTests.Value_converters_produce_a_startup_warningPassed25 ms
EzOdata.Entities.AspNetCore.Tests.EscapeHatchTests.EfCore_escape_hatch_shares_the_write_transactionPassed201 ms
EzOdata.Entities.AspNetCore.Tests.EscapeHatchTests.SimpleCRUD_escape_hatch_shares_the_write_transactionPassed483 ms
EzOdata.Entities.AspNetCore.Tests.OpenApiTests.Openapi_server_urls_point_at_the_servicePassed493 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_CompositeKeyTests.Composite_keys_work_end_to_endPassed165 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_MultiDialectTests.Every_dialect_behind_one_api_in_one_processPassed1295 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_PropertyNameTests.Entity_property_names_become_the_api_contractPassed167 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_PropertyNameTests.Row_filters_and_expand_follow_the_renamed_columnsPassed37 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Columns_the_entity_does_not_write_are_read_only_in_the_apiPassed9 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Database_constraint_violations_map_to_the_engines_error_codesPassed3 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Delete_can_be_overridden_as_a_soft_deletePassed37 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Expand_and_after_read_hooks_work_on_extended_tablesPassed3 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Handlers_can_forbid_an_update_based_on_the_original_rowPassed6 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Hook_rejection_is_a_400_and_nothing_is_writtenPassed2 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Insert_outside_the_callers_row_filter_is_403_and_rolled_backPassed4 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Insert_runs_through_the_engine_and_the_handlerPassed4 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.OnCommitted_runs_once_after_commit_and_never_after_rollbackPassed8 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Patch_changes_only_what_was_sentPassed7 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Reads_apply_role_row_filters_and_handler_filters_togetherPassed2 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Row_filters_protect_updates_and_deletesPassed3 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Side_writes_in_hooks_roll_back_with_the_api_writePassed11 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrud_SqliteEndToEndTests.Tables_without_an_entity_keep_the_stock_instant_apiPassed140 ms
EzOdata.Entities.AspNetCore.Tests.SimpleCrudNamingTests.UseNaming_maps_snake_case_tablesPassed475 ms
EzOdata.Entities.AspNetCore.Tests.StartupValidationTests.An_entity_that_does_not_match_the_database_stops_startupPassed358 ms
EzOdata.Entities.AspNetCore.Tests.StartupValidationTests.Composite_key_parts_must_be_Required_for_SimpleCRUDPassed8 ms
EzOdata.Entities.AspNetCore.Tests.StartupValidationTests.ExtendEzOData_requires_AddEzOData_firstPassed1 ms
EzOdata.Entities.AspNetCore.Tests.StartupValidationTests.Missing_engine_stops_startupPassed5 ms
EzOdata.SimpleCrud.Tests.EngineTests.An_incompatible_SimpleCRUD_is_rejected_with_a_clear_messagePassed0 ms
EzOdata.SimpleCrud.Tests.EngineTests.Delegates_are_compiled_once_per_operation_and_typePassed5 ms
EzOdata.SimpleCrud.Tests.EngineTests.Describe_reads_the_mapping_from_SimpleCRUD_itselfPassed1 ms
EzOdata.SimpleCrud.Tests.EngineTests.Each_engine_emits_its_own_dialect_in_one_process(dialect: MySQL, table: "`customers`", id: "`Id`", paging: "LIMIT 0,10")Passed14 ms
EzOdata.SimpleCrud.Tests.EngineTests.Each_engine_emits_its_own_dialect_in_one_process(dialect: PostgreSQL, table: "\"customers\"", id: "\"Id\"", paging: "LIMIT 10 OFFSET ((1-1) * 10)")Passed69 ms
EzOdata.SimpleCrud.Tests.EngineTests.Each_engine_emits_its_own_dialect_in_one_process(dialect: SQLite, table: "\"customers\"", id: "\"Id\"", paging: "LIMIT 10 OFFSET ((1-1) * 10)")Passed9 ms
EzOdata.SimpleCrud.Tests.EngineTests.Each_engine_emits_its_own_dialect_in_one_process(dialect: SQLServer, table: "[customers]", id: "[Id]", paging: "ROW_NUMBER() OVER(ORDER BY full_name)")Passed14 ms
EzOdata.SimpleCrud.Tests.EngineTests.Engines_are_lazy_singletons_per_dialectPassed0 ms
EzOdata.SimpleCrud.Tests.EngineTests.Exceptions_surface_unwrappedPassed4 ms
EzOdata.SimpleCrud.Tests.EngineTests.First_use_under_contention_creates_exactly_one_enginePassed4 ms
EzOdata.SimpleCrud.Tests.EngineTests.Isolated_engines_never_touch_the_process_wide_SimpleCRUDPassed7 ms
EzOdata.SimpleCrud.Tests.EngineTests.Keyed_DI_registrations_resolve_independent_dialectsPassed12 ms
EzOdata.SimpleCrud.Tests.EngineTests.Naming_conventions_are_per_enginePassed17 ms
EzOdata.SimpleCrud.Tests.EngineTests.Overhead_versus_calling_SimpleCRUD_directlyPassed348 ms
EzOdata.SimpleCrud.Tests.EngineTests.Sessions_commit_and_roll_backPassed11 ms
EzOdata.SimpleCrud.Tests.EngineTests.Several_custom_naming_engines_coexistPassed14 ms
EzOdata.SimpleCrud.Tests.EngineTests.Shared_engine_never_changes_global_state_implicitlyPassed1 ms
EzOdata.SimpleCrud.Tests.EngineTests.Why_isolation_is_needed_switching_SimpleCRUDs_dialect_leaves_stale_sqlPassed10 ms
EzOdata.SimpleCrud.Tests.MultiDatabaseTests.Every_database_at_once_in_one_processPassed71 ms
EzOdata.SimpleCrud.Tests.MultiDatabaseTests.Full_crud_through_the_facade(kind: "mysql")Passed117 ms
EzOdata.SimpleCrud.Tests.MultiDatabaseTests.Full_crud_through_the_facade(kind: "postgresql")Passed126 ms
EzOdata.SimpleCrud.Tests.MultiDatabaseTests.Full_crud_through_the_facade(kind: "sqlserver")Passed107 ms

How this was measured

Raw results: raw/ (BenchmarkDotNet tables and HTTP timings per database).