skills/data/dapper/SKILL.md
Use when writing read-optimized queries with Dapper alongside EF Core.
npx skillsauth add faysilalshareef/dotnet-ai-kit dapperInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Scenario | Use | |----------|-----| | Complex read queries with joins | Dapper | | Reporting and aggregation | Dapper | | Bulk insert/update | Dapper | | CRUD operations | EF Core | | Change tracking | EF Core | | Migrations | EF Core |
namespace {Company}.{Domain}.Infrastructure.Queries;
public sealed class OrderQueryService(IDbConnectionFactory connectionFactory)
{
public async Task<OrderDetailDto?> GetByIdAsync(Guid id, CancellationToken ct)
{
using var connection = connectionFactory.Create();
const string sql = """
SELECT o.Id, o.CustomerName, o.Total, o.Status, o.CreatedAt
FROM Orders o
WHERE o.Id = @Id
""";
return await connection.QuerySingleOrDefaultAsync<OrderDetailDto>(
new CommandDefinition(sql, new { Id = id }, cancellationToken: ct));
}
}
public async Task<IEnumerable<OrderWithItemsDto>> GetOrdersWithItemsAsync(
CancellationToken ct)
{
using var connection = connectionFactory.Create();
const string sql = """
SELECT o.Id, o.CustomerName, o.Total,
i.Id AS ItemId, i.ProductName, i.Quantity, i.UnitPrice
FROM Orders o
LEFT JOIN OrderItems i ON i.OrderId = o.Id
ORDER BY o.CreatedAt DESC
""";
var lookup = new Dictionary<Guid, OrderWithItemsDto>();
await connection.QueryAsync<OrderWithItemsDto, OrderItemDto, OrderWithItemsDto>(
new CommandDefinition(sql, cancellationToken: ct),
(order, item) =>
{
if (!lookup.TryGetValue(order.Id, out var existing))
{
existing = order;
existing.Items = [];
lookup[order.Id] = existing;
}
if (item is not null)
existing.Items.Add(item);
return existing;
},
splitOn: "ItemId");
return lookup.Values;
}
public async Task<PaginatedResult<OrderSummaryDto>> GetPaginatedAsync(
string? search, string? status, int page, int pageSize, CancellationToken ct)
{
using var connection = connectionFactory.Create();
var builder = new SqlBuilder();
var countTemplate = builder.AddTemplate(
"SELECT COUNT(*) FROM Orders o /**where**/");
var selectTemplate = builder.AddTemplate("""
SELECT o.Id, o.CustomerName, o.Total, o.Status, o.CreatedAt
FROM Orders o /**where**/ /**orderby**/
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY
""");
if (!string.IsNullOrEmpty(search))
builder.Where("o.CustomerName LIKE @Search", new { Search = $"%{search}%" });
if (!string.IsNullOrEmpty(status))
builder.Where("o.Status = @Status", new { Status = status });
builder.OrderBy("o.CreatedAt DESC");
var total = await connection.ExecuteScalarAsync<int>(
new CommandDefinition(countTemplate.RawSql, countTemplate.Parameters,
cancellationToken: ct));
var items = await connection.QueryAsync<OrderSummaryDto>(
new CommandDefinition(selectTemplate.RawSql,
new DynamicParameters(selectTemplate.Parameters)
{
{ "Offset", (page - 1) * pageSize },
{ "PageSize", pageSize }
}, cancellationToken: ct));
return new PaginatedResult<OrderSummaryDto>(items.ToList(), total, page, pageSize);
}
public interface IDbConnectionFactory
{
IDbConnection Create();
}
public sealed class SqlConnectionFactory(IOptions<DatabaseOptions> options)
: IDbConnectionFactory
{
public IDbConnection Create() =>
new SqlConnection(options.Value.ConnectionString);
}
services.AddSingleton<IDbConnectionFactory, SqlConnectionFactory>();
services.AddScoped<OrderQueryService>();
| Anti-Pattern | Correct Approach |
|---|---|
| String concatenation for SQL | Use SqlBuilder or parameterized queries |
| Not disposing connections | using var connection = ... |
| Using Dapper for simple CRUD | Use EF Core for CRUD |
| Forgetting CancellationToken | Pass via CommandDefinition |
grep -r "Dapper\|SqlMapper\|QueryAsync\|SqlBuilder" --include="*.cs"
grep -r "IDbConnection\|SqlConnection" --include="*.cs"
Dapper and Dapper.SqlBuilder NuGet packagesIDbConnectionFactory if not already presentdata-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.