Performance

For each ORM, we ran these tests several times with the same random seed and chose +/- the median results. The deviations of these results comparing to the most recent results are insignificant. The biggest deviation was measured for the tests "Scheme2-100-roots". 

Database Schemes

  1. Scheme1: 250,000 rows/independent-table, 200,000 rows/dependent-table
    • Big data with less relations - not so many joins, no so many additional collection rows to root rows in queries. 
  2. Scheme2: 25,000 rows/independent-table, 50,000 rows/dependent-table
    • Less data, many relations - many joins, many additional collection rows to root rows in queries. 
  3. Scheme3: 200,000 rows/independent-table, 300,000 rows/dependent-table
    • Big data, many relations... 

Test Types

  • The load tests itself has 2x5 iterations, each iteration takes 20 seconds. So, 5 iterations should be performed in 01:40 minutes (100 seconds). 
  • We also measure the real test timethat is usually longer, because of multitasking of a single computer that the tests are run on. Many of tasks has to be switched between from one to another, because of limited number of CPU threads. 
  • For every test, The first 5 iterations simulates 50 users while the last 5 iterations 100 users
  • Filtering is done by index range lookup of primary or foreign keys. 
  • None of the tests generates an empty query result. 

  1. Scheme1-100-roots
    • Generates heavy relation (join) queries for 100 roots upon the Scheme1Expected about 500 rows. 
  2. Scheme1-400-roots
    • Generates heavy relation (join) queries for 400 roots upon the Scheme1Expected about 1500 rows. 
  3. Scheme2-100-roots
    • Generates heavy relation (join) queries for 100 roots upon the Scheme2Expected about 1200 rows. 
  4. Scheme2-400-roots
    • Generates heavy relation (join) queries for 400 roots upon the Scheme2Expected about 7000 rows. 
  5. Scheme3-100-roots
    • Generates heavy relation (join) queries for 100 roots upon the Scheme3Expected about 900 rows. 
  6. Scheme3-400-roots
    • Generates heavy relation (join) queries for 400 roots upon the Scheme3Expected about 6000 rows. 

ORM Overview

Many developers use Dapper and critic Entity Framework Core for performance. Dapper declares in its Nuget package description the following: "A high performance Micro-ORM..." The only truth is, that it is micro-ORM, because the only thing it does is the mapping of columns from a flat single table SELECT into entity properties of the same name (and you have to define SQL by yourself). So, if it selects only a few data, it is surely faster. 

Entity Framework and D3ORM are also able to select only a few columns in queries... :-D

The developers should think about how to tune or simplify their queries due to performance issues rather, then searching for another ORM. Entity Framework and D3ORM are both high performance tools in this field. 

 

D3ORM

+

Entity aggregations, light layered architecture of a target project, quick development, support of own or generated entities, stored procedures, JSON serialization, DTOs, asynchronous and anonymous bulk operations. No nuget package dependencies. High performance - among other things, thanks to caching entities once aggregated for going through repeating columns much more faster and still consuming not much RAM... 

-

Learning new technology, poor support over the Internet, yet. Some claims on db schema design (no support of complex primary keys). All the versions so far contains obfuscated compilations and may be slightly slower than declared. No support of lazy loading (it is a bad proof of concept for loading data from a database and we want to keep your project clean). 

 

Dapper

+

Maps single table column names and values into entity or DTO properties. 

-

Does not aggregate entities, does not help with constructing SQL in any way, hard to maintain those SQL queries (plain text), consumes a lot of RAM according to data it selects, low performance... 

 

Entity Framework

+

Entity aggregations, support of complex primary keys, sophisticated query building using LINQ, wide support over the Internet, quick development, high performance, caching entities once aggregated. 

-

Some small impact on an architecture through all layers - less usable for well designed project (harder separation of queries and repositories and also queries and database connections), database first concept generated entities are hardly convertible into JSON in an automated way, enables beginners to build performance issue queries by an accident. 

 

NHibernate (DNF)

Since we were being giving parsing exceptions for such a complex queries we use in these tests after conversion from SQL -> HQL, this ORM is not included in the tests. We were only able to parse HQL and retrieve results for eager loading from not so complicated nested queries. 

+

Entity aggregations, support of complex primary keys, bulk operations, eager and lazy loading, many years of experience. 

-

