How do I handle Database Connections with Dapper in .NET?
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Dapper is lightweight because it works directly on top of ADO.NET connections instead of hiding them behind a heavy abstraction. That means connection handling is your responsibility, and good Dapper code depends on short-lived pooled connections, explicit transactions, and clear ownership of when a connection is opened and disposed.
The Core Rule: Use Short-Lived Connections
In most applications, you should not keep one open database connection around as a singleton. ADO.NET already provides connection pooling, so the normal Dapper pattern is:
- create a connection
- open it
- execute the query or command
- dispose it
Example:
Even though you create a new SqlConnection object per call, the underlying physical connection is usually reused from the pool.
Transactions Belong Around Multi-Step Writes
If one operation spans several statements, wrap them in a transaction and pass that transaction into each Dapper call:
Without this, partial writes are easy to create.
Centralize Connection Creation
In larger codebases, repeating new SqlConnection(...) everywhere makes it harder to change behavior consistently. A small factory helps:
This makes repositories easier to test and keeps connection setup consistent.
Let Connection Pooling Do Its Job
A common anti-pattern is trying to "optimize" by keeping one shared connection open. That usually creates threading and resilience problems instead of performance gains.
With Dapper, you normally want:
- short-lived connection objects
- a stable connection string
- pooling left to the provider
That is the path ADO.NET is already optimized for.
Parameterize Everything
Dapper makes parameterized SQL easy, and connection-handling discipline goes together with query discipline:
Do not build SQL by string concatenation. That creates correctness issues and opens the door to injection vulnerabilities.
Cancellation and Timeouts Matter
In web apps and background services, pass cancellation tokens through your commands. If the request has been canceled or the job is shutting down, the database call should not keep running unnecessarily.
Also remember that long-running queries are not a connection-management success story. If connections stay occupied for too long, the pool becomes a bottleneck.
Common Pitfalls
- Holding one open connection as a singleton service.
- Forgetting to dispose the connection and slowly exhausting the pool.
- Running several related write statements without a transaction.
- Building SQL by string concatenation instead of parameterization.
- Ignoring cancellation, timeouts, and query duration when diagnosing pool pressure.
Summary
- Use short-lived pooled connections and dispose them promptly.
- Wrap multi-step writes in explicit transactions.
- Centralize connection creation if the codebase is large enough to benefit from it.
- Let ADO.NET pooling handle reuse instead of keeping one global connection open.
- Good Dapper code is mostly disciplined ADO.NET code with better query mapping.
Related reading
- How do I import CSV file into a MySQL table?
- How do I insert a map into DynamoDB table?
- How do I install command line MySQL client on mac?
- How do I kill a process in MySQL running within Amazon RDS?
- How do I implement IEnumerableT
- How do I improve ASP.NET MVC application performance?
- How do I kill all the processes in Mysql show processlist?
- How do I limit the number of rows returned by an Oracle query after ordering?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.