Skip to main content

Decision Guide

Dapper ecosystem packages

Dapper keeps data access close to SQL and is often chosen for read-heavy or performance-critical paths. The secondary decision is how much convenience to add around core Dapper without hiding query intent or creating fragile conventions.

5decision criteria
3packages compared
5pre-launch checks

The decision to make

Should a .NET data layer use raw Dapper only or include Dapper extension packages?

  • Need for explicit SQL control versus convention-driven CRUD helpers.
  • Mapping complexity for multi-mapping, one-to-many hydration and custom type handlers.
  • Balance between low abstraction overhead and team productivity.
  • Transaction management, batching and provider portability requirements.
  • How much generated SQL you are willing to review and test explicitly.

Shortlist & Trade-offs

Curated starting points to compare. Always evaluate suitability against your specific project requirements.

1

Dapper

Core micro-ORM focused on fast materialization and minimal abstraction over SQL.

Inspect package
Best fit: Teams that want explicit SQL ownership, predictable query plans and thin data access layers.
Validate first: You must own SQL composition, migration strategy and relational modeling discipline yourself.
2

Dapper.Contrib

Adds simple CRUD helpers and attribute-based mapping conventions on top of Dapper.

Inspect package
Best fit: Internal services with straightforward entity CRUD where convenience outweighs advanced SQL control.
Validate first: Generic convenience APIs can become limiting for complex joins, projections or provider-specific SQL.
3

Dommel

Convention-based CRUD and expression support for Dapper-oriented repositories.

Inspect package
Best fit: Projects that want more repository ergonomics while still staying in the Dapper ecosystem.
Validate first: Convention assumptions and SQL generation should be validated against indexing and execution plans.

Practical implementation plan

  1. 1

    Keep SQL close to the feature that owns it and parameterize every value. Define small request/response records instead of mapping whole persistence entities by default.

  2. 2

    Open connections late and dispose them predictably; pass cancellation tokens through async calls. Use explicit transactions only for a clearly defined atomic boundary.

  3. 3

    Treat query plans as part of the implementation. Add indexes for measured access patterns, set command timeouts deliberately, and test mappings against the actual provider.

Risk

Common pitfalls

  • Building SQL with interpolation or concatenation instead of parameters, which creates injection risk and fragments database plan caches.
  • Hiding complex queries behind generic repositories until the SQL, transaction scope, and performance characteristics are impossible to see.
  • Assuming multi-mapping automatically resolves aggregates correctly; verify split columns and de-duplicate parent rows explicitly.

Checklist

Before production

  • All external values are bound as parameters, including filters and paging values.
  • Queries have cancellation, timeout, and error logging behavior appropriate to the request path.
  • Migration and schema ownership are explicit even though Dapper does not provide them.
  • High-volume queries have representative integration tests and execution-plan review.
  • Connection strings and database credentials use the deployment platform's secret store.

When to revisit this choice

Revisit your Dapper package mix when domain queries become more complex, write models grow, or query-generation conventions hide critical SQL details.