Is Entity Framework Core Quicker than Dapper?

10th September 2020

I want to see if Entity Framework Core has features that would make it quicker than Dapper.

Now, I've always used Entity Framework over Dapper, so I'm likely to be more biased in the former. However, it would take me some convincing to switch from Entity Framework to Dapper.

What are the Speed Differences between EF Core & Dapper?

One of the things that may convince me to switch to Dapper is speed.

To me, it's always been common perception that Dapper performs well when it comes to executing a query. This seemed to be the case when using Entity Framework on a ASP.NET Framework application.

In-fact, on Dapper's website, it states that it "owns the title of King of Micro ORM in terms of speed". A bold statement!

But Entity Framework has come along way in the last few years, particularly with .NET Core. Does Entity Framework Core perform better than Dapper when it comes to speed?

We are going to go ahead and perform some tests on both ORM's.

Setting up the Tests

We have set up the current database structure.

Entity Relationship Diagram (ERD) of a Blog

Now the blog table will hold 10,000 records. This is so the database is doing some work to return the results from the query.

We will conduct the following tests with both ORM's:

  • Get 10 records from the Blog table
  • Retrieve 10,000 records from the Blog table
  • Return 10,000 records from the Blog table, joining the BlogCategory and Category tables

Setting up is Quicker in Dapper

One of the things that Dapper does have the advantage of is setting up.

With Dapper, you need to install the Nuget package, create the entities you are querying against, then you're ready to perform the queries.

It's slightly longer with EF Core! You have to set up a DbContext, create the entities you are querying against, add each entity to your DbContext and map it to a table name.

Prepare for Our Tests

We are going to use a Console application to test our results. We will run a query six times for each ORM to get a sense of how well it performs.

However, for the first test, we won't display the result. This is because it often takes longer to get the results first time around as things need to be initialised.

Here are the entities we have set up. Take note that the [Table] attribute and the static OnModelCreating methods are for Entity Framework only!

// Blog.cs
[Table("Blog", Schema = "b")]
public class Blog
{
	public virtual int Id { get; set; }

	public virtual string Title { get; set; }

	public virtual string Article { get; set; }

	public virtual DateTimeOffset? Publish { get; set; }

	public virtual string Slug { get; set; }
	public ICollection<BlogCategory> BlogCategories { get; set; }


	public static void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<Blog>().HasKey(entity => entity.Id);
	}
}
// BlogCategory.cs
[Table("BlogCategory", Schema = "b")]
public partial class BlogCategory
{
	public virtual int Id { get; set; }

	public virtual int BlogId { get; set; }

	public virtual int CategoryId { get; set; }

	public virtual Blog Blog { get; set; }

	public virtual Category Category { get; set; }

	public static void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<BlogCategory>().HasKey(entity => entity.Id);

		modelBuilder.Entity<BlogCategory>()
			.HasOne(blogCategory => blogCategory.Blog)
			.WithMany(blog => blog.BlogCategories)
			.HasPrincipalKey(blog => blog.Id)
			.HasForeignKey(blogCategory => blogCategory.BlogId);

		modelBuilder.Entity<BlogCategory>()
			.HasOne(blogCategory => blogCategory.Category)
			.WithMany()
			.HasPrincipalKey(category => category.Id)
			.HasForeignKey(blog => blog.CategoryId);
	}
}
// Category.cs
[Table("Category", Schema = "b")]
public partial class Category
{
	public virtual int Id { get; set; }

	public virtual string Name { get; set; }

	public virtual string Slug { get; set; }

	public static void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<Category>().HasKey(entity => entity.Id);

		modelBuilder.Entity<Category>().Property(category => category.Name).HasMaxLength(200);
	}
}

Extra Work for Entity Framework

I said that there needed to be extra work to set up Entity Framework.

And here it is! We need to set up the DbContext. That includes adding the entities to our DbContext and setting up the connection to our SQL Server database.

