We're starting a greenfield service and trying to decide whether to standardize on Dapper or EF Core. The team likes Dapper's speed and lack of abstraction, but I'm nervous about losing change tracking, migrations, and LINQ composability later on.
For a new ASP.NET Core API with moderate domain complexity — roughly 15 entities, some reporting endpoints, and a couple of CRUD screens — what would you actually pick today? I'd rather hear from folks who've lived through the maintenance pain of one or the other, not just benchmark numbers.
For a new ASP.NET Core API, I’d usually start with EF Core unless the team already knows it needs a more SQL-first approach. The choice is less about which tool is universally faster and more about how you want to build and maintain data access.
Where EF Core fits
EF Core is a good default when the API has a conventional relational model and you want features such as change tracking, LINQ queries, and schema migrations in one ecosystem. It helps with routine create, read, update, and delete work, and its conventions can make a new project quicker to get moving.
The tradeoff is that you need to understand what queries EF Core generates. Poorly shaped queries, loading too much related data, or overlooking database indexes can still hurt query performance. An ORM does not remove the need to know SQL.
Where Dapper fits
Dapper is appealing when you want to write and review raw SQL directly, especially for reporting, complex joins, or queries where exact database behavior matters. It stays lightweight and gives you clear control over the query, but you take on more of the mapping and data-access conventions yourself. Dapper also does not provide EF Core-style change tracking or migrations.
For a greenfield API, I’d choose EF Core for the main application data path, then use Dapper selectively if a measured bottleneck or a particularly SQL-heavy feature calls for it. If your team prefers explicit SQL and is comfortable owning that extra plumbing from day one, using Dapper throughout is reasonable too. Either way, keep data access behind a small, consistent boundary and make the choice based on the query patterns your API actually has.