In this article, we will discuss the differences between EntityFramework Core and Dapper.

To download the source code for this article, you can visit our GitHub repository.

EntityFramework Core and Dapper are .NET libraries widely used for data access and management. Both are Object-Relational-Mappers (ORMs), so to gain a better understanding, let’s first dive into the concept of Object-Relational-Mapping.

Understanding Object-Relational-Mapping

Object-Relational Mapping is a technique we apply to facilitate the interaction between a database and an application. It achieves this by leveraging the object-oriented paradigm and providing an abstraction layer that translates our code into SQL. In simple terms, it allows us to work with databases using programming languages we are familiar with.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Object-Relational-Mappers are libraries specifically designed for this purpose.

EntityFramework Core

EF Core is a full-fledged ORM that provides a high-level abstraction over the database. It offers an extensive selection of APIs, which facilitate seamless data access and manipulation.

Let’s give an example of how EntityFramework Core works by retrieving a list of 1000 persons:

public List<Person> GetXPersonsEFCore(int personsNumber)
{
    return _context.Persons.Take(personsNumber).ToList();  
}

var personsRepository = new PersonsRepository(); 
personsRepository.GetXPersonsEFCore(1000);

Of course, this is just a simple example, and we are not going to dive deep into EF Core as that’s not the topic here. But, for a deep dive, you can check our series on EF Core.

Dapper

Dapper is a simple micro-ORM that provides fast and efficient data access as it executes raw SQL queries.

This time, we will execute the same query using Dapper:

public List<Person> GetXPersonsDapper(int personsNumber)
{
    _dbConnection.Open();

    return _dbConnection.Query<Person>($"SELECT TOP({personsNumber}) * FROM Persons").ToList();
}

var personsRepository = new PersonsRepository();
personsRepository.GetXPersonsDapper(1000);

Again, we are not going to deep dive into Dapper here, but feel free to check our article on Dapper to get deeper into this topic.

Dapper and Entity Framework Core Differences

Now that we have a general view of EF Core’s and Dapper’s basic mechanics let’s continue with their differences.

Flexibility and Features

Dapper empowers us to have fine-grained control over the SQL statements, by allowing us to write SQL statements ourselves. As a result, it enables optimization tailored to our specific needs. However, it is important to note that such a level of control requires us to ensure the security and correctness of the SQL statements we construct. We need to take precautions to ensure that we properly format our queries and optimize them for efficient database operations.

On the other hand, EF Core provides a higher-level, object-oriented approach to database operations. It allows us to focus on our application logic while minimizing the need for boilerplate code. Additionally, it offers a comprehensive range of features for efficient database schema management.

For example, EF Core supports migrations – a mechanism facilitating easy and seamless updates to the database structure. Furthermore, it provides support for both code-first and database-first approaches. This enables us to select the approach that best suits our specific requirements.

Learning Curve

Dapper offers a relatively low learning curve due to its reliance on raw SQL queries and simplicity. As a result, if we are familiar with SQL we can quickly grasp Dapper’s straightforward syntax and seamlessly integrate this ORM into our code.

Conversely, EF Core serves as a comprehensive ORM, demanding a steeper learning curve. Particularly for some of us who are new to developing, to comprehend the entire framework and its extensive functionalities might be difficult. However, in the long run, we can enhance our productivity with Entity Framework Core, benefiting from its diverse range of features.

Lastly, it is important to note that despite using EF Core, we should still possess SQL knowledge to ensure accurate and efficient implementations. 

Strong Typing

Dapper doesn’t provide strong typing as it uses object mapping to populate data into plain CLR objects (POCOs). We usually do the mapping through reflection or by specifying the mappings manually. While we can manually map query results to strongly typed objects, Dapper does not enforce or provide automatic strong typing.

EF Core generates strongly typed entity classes based on the database schema. These classes have properties with types that correspond to the underlying database columns. This provides strong typing when working with entities, helping to catch type-related errors at compile time.

Compile-Time Checking

Dapper relies on SQL queries, which we provide as plain strings. As a result, we discover any issues or errors in queries and mappings during runtime.

By comparison, EF Core provides a significant level of compile-time validation of query syntax and property mapping. This ensures the LINQ queries are syntactically correct and aligned with the entities schema. It’s important to note that dynamic query validation occurs during the evaluation and compilation of expression trees and anonymous methods in EntityFramework Core. As a result, runtime type mismatches, validation issues, and other types of exceptions can still occur in EF.

Performance

