ronnythedev/dotnet-clean-architecture-skills/19-dotnet-dapper-query-builder

Claude Code and agent skill by ronnythedev/dotnet-clean-architecture-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Generates optimized read queries using Dapper. Includes multi-mapping for joins, pagination, dynamic filtering, CTEs, and best practices for high-performance data access.

Install

npx vskill@latest install ronnythedev/dotnet-clean-architecture-skills/19-dotnet-dapper-query-builder

SKILL.md

Dapper Query Builder

Overview

Dapper provides lightweight, high-performance data access:

  • Raw SQL - Full control over queries
  • Multi-mapping - Handle complex joins
  • Parameterized queries - SQL injection protection
  • Minimal overhead - Near ADO.NET performance

Quick Reference

MethodPurposeUse Case
QueryAsync<T>Multiple rowsLists, reports
QueryFirstOrDefaultAsync<T>Single rowGet by ID
QueryMultipleAsyncMultiple result setsComplex data
ExecuteAsyncNo resultsInsert/Update/Delete
ExecuteScalarAsync<T>Single valueCount, exists

Template: Basic Query Handler

csharp
// src/{name}.application/{Feature}/Get{Entity}ById/Get{Entity}ByIdQueryHandler.cs
using System.Data;
using Dapper;
using {name}.application.abstractions.data;
using {name}.application.abstractions.messaging;
using {name}.domain.abstractions;
namespace {name}.application.{feature}.Get{Entity}ById;
internal sealed class Get{Entity}ByIdQueryHandler
: IQueryHandler<Get{Entity}ByIdQuery, {Entity}Response>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
public Get{Entity}ByIdQueryHandler(ISqlConnectionFactory sqlConnectionFactory)
{
_sqlConnectionFactory = sqlConnectionFactory;
}
public async Task<Result<{Entity}Response>> Handle(
Get{Entity}ByIdQuery request,
CancellationToken cancellationToken)
{
using IDbConnection connection = _sqlConnectionFactory.CreateConnection();
const string sql = """
SELECT
e.id AS Id,
e.name AS Name,
e.description AS Description,
e.is_active AS IsActive,
e.created_at AS CreatedAt,
o.id AS OrganizationId,
o.name AS OrganizationName
FROM entity e
INNER JOIN organization o ON e.organization_id = o.id
WHERE e.id = @Id
""";
var entity = await connection.QueryFirstOrDefaultAsync<{Entity}Response>(
sql,
new { request.Id });
return entity is null
? Result.Failure<{Entity}Response>({Entity}Errors.NotFound)
: entity;
}
}

Template: Multi-Mapping (One-to-Many)

csharp
internal sealed class Get{Entity}WithDetailsQueryHandler
: IQueryHandler<Get{Entity}WithDetailsQuery, {Entity}DetailResponse>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
public async Task<Result<{Entity}DetailResponse>> Handle(
Get{Entity}WithDetailsQuery request,
CancellationToken cancellationToken)
{
using IDbConnection connection = _sqlConnectionFactory.CreateConnection();
const string sql = """
SELECT
e.id AS Id,
e.name AS Name,
c.id AS ChildId,
c.name AS ChildName,
c.sort_order AS SortOrder
FROM entity e
LEFT JOIN child c ON c.entity_id = e.id
WHERE e.id = @Id
ORDER BY c.sort_order
""";
var entityDict = new Dictionary<Guid, {Entity}DetailResponse>();
await connection.QueryAsync<{Entity}DetailResponse, ChildResponse, {Entity}DetailResponse>(
sql,
(entity, child) =>
{
if (!entityDict.TryGetValue(entity.Id, out var existingEntity))
{
existingEntity = entity;
existingEntity.Children = new List<ChildResponse>();
entityDict.Add(entity.Id, existingEntity);
}
if (child is not null)
{
existingEntity.Children.Add(child);
}
return existingEntity;
},
new { request.Id },
splitOn: "ChildId");
var result = entityDict.Values.FirstOrDefault();
return result is null
? Result.Failure<{Entity}DetailResponse>({Entity}Errors.NotFound)
: result;
}
}

Template: Paginated Query with Filtering

