← Home
.NETData accessReported by candidates

EF Core, Dapper or ADO.NET: what are the trade-offs, and when do you use each?

Asked at Infosys, Kellton, Fingent, MSC Technology, IDBI Intech and Sage IT, often as its ADO.NET parts (DataReader vs DataSet, ExecuteNonQuery); a 2026 IBM report too.

Say this first · about 30 seconds
Make it yours · Navistar month-end dealer report (invented example)
The code, simplified

Invented to show the shape, in C#. Tap Next to walk the October report.

Navistar.sln · where these 2 files sit
  • Reports/
Diagram · step 1The job asks for October's dealer report.
DealerReportJob.cs · line 1 · 1 of 2The month-end job runs this on 1 November for October.

Fix later in your repo The SQL lives in a C# constant; it could move to a versioned .sql file next to the migrations.

Say it out loud, then check

Show the 30-second answer
  • ADO.NET is the base layer: I open the connection, write the SQL and read every column myself. Full control, but a lot of code.
  • Dapper sits on top of it: I still write the SQL, and Dapper maps the rows to objects. I use it for heavy read queries and reports.
  • EF Core is a full ORM: I write LINQ, it writes the SQL, tracks changes and runs migrations. I use it for normal create, read, update and delete.
  • In our API we used EF Core for writes and Dapper for the slow reports, on the same connection.
Check your answer: tick what you said

You said 0 of 7

Then add your example: Navistar month-end dealer report (invented example)
  • Situation: at Navistar Financial the month-end dealer report joined contracts, payments and dealers with window functions the had tuned. Written in EF Core LINQ, it produced different, slower SQL and took 6 minutes.
  • What I did: I kept EF Core for the API's writes and moved only that report to Dapper, running the 's SQL as is, with parameters, on EF Core's own connection.
  • Result: the report ran in under a minute, the SQL was reviewable by the , and nothing else in the app changed.
  • What I'd say: I pick per query, not per project: EF Core by default, Dapper where I need exact SQL.

See it

1/6Before: the BMW FS statement API used plain ADO.NET. Every query: open, command, read each column.

In plain words

The senior answer · about 2 minutes
What each one does for you
  • ADO.NET is the bottom layer every .NET data library uses. You create a , a with your SQL and parameters, and read rows from a , column by column. Nothing is hidden, so nothing is surprising, but every query is 15 lines and a renamed column only fails when that line runs.
  • Dapper is a small library on top. You still write the SQL, but one call, , runs it and builds the objects for you by matching column names to property names. It's close to hand-written ADO.NET speed, with a fraction of the code.
  • EF Core is a full ORM, an object-relational mapper. You query with LINQ and it writes the SQL. It also tracks every object it loads, so you change properties and call , and it works out the UPDATE statements and runs them in one transaction. Migrations keep the database schema in step with the code.
Where EF Core costs you
  • Tracking: every loaded entity (an object EF maps to a table row) gets a snapshot, a copy of its values, so EF can spot changes. For a read-only screen of thousands of rows that's wasted memory and time. , plus into a small object with only the columns the screen needs, fixes most of it.
  • Hidden queries: a loop that touches a related list can send one query per row, the N+1 problem. That's the try-first bug below. I turn on EF's SQL logging in development and watch the dependency count in , so I see how many queries a request really sends.
  • Complex SQL: window functions (SQL that ranks or totals across rows), query hints or a report the tuned by hand don't fit LINQ well. There I write the SQL myself, with Dapper or with EF's .
How I choose
  • Default to EF Core for an API with normal create, read, update and delete: it's the fastest to build and the safest to change. Use Dapper for read-heavy reports and hot endpoints where I want exact SQL. Plain ADO.NET only for special cases: bulk copy with , or a library that can't take a dependency.
  • They aren't exclusive. Dapper works on EF Core's own connection, so one request can save with EF and read a report with Dapper inside the same transaction.
  • Whatever I pick, I measure before I switch. Most "EF is slow" problems I've seen were N+1 queries or loading whole entities, and were fixed without leaving EF.

