-
Notifications
You must be signed in to change notification settings - Fork 0
Merlin Vs Dapper
- Runtime: .NET 10.0.10
- CPU: Intel Core i7-8086K @ 4.00GHz
- OS: Windows 11
- Dataset: 100,000 rows
- Database: MySQL
- Benchmark: BenchmarkDotNet
- GC: Concurrent Workstation
Scenario: 100,000 User (11x Properties) records with no nested objects
| Metric | Dapper | Merlin | Difference |
|---|---|---|---|
| Mean Execution Time | 349.5 ms | 422.6 ms | +73.1 ms |
| Relative Performance | 1.00x | 1.21x | 21% slower |
| Allocated Memory | 169.72 MB | 190.32 MB | +20.60 MB |
| Allocation Ratio | 1.00x | 1.12x | 12% more |
| Gen0 Collections | 29,000 | 34,000 | +17% |
| Gen1 Collections | 9,000 | 10,000 | +11% |
| Gen2 Collections | 2,000 | 3,000 | +50% |
Merlin introduced approximately:
- 73 ms additional execution time
- 20 MB additional allocations
- ~21% execution overhead
- ~12% allocation overhead
Scenario: 100,000 User (11 properties) records with one nested Client object (11 Properties)
Object structure:
| Metric | Dapper | Merlin | Difference |
|---|---|---|---|
| Mean Execution Time | 726.6 ms | 891.6 ms | +165.0 ms |
| Relative Performance | 1.00x | 1.23x | 23% slower |
| Allocated Memory | 296.21 MB | 323.68 MB | +27.47 MB |
| Allocation Ratio | 1.00x | 1.09x | 9% more |
| Gen0 Collections | 50,000 | 56,000 | +12% |
| Gen1 Collections | 16,000 | 16,000 | No difference |
| Gen2 Collections | 1,000 | 3,000 | +200% |
Merlin introduced approximately:
- 165 ms additional execution time
- 27 MB additional allocations
- ~23% execution overhead
- ~9% allocation overhead
Dapper is faster, but not by much, the trade off is a simpler API
private static readonly QueryEngine DB = new("Local");
[Benchmark(Baseline = true)]
public int Test1_Dapper()
{
using IDbConnection connection = new MySqlConnection(ConStr);
string sql = "SELECT * FROM merlin_bench.users U LEFT JOIN merlin_bench.clients C ON U.user_client = C.client_id;";
var result = connection.Query<UserExtended, Client, UserExtended>( sql, map: (user, profile) => {
user.Client = profile;
return user;
},
param: new { },
splitOn: "client_id" // Tells Dapper where the Profile object fields begin
);
return result.AsList().Count();
}
[Benchmark]
public int Test2_Merlin()
{
var q = new GenericQuery("SELECT * FROM merlin_bench.users U LEFT JOIN merlin_bench.clients C ON U.user_client = C.client_id;");
var data = DB.GetList<UserExtended>(q);
return data.Count;
}
Merlin performs approximately 21% slower than Dapper while allocating only 12% more memory.
The overhead is primarily associated with:
- Metadata-driven mapping
- Property lookup
- Conversion handling
- Automatic object hydration
With a nested object, Merlin maintains nearly the same performance ratio:
| Test | Merlin Overhead |
|---|---|
| Flat Object | 21% slower |
| Nested Object | 23% slower |
This indicates that Merlin's object graph hydration does not introduce significant additional overhead as complexity increases.
The benchmark demonstrates that Merlin trades a small amount of runtime performance for increased mapping flexibility.
Dapper advantages:
- Maximum raw performance
- Minimal abstraction
- Very efficient simple object mapping
Merlin advantages:
- Automatic object hydration
- Nested object support
- Metadata-driven mapping
- Reduced manual mapping code
- Database abstraction without a full ORM tracking model
For business applications where maintainability and development speed are important, a ~20% performance