Dapper for read-optimized queries alongside EF Core. Multi-mapping, pagination, dynamic filtering, CTEs, bulk operations.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
| 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 present