Dapper has gained widespread recognition for its good performance. It boasts lower memory usage and shorter execution times because it relies on raw SQL queries. Queries that we have already optimized.

In contrast, EF Core introduces a significant overhead due to its abstraction layer, as it automatically generates SQL statements, which may not always result in the most optimal approach. However, it offers several performance optimization techniques, including caching and change tracking, which can enhance overall performance.

With that said, let’s see and compare the execution times of these two ORMs for the retrieval of 10,000, 100,000, and 1,000,000 persons:

|            Method | personsNumber |       Mean |     Error |    StdDev |     Median |
|------------------ |-------------- |-----------:|----------:|----------:|-----------:|
| GetXPersonsDapper |         10000 |   9.456 ms | 0.6449 ms |  1.891 ms |   8.582 ms |
| GetXPersonsEFCore |         10000 |  33.976 ms | 1.0980 ms |  2.987 ms |  32.948 ms |
|                   |               |            |           |           |            |
| GetXPersonsDapper |        100000 | 108.760 ms | 2.1549 ms |  4.908 ms | 107.011 ms |
| GetXPersonsEFCore |        100000 | 439.645 ms | 8.7468 ms | 10.742 ms | 437.660 ms |
|                   |               |            |           |           |            |
| GetXPersonsDapper |       1000000 | 109.266 ms | 2.0619 ms |  3.330 ms | 103.615 ms |
| GetXPersonsEFCore |       1000000 | 440.308 ms | 7.8482 ms |  9.925 ms | 441.423 ms |

In these tests, dapper exhibits a noticeably higher performance than EF Core. That said, this is a simple example as there are many variables that can affect EF Core queries (as we already mentioned) and those results can be much faster.

So, let’s just see how using the AsNoTracking() method with EF Core query can change the results:

|            Method | personsNumber |       Mean |     Error |    StdDev |     Median |
|------------------ |-------------- |-----------:|----------:|----------:|-----------:|
| GetXPersonsDapper |         10000 |   9.456 ms | 0.6449 ms |  1.891 ms |   8.582 ms |
| GetXPersonsEFCore |         10000 |  12.762 ms | 1.1027 ms |  3.234 ms |  11.057 ms |
|                   |               |            |           |           |            |
| GetXPersonsDapper |        100000 | 108.760 ms | 2.1549 ms |  4.908 ms | 107.011 ms |
| GetXPersonsEFCore |        100000 | 130.625 ms | 2.5758 ms |  5.866 ms | 130.205 ms |
|                   |               |            |           |           |            |
| GetXPersonsDapper |       1000000 | 109.266 ms | 2.0619 ms |  3.330 ms | 103.615 ms |
| GetXPersonsEFCore |       1000000 | 134.120 ms | 3.0254 ms |  8.873 ms | 133.119 ms |

We can clearly see that by properly tracking (or in this case not tracking) changes can seriously affect the EF Core results. Even though Dapper still performs faster, now the results are much closer. 

What to Choose?

Now that we have gained a general understanding of the differences between EntityFramework Core and Dapper, let’s explore the scenarios in which each one excels.

When to Use Dapper

In scenarios where performance and resource management take precedence, we lean toward utilizing Dapper. This preference is especially evident when we possess advanced SQL knowledge and seek to optimize our queries accordingly. Moreover, Dapper finds an application in large databases where query execution time incurs significant costs.

Dapper’s lightweight design makes it a compelling option for applications that prioritize high-performance data retrieval. Moreover by bypassing the overhead of full-fledged ORMs, Dapper enables efficient and fast data mapping, resulting in improved application performance.

When to Use EF Core

In contrast, EF Core excels in scenarios where we focus on productivity, abstraction, and convenience. Swift application development and instances where we prioritize working with object-oriented paradigms find it to be a perfect fit. With its automated query generation, change tracking, and migration features, EF Core simplifies database interaction and schema updates.

Furthermore, EF Core’s proficiency in managing intricate relationships, and its compatibility across multiple database providers make it the ideal choice for applications with complex data modeling needs. Its comprehensive management capabilities ensure smooth operations and efficient handling of data, empowering developers to build robust and scalable applications.

Conclusion

In summary, in this article, we examined EntityFramework Core and Dapper and their basic mechanics, as well as provided a comprehensive understanding of these two popular .NET libraries. We also analyzed the two ORMs’ differences in their flexibility and features, learning curve, strong typing support, compile-time checking, and performance characteristics. Last but not least, we analyzed the circumstances in which we should use each library.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!