Dapper
Lightweight .NET micro-ORM that extends IDbConnection with convenience methods for SQL query execution and result mapping. Dapper adds: connection.QueryAsync<Agent>(sql, params) for typed query results, connection.ExecuteAsync(sql, params) for inserts/updates, connection.QueryFirstOrDefaultAsync<Agent>(sql, params), multi-mapping (JOIN results to multiple objects), QueryMultiple for multiple result sets, and stored procedure execution. Dapper maps SQL column names to C# property names automatically (case-insensitive). No migrations, no change tracking, no LINQ — just SQL + fast object mapping. StackOverflow was built with Dapper. Excellent for performance-critical agent data access paths and complex reporting queries.
Score Breakdown
⚙ Agent Friendliness
🔒 Security
Dapper does NOT protect against SQL injection if you use string interpolation — always use parameterized queries with anonymous objects or DynamicParameters. Store connection strings in Azure Key Vault or .NET User Secrets. Enable TLS for all agent database connections (Encrypt=True in SQL Server connection string).
⚡ Reliability
Best When
Your .NET agent service has performance-critical data access paths, complex reporting queries, or stored procedure integration where EF Core's generated SQL is insufficient — Dapper gives you full SQL control with minimal mapping boilerplate.
Avoid When
You need database migrations, you want LINQ queries, or your agent data access is simple CRUD where EF Core's convenience outweighs Dapper's performance.
Use Cases
- • High-performance agent reporting query — await connection.QueryAsync<AgentMetrics>("SELECT a.id, COUNT(t.id) as tool_calls FROM agents a LEFT JOIN tool_calls t ON a.id = t.agent_id GROUP BY a.id WHERE a.created_at > @since", new { since = DateTime.UtcNow.AddDays(-7) }) executes complex agent analytics without ORM overhead
- • Agent stored procedure execution — await connection.QueryAsync<Agent>("sp_GetActiveAgents", new { UserId = userId }, commandType: CommandType.StoredProcedure) calls stored procedure and maps results to Agent objects
- • Multi-table agent query with Dapper multi-mapping — connection.QueryAsync<Agent, Tool, Agent>(sql, (agent, tool) => { agent.Tools.Add(tool); return agent; }, splitOn: "ToolId") maps JOIN results to nested Agent objects
- • Bulk agent insert performance — await connection.ExecuteAsync("INSERT INTO agents (name, user_id) VALUES (@Name, @UserId)", agentList) batches multiple agent inserts efficiently via Dapper's enumerable parameter support
- • Mixed Dapper + EF Core agent repository — use EF Core for standard CRUD and Dapper for complex agent reporting queries where LINQ-generated SQL is suboptimal; both share same SqlConnection for agent transaction consistency
Not For
- • Schema migrations — Dapper has no migration system; use EF Core migrations, Flyway, or DbUp for agent database schema management alongside Dapper
- • Complex object graphs — Dapper requires explicit JOIN + multi-mapping for nested objects; EF Core's Include() is simpler for agent models with multiple levels of related entities
- • Teams who prefer no-SQL approach — Dapper requires writing SQL; for agent services where team doesn't want SQL, EF Core LINQ queries provide abstraction
Interface
Authentication
Dapper extends IDbConnection — auth via connection string. Use SQL Server integrated auth or connection string credentials. Azure Managed Identity supported via SqlConnection token configuration.
Pricing
Dapper is Apache 2.0 licensed, maintained by StackOverflow/DapperLib. Free for all use.
Agent Metadata
Known Gotchas
- ⚠ SQL injection via string interpolation — var agents = conn.Query<Agent>($"SELECT * FROM agents WHERE name = '{name}'") is SQL injection vulnerability; ALWAYS use parameterized queries: conn.QueryAsync<Agent>("SELECT * FROM agents WHERE name = @Name", new { Name = name }); Dapper parameterizes when using anonymous objects, never string interpolation
- ⚠ Column name mapping is case-insensitive but exact name match — Dapper maps SQL column 'agent_name' to C# property 'AgentName' only if using Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; without this setting, snake_case DB columns don't map to PascalCase C# agent properties; configure once at startup
- ⚠ Connection must be open or Dapper auto-opens — Dapper can open closed connections automatically but won't close them; for agent services managing connection lifetime manually, open connection before query and ensure disposal; connection leaks in agent services exhaust SQL Server connection pool
- ⚠ QueryMultiple disposable must be disposed — var multi = await conn.QueryMultipleAsync(sql, params); var agents = multi.Read<Agent>(); forgetting await multi.DisposeAsync() leaves server-side result set cursor open; wrap in using statement for agent multi-result queries to prevent cursor resource leaks
- ⚠ Dynamic parameters required for dynamic column agent queries — conn.QueryAsync<Agent>(sql, new DynamicParameters(dict)) for agent queries with runtime-determined parameters; anonymous objects compile-time only; DynamicParameters.Add(name, value, dbType) for typed parameter binding in agent dynamic query builders
- ⚠ Multi-mapping splitOn must match exact column name — QueryAsync with multi-mapping splitOn: 'ToolId' requires SQL to return column named exactly 'ToolId' (case-sensitive on some DBs) as boundary between mapped types; wrong split column causes entire second object to be null in agent JOIN mapping without error
Alternatives
Full Evaluation Report
Detailed scoring breakdown, competitive positioning, security analysis, and improvement recommendations for Dapper.
Scores are editorial opinions as of 2026-03-06.