ronnythedev/dotnet-clean-architecture-skills/03-dotnet-cqrs-query-generator

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 CQRS Queries with Handlers and Response DTOs for read operations. Uses Dapper for optimized read queries, bypassing the domain model for better performance.

Install

npx vskill@latest install ronnythedev/dotnet-clean-architecture-skills/03-dotnet-cqrs-query-generator

SKILL.md

CQRS Query Generator

Overview

This skill generates Queries following the CQRS pattern. Queries are read-only operations that return data without modifying state. Key principles:

  • Queries never modify state - Read-only operations
  • Use Dapper for reads - Bypass EF Core for performance
  • Return DTOs, not entities - Projection to response models
  • Direct SQL queries - Optimized for the specific use case

Quick Reference

Query TypeUse CaseReturns
GetByIdSingle entity by IDResult<EntityResponse>
GetAllAll entities (with optional filtering)Result<IReadOnlyList<EntityResponse>>
GetPagedPaginated listResult<PagedList<EntityResponse>>
SearchFiltered/searched resultsResult<IReadOnlyList<EntityResponse>>
ExistsCheck if entity existsResult<bool>

Query Structure

/Application/{Feature}/
├── Get{Entity}ById/
│ ├── Get{Entity}ByIdQuery.cs # Query + Validator + Handler
│ └── {Entity}Response.cs # Response DTO
├── GetAll{Entities}/
│ ├── GetAll{Entities}Query.cs
│ └── {Entity}ListResponse.cs
└── Get{Entities}ByOrganization/
├── Get{Entities}ByOrganizationQuery.cs
└── {Entity}ByOrganizationResponse.cs

Template: Get By ID Query

csharp
// src/{name}.application/{Feature}/Get{Entity}ById/Get{Entity}ByIdQuery.cs
using System.Data;
using Dapper;
using FluentValidation;
using {name}.application.abstractions.data;
using {name}.application.abstractions.messaging;
using {name}.domain.abstractions;
using {name}.domain.{entities};
namespace {name}.application.{feature}.Get{Entity}ById;
// ═══════════════════════════════════════════════════════════════
// QUERY RECORD
// ═══════════════════════════════════════════════════════════════
public sealed record Get{Entity}ByIdQuery(Guid Id) : IQuery<{Entity}Response>;
// ═══════════════════════════════════════════════════════════════
// VALIDATOR
// ═══════════════════════════════════════════════════════════════
internal sealed class Get{Entity}ByIdQueryValidator : AbstractValidator<Get{Entity}ByIdQuery>
{
public Get{Entity}ByIdQueryValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("{Entity} ID is required");
}
}
// ═══════════════════════════════════════════════════════════════
// HANDLER
// ═══════════════════════════════════════════════════════════════
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.created_at AS CreatedAt,
e.updated_at AS UpdatedAt
FROM {table_name} e
WHERE e.id = @Id
""";
var {entity} = await connection.QueryFirstOrDefaultAsync<{Entity}Response>(
sql,
new { request.Id });
if ({entity} is null)
{
return Result.Failure<{Entity}Response>({Entity}Errors.NotFound);
}
return {entity};
}
}
Response DTO
csharp
// src/{name}.application/{Feature}/Get{Entity}ById/{Entity}Response.cs
namespace {name}.application.{feature}.Get{Entity}ById;
public sealed class {Entity}Response
{
public Guid Id { get; init; }
public required string Name { get; init; }
public string? Description { get; init; }
public DateTime CreatedAt { get; init; }
public DateTime UpdatedAt { get; init; }
}

Template: Get All Query

csharp
// src/{name}.application/{Feature}/GetAll{Entities}/GetAll{Entities}Query.cs
using System.Data;
using Dapper;
using {name}.application.abstractions.data;
using {name}.application.abstractions.messaging;
using {name}.domain.abstractions;
namespace {name}.application.{feature}.GetAll{Entities};
public sealed record GetAll{Entities}Query : IQuery<IReadOnlyList<{Entity}ListResponse>>;
internal sealed class GetAll{Entities}QueryHandler
: IQueryHandler<GetAll{Entities}Query, IReadOnlyList<{Entity}ListResponse>>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
public GetAll{Entities}QueryHandler(ISqlConnectionFactory sqlConnectionFactory)
{
_sqlConnectionFactory = sqlConnectionFactory;
}
public async Task<Result<IReadOnlyList<{Entity}ListResponse>>> Handle(
GetAll{Entities}Query request,
CancellationToken cancellationToken)
{
using IDbConnection connection = _sqlConnectionFactory.CreateConnection();
const string sql = """
SELECT
e.id AS Id,
e.name AS Name,
e.description AS Description
FROM {table_name} e
ORDER BY e.name ASC
""";
var {entities} = await connection.QueryAsync<{Entity}ListResponse>(sql);
return {entities}.ToList();
}
}

Template: Get By Parent ID Query

csharp
// src/{name}.application/{Feature}/Get{Entities}ByOrganizationId/Get{Entities}ByOrganizationIdQuery.cs
using System.Data;
using Dapper;
using FluentValidation;
using {name}.application.abstractions.data;
using {name}.application.abstractions.messaging;
using {name}.domain.abstractions;
namespace {name}.application.{feature}.Get{Entities}ByOrganizationId;
public sealed record Get{Entities}ByOrganizationIdQuery(
Guid OrganizationId) : IQuery<IReadOnlyList<{Entity}Response>>;
internal sealed class Get{Entities}ByOrganizationIdQueryValidator
: AbstractValidator<Get{Entities}ByOrganizationIdQuery>
{
public Get{Entities}ByOrganizationIdQueryValidator()
{
RuleFor(x => x.OrganizationId).NotEmpty();
}
}
internal sealed class Get{Entities}ByOrganizationIdQueryHandler
: IQueryHandler<Get{Entities}ByOrganizationIdQuery, IReadOnlyList<{Entity}Response>>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
public Get{Entities}ByOrganizationIdQueryHandler(ISqlConnectionFactory sqlConnectionFactory)
{
_sqlConnectionFactory = sqlConnectionFactory;
}
public async Task<Result<IReadOnlyList<{Entity}Response>>> Handle(
Get{Entities}ByOrganizationIdQuery 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.organization_id AS OrganizationId,
o.name AS OrganizationName
FROM {table_name} e
INNER JOIN organization o ON e.organization_id = o.id
WHERE e.organization_id = @OrganizationId
ORDER BY e.name ASC
""";
var {entities} = await connection.QueryAsync<{Entity}Response>(
sql,
new { request.OrganizationId });
return {entities}.ToList();
}
}

