Skip to main content
ClaudeWave
Skill68 repo starsupdated 14d ago

dotnet-dapper-query-builder

The dotnet-dapper-query-builder skill generates optimized read queries using Dapper with templates for single and multi-mapped results. Use this when building high-performance data access layers that require raw SQL control, complex joins, pagination, dynamic filtering, and common table expressions while maintaining SQL injection protection through parameterized queries.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/ronnythedev/dotnet-clean-architecture-skills /tmp/dotnet-dapper-query-builder && cp -r /tmp/dotnet-dapper-query-builder/skills/19-dotnet-dapper-query-builder ~/.claude/skills/dotnet-dapper-query-builder
Then start a new Claude Code session; the skill loads automatically.

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

| Method | Purpose | Use Case |
|--------|---------|----------|
| `QueryAsync<T>` | Multiple rows | Lists, reports |
| `QueryFirstOrDefaultAsync<T>` | Single row | Get by ID |
| `QueryMultipleAsync` | Multiple result sets | Complex data |
| `ExecuteAsync` | No results | Insert/Update/Delete |
| `ExecuteScalarAsync<T>` | Single value | Count, 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
dotnet-clean-architectureSkill

Scaffolds a complete .NET solution following Clean Architecture principles with proper layer separation (API, Application, Domain, Infrastructure). Creates project structure, dependency injection setup, and cross-cutting concerns configuration.

dotnet-cqrs-command-generatorSkill

Generates CQRS Commands with Handlers, Validators, and Request DTOs following Clean Architecture patterns. Commands represent actions that modify state and return Result types for proper error handling.

dotnet-cqrs-query-generatorSkill

Generates CQRS Queries with Handlers and Response DTOs for read operations. Uses Dapper for optimized read queries, bypassing the domain model for better performance.

dotnet-domain-entity-generatorSkill

Generates Domain Entities following DDD principles with factory methods, private setters, domain events, and proper encapsulation. Supports aggregate roots, child entities, and value objects.

dotnet-repository-patternSkill

Generates Repository interfaces and implementations following the Repository pattern. Provides data access abstraction for aggregate roots with EF Core implementations.

dotnet-ef-core-configurationSkill

Generates Entity Framework Core configurations using Fluent API. Maps domain entities to database tables with proper relationships, constraints, and conventions.

dotnet-legacy-api-controllersSkill

Generates RESTful API Controllers with proper routing, versioning, authorization, and MediatR integration. Follows REST conventions and Clean Architecture patterns.

dotnet-minimal-api-endpointsSkill

Generates Minimal API endpoints following Microsoft's recommended approach. Creates fast, testable HTTP APIs with minimal code using MapGet/MapPost/MapPut/MapDelete. Preferred over controller-based APIs for new projects.