Very hard to maintain: User have to maintain duplicitous information of mappings in xml (or in entity attributes using Fluent NHibernate) when entity changes (no generator), eager loading possible only with several queries or by one query using HQL, which is similar to maintain as SQL (plain text). Slow and architecture impacting development in most of the cases. HQL cannot be used for some more complex queries with nested selects - you might join collections in a flat way, that could mean an performance issue or inability to resolve some queries. Pure documentation, pure support of .NET 6.0 (i.e.: Fluent NHibernate is missing completely). 

 

Pure SQL

+

Full control of queries, direct and quick data fetching 1:1. 

-

No mapping between SQL table <-> entity, hard to maintain queries (plain text), no caching of repeating columns' data. 

 

Telerik OpenAccess (DNF)

Since this ORM is not supported for .NET 6.0, it has not been included in the tests. 

+

Uses LINQ. 

-

Heavy impact on any architecture using classes such as TrackedList or DataSetResultno support of eager loading - collections are loaded in a lazy way in separate queries into TrackedList - useful only for a few cases... every time you need to respond with a full result, the database is called many times. No support for .NET 6+. 

MySQL Performance Test Results

TransactionsRAMErrorsMultitaskingTransactionsRAMErrorsMultitaskingTransactionsRAMErrorsMultitaskingTransactionsRAMErrorsMultitasking
Scheme1 100r 50uD36473 128 0 00:02:21.85 DPR6092 222 0 00:02:24.05 EF66340 169 0 00:02:23.55 SQL6041 165 0 00:02:24.06
Scheme1 100r 100uD35888 128 0 00:02:34.26 DPR6277 222 0 00:02:35.39 EF66180 169 0 00:02:35.02 SQL5958 165 0 00:02:32.24
Scheme1 400r 50uD33821 534 0 00:02:21.40 DPR3677 1471 0 00:02:20.99 EF63084 1358 0 00:02:21.40 SQL3984 338 0 00:02:21.93
Scheme1 400r 100uD33831 534 0 00:02:31.77 DPR3617 1471 0 00:02:33.07 EF63440 1358 0 00:02:15.46 SQL3816 338 0 00:02:37.92
Scheme2 100r 50uD34040 467 0 00:02:19.26 DPR2092 3555 0 00:02:39.36 EF64378 411 0 00:02:17.86 SQL2963 613 0 00:02:28.92
Scheme2 100r 100uD33987 467 0 00:02:31.04 DPR1988 3555 0 00:02:58.21 EF63993 411 3 00:02:26.76 SQL2757 613 0 00:02:41.61
Scheme2 400r 50uD31422 476 0 00:02:43.59 DPR891 9994 0 00:05:36.88 EF61531 1287 0 00:02:37.71 SQL986 3808 0 00:03:28.59
Scheme2 400r 100uD31468 476 0 00:03:09.57 DPR1181 9994 47 00:07:58.10 EF61606 1287 0 00:02:58.87 SQL1122 3808 0 00:04:17.45
Scheme3 100r 50uD33119 198 0 00:02:35.04 DPR3402 581 0 00:02:29.23 EF63553 214 0 00:02:30.82 SQL2989 261 0 00:02:36.11
Scheme3 100r 100uD33711 198 0 00:02:41.78 DPR3278 581 0 00:02:38.15 EF63643 214 1 00:02:57.06 SQL3535 261 0 00:02:31.94
Scheme3 400r 50uD31486 202 51 00:03:48.31 DPR1416 2526 21 00:04:00.48 EF61562 354 20 00:03:22.18 SQL1341 655 37 00:03:50.27
Scheme3 400r 100uD31848 202 1 00:03:10.16 DPR358 2526 16 00:03:31.00 EF61640 354 3 00:03:06.01 SQL1640 655 50 00:03:57.70
OVERALLD341094 (1)314.17 (1)52 (2)00:02:44.00 (2)DPR352693058.1784 (3)00:03:28.71EF640750 (2)632.17 (2)27 (1)00:02:39.39 (1)SQL37082 (3)973.33 (3)8700:02:59.06 (3)

Overall TransactionsOverall TransactionsOverall TransactionsOverall TransactionsOverall TransactionsOverall TransactionsOverall Transactions

Benchmark

Benchmark computation include all the criteria in the following pattern. 

  • Benchmark = Transactions - RAM/4 - (Errors)*8 - (multitaskingIteration(seconds) - 140)*4

Overall Transactions