Template: Paginated Query

csharp
// src/{name}.application/{Feature}/Get{Entities}Paged/Get{Entities}PagedQuery.cs
using System.Data;
using Dapper;
using FluentValidation;
using {name}.application.abstractions.data;
using {name}.application.abstractions.messaging;
using {name}.domain.abstractions;
namespace {name}.application.{feature}.Get{Entities}Paged;
public sealed record Get{Entities}PagedQuery(
int PageNumber,
int PageSize,
string? SearchTerm = null) : IQuery<PagedList<{Entity}Response>>;
internal sealed class Get{Entities}PagedQueryValidator
: AbstractValidator<Get{Entities}PagedQuery>
{
public Get{Entities}PagedQueryValidator()
{
RuleFor(x => x.PageNumber).GreaterThan(0);
RuleFor(x => x.PageSize).InclusiveBetween(1, 100);
}
}
internal sealed class Get{Entities}PagedQueryHandler
: IQueryHandler<Get{Entities}PagedQuery, PagedList<{Entity}Response>>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
public Get{Entities}PagedQueryHandler(ISqlConnectionFactory sqlConnectionFactory)
{
_sqlConnectionFactory = sqlConnectionFactory;
}
public async Task<Result<PagedList<{Entity}Response>>> Handle(
Get{Entities}PagedQuery 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;
const string countSql = """
SELECT COUNT(*)
FROM {table_name} e
WHERE (@SearchTerm IS NULL OR e.name ILIKE @SearchTerm)
""";
const string dataSql = """
SELECT
e.id AS Id,
e.name AS Name,
e.description AS Description,
e.created_at AS CreatedAt
FROM {table_name} e
WHERE (@SearchTerm IS NULL OR e.name ILIKE @SearchTerm)
ORDER BY e.created_at DESC
OFFSET @Offset ROWS
FETCH NEXT @PageSize ROWS ONLY
""";
var totalCount = await connection.ExecuteScalarAsync<int>(
countSql,
new { SearchTerm = searchPattern });
var items = await connection.QueryAsync<{Entity}Response>(
dataSql,
new
{
SearchTerm = searchPattern,
Offset = offset,
request.PageSize
});
return new PagedList<{Entity}Response>(
items.ToList(),
request.PageNumber,
request.PageSize,
totalCount);
}
}
// Shared paged list model
public sealed class PagedList<T>
{
public IReadOnlyList<T> Items { get; }
public int PageNumber { get; }
public int PageSize { get; }
public int TotalCount { get; }
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
public bool HasPreviousPage => PageNumber > 1;
public bool HasNextPage => PageNumber < TotalPages;
public PagedList(IReadOnlyList<T> items, int pageNumber, int pageSize, int totalCount)
{
Items = items;
PageNumber = pageNumber;
PageSize = pageSize;
TotalCount = totalCount;
}
}

Template: Query with Multi-Mapping (Joins)

csharp
// src/{name}.application/{Feature}/Get{Entity}WithDetails/Get{Entity}WithDetailsQuery.cs
using System.Data;
using Dapper;
using FluentValidation;
using {name}.application.abstractions.data;
using {name}.application.abstractions.messaging;
using {name}.domain.abstractions;
namespace {name}.application.{feature}.Get{Entity}WithDetails;
public sealed record Get{Entity}WithDetailsQuery(
Guid Id) : IQuery<{Entity}DetailResponse>;
internal sealed class Get{Entity}WithDetailsQueryHandler
: IQueryHandler<Get{Entity}WithDetailsQuery, {Entity}DetailResponse>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
public Get{Entity}WithDetailsQueryHandler(ISqlConnectionFactory sqlConnectionFactory)
{
_sqlConnectionFactory = sqlConnectionFactory;
}

This is the start of the file. Read the full SKILL.md on GitHub.

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/03-dotnet-cqrs-query-generator/badge)](https://verified-skill.com/skills/ronnythedev/dotnet-clean-architecture-skills/03-dotnet-cqrs-query-generator)

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