csharp
internal sealed class Search{Entities}QueryHandler
: IQueryHandler<Search{Entities}Query, PagedList<{Entity}Response>>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
public async Task<Result<PagedList<{Entity}Response>>> Handle(
Search{Entities}Query request,
CancellationToken cancellationToken)
{
using IDbConnection connection = _sqlConnectionFactory.CreateConnection();
var offset = (request.PageNumber - 1) * request.PageSize;
var searchPattern = request.SearchTerm is not null
? $"%{request.SearchTerm}%"
: null;
// Build dynamic WHERE clause
var whereConditions = new List<string> { "1 = 1" };
if (searchPattern is not null)
whereConditions.Add("(e.name ILIKE @SearchPattern OR e.description ILIKE @SearchPattern)");
if (request.OrganizationId.HasValue)
whereConditions.Add("e.organization_id = @OrganizationId");
if (request.IsActive.HasValue)
whereConditions.Add("e.is_active = @IsActive");
var whereClause = string.Join(" AND ", whereConditions);
var countSql = $"""
SELECT COUNT(*)
FROM entity e
WHERE {whereClause}
""";
var dataSql = $"""
SELECT
e.id AS Id,
e.name AS Name,
e.description AS Description,
e.is_active AS IsActive,
e.created_at AS CreatedAt
FROM entity e
WHERE {whereClause}
ORDER BY e.created_at DESC
OFFSET @Offset ROWS
FETCH NEXT @PageSize ROWS ONLY
""";
var parameters = new
{
SearchPattern = searchPattern,
request.OrganizationId,
request.IsActive,
Offset = offset,
request.PageSize
};
var totalCount = await connection.ExecuteScalarAsync<int>(countSql, parameters);
var items = await connection.QueryAsync<{Entity}Response>(dataSql, parameters);
return new PagedList<{Entity}Response>(
items.ToList(),
request.PageNumber,
request.PageSize,
totalCount);
}
}

Template: CTE (Common Table Expression)

csharp
const string sql = """
WITH RankedItems AS (
SELECT
e.*,
ROW_NUMBER() OVER (PARTITION BY e.category_id ORDER BY e.score DESC) as rank
FROM entity e
WHERE e.organization_id = @OrganizationId
),
TopItems AS (
SELECT * FROM RankedItems WHERE rank <= 3
)
SELECT
ti.id AS Id,
ti.name AS Name,
ti.score AS Score,
ti.rank AS Rank,
c.name AS CategoryName
FROM TopItems ti
INNER JOIN category c ON ti.category_id = c.id
ORDER BY c.name, ti.rank
""";

Template: SQL Connection Factory

csharp
// src/{name}.application/Abstractions/Data/ISqlConnectionFactory.cs
using System.Data;
namespace {name}.application.abstractions.data;
public interface ISqlConnectionFactory
{
IDbConnection CreateConnection();
}
// src/{name}.infrastructure/Data/SqlConnectionFactory.cs
using System.Data;
using Npgsql;
namespace {name}.infrastructure.data;
internal sealed class SqlConnectionFactory : ISqlConnectionFactory
{
private readonly string _connectionString;
public SqlConnectionFactory(string connectionString)
{
_connectionString = connectionString;
}
public IDbConnection CreateConnection()
{
var connection = new NpgsqlConnection(_connectionString);
connection.Open();
return connection;
}
}

Column Mapping (Snake Case → PascalCase)

sql
SELECT
e.id AS Id, -- Maps to Id
e.first_name AS FirstName, -- Maps to FirstName
e.created_at AS CreatedAt, -- Maps to CreatedAt
e.organization_id AS OrganizationId -- Maps to OrganizationId
FROM entity e

Critical Rules

  1. Always use parameters - Never concatenate user input
  2. Use `using` for connections - Proper disposal
  3. Alias columns to match DTOs - AS PropertyName
  4. Multi-mapping for joins - Avoid N+1 queries
  5. CTEs for complex logic - More readable than nested queries
  6. OFFSET/FETCH for pagination - Standard SQL pagination
  7. ILIKE for case-insensitive - PostgreSQL specific
  8. Return DTOs not entities - Query projections only

Related Skills

  • dotnet-cqrs-query-generator - Query handler structure
  • dotnet-repository-pattern - EF Core for writes
  • dotnet-clean-architecture - Application layer

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
64
Repository forks
11
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ronnythedev/dotnet-clean-architecture-skills/19-dotnet-dapper-query-builder/badge)](https://verified-skill.com/skills/ronnythedev/dotnet-clean-architecture-skills/19-dotnet-dapper-query-builder)

Something wrong with this skill or its page? Tell us.