All in one flow

Coming Not written yet: the card pass adds it.

The story

  1. Before

    BMW FS's statement API was written years ago in plain ADO.NET. Every query was a connection, a command, a reader and a loop that read each column by hand. Twenty queries meant twenty copies of that pattern.

  2. Why it had to change

    A renamed a column. The C# still compiled, because column names are just strings inside the code. The error came at run time, on the first statement run that night. And every new screen took a day of copy-paste mapping.

  3. How it works now

    The team moved everyday create, read, update and delete to EF Core: schema changes now go through EF migrations, so a rename happens on the C# property first and every query that uses the old name breaks the build instead of production; and saving changes is one call. Then the monthly statement run, 200,000 rows, got slow under EF's tracking. That one report moved to Dapper with SQL the tuned. All three still run on ADO.NET underneath; they differ in how much of it you write. The diagram walks it.

  4. What happens next

    EF Core for the API's normal work, Dapper for the heavy report, on the same connection. Plain ADO.NET stayed only for a bulk import that uses .

  5. What we gained

    Less code to write and review, mapping errors caught at build time, and the hot report as fast as hand-written SQL. The trap that's left is EF writing more queries than you think: the try-first exercise below.

In the lending API

EF Core: LINQ in, SQL out. Read-only, so no tracking, and only the columns the screen needs.

Only the key lines are shown; Deep walks the whole file.

Lending.sln · where this file sits
  • src/Infrastructure/Data/
ContractRepository.cs · line 4 · 1 of 4_db is the field, EF Core's door to the database. ASP.NET Core creates it per request and passes it to the repository's constructor (dependency injection).

The code, file by file

The same kind of read three ways in the lending API. Tap Next to walk the diagram with the code.

Lending.sln · where these 3 files sit
  • src/Infrastructure/
    • Data/
    • Reports/
Diagram · step 1Before: the BMW FS statement API used plain ADO.NET. Every query: open, command, read each column.
StatementReaderAdo.cs · line 2 · 1 of 7Called by the statement job for one contract.

What they ask next

The follow-ups interviews really ask. Answer out loud, then tap to check.

Follow-up tree

Answer, then open the next level. That is the order an interviewer digs in.

Reporteda candidate was asked Intervieweran interviewer asks it

Production bugs, and how to tell them

Each one: what you see, why, the fix in code, and the line to say.

Production incident

Not on this card A basic concept card: its production story is the try-first N+1 bug here; slow-query tuning has its own scenario card.

Traps

Traps

What should you say instead? Tap to check.

Live coding

Easy first, then harder. Set a timer and type it before you look.

Task 1

Reportedeasy · 10 minutes

Write a program that connects to a SQL database, runs a parameterised query, prints the rows, and disconnects.

Sage IT: write a program to connect to and disconnect from a SQL database
Stuck? Hints
  1. Which three ADO.NET types do you need?
  2. How do you make sure the connection is closed even if the query throws?
  3. Where does the value for the WHERE clause go, if not into the SQL string?
Show a solution
Lending.sln · where this file sits
Program.cs · line 2 · 1 of 5For .
What the interviewer checks
  • using (or await using) on connection, command and reader.
  • A parameter with its type (Add(..., SqlDbType.Decimal)), never string concatenation; can say why can hurt.
  • Async calls: , , .
  • Connection string from configuration, not hard-coded with a password.
  • Can say what "disconnect" really does: back to the pool.
With AI tools

Copilot writes EF Core fluently, and it writes N+1 loops just as fluently: an query inside a looks clean and passes small tests. It also suggests with string interpolation when asked for raw SQL. When I review AI data-access code I check for queries inside loops, on read-only paths, instead of whole entities, and parameters on every raw SQL call. Then I look at the SQL it really sends, in the EF logs.

Glossary: every name, one line each →

My notes

Paste the NOTES block from your doubt chat here. The grader never reads notes; the coach reads "Still unclear".

Read moreWhere it was asked