Dapper Data Access Standards
Before reviewing: Read .planning/CONVENTIONS.md for the repository's data access patterns (raw Dapper, Dapper.Contrib, DapperAOT). Follow the established approach.
Hard Rules for Review
Must Flag as Critical
- •SQL injection — String concatenation or interpolation in SQL queries instead of parameterised queries
- •Undisposed connections —
IDbConnectioncreated withoutusingstatement orIAsyncDisposablepattern - •Wrong CommandType — Calling stored procedures with
CommandType.Textinstead ofCommandType.StoredProcedure - •Missing transactions — Multi-statement writes without transaction wrapping
- •Type mismatches — C# parameter types not matching SQL column types (causes implicit conversions and scans)
Must Flag as Important
- •Missing CancellationToken — Async queries without
CancellationTokenpropagation viaCommandDefinition - •SELECT * — Querying all columns when only a subset is needed
- •N+1 queries — Loading related data in a loop instead of using
QueryMultipleor joins - •Missing async — Using
QuerywhenQueryAsyncis available - •Hardcoded connection strings — Connection strings outside of configuration/DI
Connection Management
DI Factory Pattern
csharp
// Registration — inject a factory, not a connection
builder.Services.AddTransient<IDbConnection>(sp =>
new SqlConnection(sp.GetRequiredService<IConfiguration>()
.GetConnectionString("BankingDb")));
// Usage — always use 'using' for disposal
public class AccountRepository
{
private readonly IDbConnection _connection;
public AccountRepository(IDbConnection connection) => _connection = connection;
public async Task<Account?> GetByIdAsync(long accountId, CancellationToken ct)
{
var command = new CommandDefinition(
"SELECT AccountId, Balance, Status FROM Banking.Account WHERE AccountId = @AccountId",
new { AccountId = accountId },
cancellationToken: ct);
return await _connection.QuerySingleOrDefaultAsync<Account>(command);
}
}
Stored Procedure Calling
Cross-reference mssql-stored-procedures skill for the stored procedure implementation patterns.
Basic Call
csharp
var result = await connection.QuerySingleOrDefaultAsync<TransferResult>(
"Banking.usp_TransferFunds",
new { FromAccountId = source, ToAccountId = dest, Amount = amount, IdempotencyKey = key },
commandType: CommandType.StoredProcedure);
DynamicParameters with OUTPUT
csharp
var parameters = new DynamicParameters();
parameters.Add("@AccountId", accountId);
parameters.Add("@Amount", amount);
parameters.Add("@NewBalance", dbType: DbType.Decimal, direction: ParameterDirection.Output, precision: 19, scale: 4);
parameters.Add("@ReturnValue", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);
await connection.ExecuteAsync(
"Banking.usp_DebitAccount",
parameters,
commandType: CommandType.StoredProcedure);
var newBalance = parameters.Get<decimal>("@NewBalance");
var returnCode = parameters.Get<int>("@ReturnValue");
Resilience & Auditability Rules
- •DECIMAL(19,4) precision — All monetary parameters must use
decimalin C# mapped toDECIMAL(19,4)in SQL. Never usedoubleorfloatfor money - •Audit columns — INSERT/UPDATE queries must set audit columns (
CreatedBy,CreatedDate,ModifiedBy,ModifiedDate) using server-side values (SYSUTCDATETIME()) not client-side - •Optimistic concurrency — Updates to critical records should check
RowVersionand handle@@ROWCOUNT = 0(concurrency conflict) - •Idempotency checks — Mutation operations should check for existing idempotency keys before executing (cross-ref
mssql-stored-proceduresskill)
Review Checklist
Connection & Lifecycle
- • Connections created with
usingor via DI (Transient lifetime)? - • No connection caching or singleton connections?
- • Connection strings from configuration (not hardcoded)?
Queries
- • All queries parameterised (no string concatenation/interpolation)?
- • Specific columns selected (no
SELECT *)? - •
QueryAsyncused instead ofQueryfor I/O-bound operations? - •
CancellationTokenpropagated viaCommandDefinition? - • No N+1 query patterns?
Stored Procedures
- •
CommandType.StoredProcedureused for stored procedure calls? - •
DynamicParametersused for OUTPUT parameters? - • C# parameter types match SQL parameter types?
Transactions
- • Multi-statement writes wrapped in transactions?
- • Transactions committed in try, rolled back in catch?
- • Transaction scope kept minimal?
Resilience & Auditability
- •
decimalused for all monetary values? - • Audit columns set on writes?
- • Optimistic concurrency (RowVersion check) for critical updates?
- • Idempotency key checked before mutation writes?