// EfDbContext.cs
public partial class EfDbContext : DbContext
{
	public IConfigurationRoot _config;
	public EfDbContext()
	{
		_config = new ConfigurationBuilder().AddJsonFile(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\appsettings.json").Build();
	}

	protected override void OnModelCreating(ModelBuilder builder)
	{
		Blog.OnModelCreating(builder);
		BlogCategory.OnModelCreating(builder);
		Category.OnModelCreating(builder);
	}

	protected override void OnConfiguring(DbContextOptionsBuilder builder)
	{
		builder.UseSqlServer(_config.GetConnectionString("RoundTheCodeEfDapper"));
	}
}

Running Tests in a Console Application

Finally, we can set up our console application to perform the tests and display the results.

We will create separate functions for both Entity Framework and Dapper.

As stated above, we will run a test for each ORM 6 times, but exclude from displaying the first result.

// Program.cs
class Program
{
	static void Main(string[] args)
	{
		Console.WriteLine("Hello World!");

		EF();

		Dapper();

		Console.ReadKey();
	}

	static void Dapper()
	{
		var config = new ConfigurationBuilder().AddJsonFile(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\appsettings.json").Build();

		using (var conn = new SqlConnection(config.GetConnectionString("RoundTheCodeEfDapper")))
		{
			for (var tt = 1; tt <= 6; tt++)
			{
				var stopwatch = new Stopwatch();
				stopwatch.Start();
				// QUERY HERE
				stopwatch.Stop();

				if (tt >= 2)
				{
					Console.WriteLine("Dapper - The query ran in " + stopwatch.Elapsed.TotalSeconds.ToString("0.000"));
				}
			}
		}
	}

	static void EF()
	{
		using (var context = new EfDbContext())
		{
			for (var tt = 1; tt <= 6; tt++)
			{
				var stopwatch = new Stopwatch();
				stopwatch.Start();
				// QUERY HERE
				stopwatch.Stop();

				if (tt >= 2)
				{
					Console.WriteLine("EF - The query ran in " + stopwatch.Elapsed.TotalSeconds.ToString("0.000"));
				}
			}
		}
	}
}

Who Comes Out on Top? EF Core or Dapper?

Now that we have set up our application, we can perform our tests.

That's start it off nice and easy.

Get 10 Records from the Blog Table

Relatively easy to start off with! Get 10 records from the blog table.

The way we perform these queries in Entity Framework and Dapper are as follows:

// Entity Framework
var blogArticles = context.Set<Blog>().Take(10).ToList();
// Dapper
var blogArticles = conn.Query<Blog>("SELECT TOP 10 [b].[Id], [b].[Article], [b].[Publish], [b].[Slug], [b].[Title] FROM [b].[Blog] [b]");

And here are the results:

TestEF Core (time in seconds)Dapper (time in seconds)
10.0080.013
20.0020.007
30.0010.001
40.0010.001
50.0010.001

Not much in that! Dapper is a little slower than EF Core for the first two tests, but the difference is negligible. So, onto the next test...

Retrieve 10,000 records from the Blog table

A slight adjustment is needed to the queries. As we already have 10,000 records in the Blog table, we now have to take off the limit for both queries.

// Entity Framework
var blogArticles = context.Set<Blog>().ToList();
// Dapper
var blogArticles = conn.Query<Blog>("SELECT [b].[Id], [b].[Article], [b].[Publish], [b].[Slug], [b].[Title] FROM [b].[Blog] [b]");

As you can see from the results below, Dapper's performance is starting to get sluggish. It's around a tenth of a second slower than Entity Framework Core.

In fact, on the last test, Dapper is over two tenths of a second slower than EF Core.

TestEF Core (time in seconds)Dapper (time in seconds)
10.2300.355
20.2210.350
30.2150.375
40.2140.368
50.2130.422

Return 10,000 records from the Blog table, joining the BlogCategory and Category tables

The final test is to join the BlogCategory and Category tables together. This is so we can get information about the categories that the blog article belongs to.

This is where Entity Framework outshines Dapper.

With Entity Framework Core, I only have to add an Include function into my query.

var blogArticles = context.Set<Blog>().Include("BlogCategories.Category").ToList();

This is because we have set up the relationship in our static OnModelCreating method.

However, we have not done that in Dapper. That means we need to code it so we get the same results.

var blogArticles = conn.Query<Blog, Category, Blog>(
                        "SELECT [b].[Id], [b].[Article], [b].[Publish], [b].[Slug], [b].[Title], .[Id], .[Name], .[Slug] " +
                        "FROM [b].[Blog] AS [b] " +
                        "LEFT JOIN (" +
                        "SELECT [bc].BlogId, .Id, c.[Name], c.[Slug] FROM [b].[BlogCategory] bc" +
                        " INNER JOIN [b].[Category] c ON bc.CategoryId=c.Id" +
                        ") AS c ON c.BlogId=b.Id",
                    (blog, category) =>
                    {
                        blog.BlogCategories = blog.BlogCategories ?? new List<BlogCategory>();
                        blog.BlogCategories.Add(new BlogCategory { BlogId = blog.Id, CategoryId = category.Id, Category = category });

                        return blog;
                    }, splitOn: "CategoryId, Id"
                    ).ToList();
                    blogArticles = blogArticles.GroupBy(p => p.Id).Select(g =>
                    {
                        var groupedBlogArticle = g.First();
                        groupedBlogArticle.BlogCategories = g.Select(p => p.BlogCategories.Single()).ToList();
                        return groupedBlogArticle;
                    }).ToList();

Not only do we have to get the query, we also need to do a seperate GroupBy function.

The problem we've got is that if we just performed the query, any blog article with two or more categories would get duplicated.

That would mean that we would have more than 10,000 results.

So is all this extra work worth it?

TestEF Core (time in seconds)Dapper (time in seconds)
12.1220.831
20.8650.832
30.8551.068
40.8670.912
50.8580.868

No is the answer! In the first test, EF Core took over two seconds! But then, it stabilised to around 0.8 seconds.

There are instances where Dapper is a tenth, or two tenths of a second slower than EF Core.

You can watch us perform these tests and the steps we took to setup these tests in the video below:

Is Entity Framework Core the Best Way to Go?

In my opinion yes. There are no speed benefits using Dapper over Entity Framework Core on the queries I wish to run.

Sure, it can take longer to set up Entity Framework Core. And this will be more complex with bigger solutions.

But you have to look at the benefits after that.

You don't need to write out your SQL Server statements in EF Core. That helps with SQL injection vulnerabilities.

But for more complicated SQL Server statements, you do have a choice of writing raw SQL in an Entity Framework Core execution.

With adding joins, it's a lot simpler writing a query in Entity Framework Core than in Dapper.

And we haven't talked about being able to track changes in Entity Framework Core.

For me it's a no brainer! It's Entity Framework Core!

Want More ASP.NET Core Coding Tutorials?

Subscribe to my YouTube channel to get more ASP.NET Core coding tutorials.

You'll get videos where I share my screen and implement a how-to guide on a topic related to ASP.NET Core.

You can expect to see videos from the following technologies:

  • Blazor
  • Web APIs
  • SQL Server
  • Entity Framework
  • SignalR
  • and many more...

By subscribing, you can get access to all my ASP.NET Core coding tutorials completely free!

And so you never miss out on a new video, you have the option to be notified every time a new video is published.

So what are you waiting for?

To subscribe, click here to view my YouTube channel, and click on the red "Subscribe" button.

David Grace

David Grace

I am a .NET developer, building web applications in .NET Framework and .NET Core with a SQL Server database.

Some of the .NET packages I have used include Entity Framework and MVC.

I've also used many JavaScript frameworks such as React and jQuery, and have experience building CSS in SASS.

Twitter Feed