Class DbCommandAttribute
Namespace: Momentum.Extensions.Abstractions.Dapper
Assembly: Momentum.Extensions.Abstractions.dll
Marks a class for database command code generation, creating efficient data access patterns with minimal boilerplate.
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class DbCommandAttribute : Attribute
Inheritance
object ← Attribute ← DbCommandAttribute
Inherited Members
Attribute.GetCustomAttributes(MemberInfo, Type), Attribute.GetCustomAttributes(MemberInfo, Type, bool), Attribute.GetCustomAttributes(MemberInfo), Attribute.GetCustomAttributes(MemberInfo, bool), Attribute.IsDefined(MemberInfo, Type), Attribute.IsDefined(MemberInfo, Type, bool), Attribute.GetCustomAttribute(MemberInfo, Type), Attribute.GetCustomAttribute(MemberInfo, Type, bool), Attribute.GetCustomAttributes(ParameterInfo), Attribute.GetCustomAttributes(ParameterInfo, Type), Attribute.GetCustomAttributes(ParameterInfo, Type, bool), Attribute.GetCustomAttributes(ParameterInfo, bool), Attribute.IsDefined(ParameterInfo, Type), Attribute.IsDefined(ParameterInfo, Type, bool), Attribute.GetCustomAttribute(ParameterInfo, Type), Attribute.GetCustomAttribute(ParameterInfo, Type, bool), Attribute.GetCustomAttributes(Module, Type), Attribute.GetCustomAttributes(Module), Attribute.GetCustomAttributes(Module, bool), Attribute.GetCustomAttributes(Module, Type, bool), Attribute.IsDefined(Module, Type), Attribute.IsDefined(Module, Type, bool), Attribute.GetCustomAttribute(Module, Type), Attribute.GetCustomAttribute(Module, Type, bool), Attribute.GetCustomAttributes(Assembly, Type), Attribute.GetCustomAttributes(Assembly, Type, bool), Attribute.GetCustomAttributes(Assembly), Attribute.GetCustomAttributes(Assembly, bool), Attribute.IsDefined(Assembly, Type), Attribute.IsDefined(Assembly, Type, bool), Attribute.GetCustomAttribute(Assembly, Type), Attribute.GetCustomAttribute(Assembly, Type, bool), Attribute.Equals(object?), Attribute.GetHashCode(), Attribute.Match(object?), Attribute.IsDefaultAttribute(), Attribute.TypeId, object.GetType(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()
Examples
DbCommand Attribute Examples
Basic Stored Procedure Command
[DbCommand(sp: "create_user")]
public record CreateUserCommand(string Name, string Email) : ICommand<int>;
// Generated usage:
var command = new CreateUserCommand("John Doe", "john@example.com");
var userId = await CreateUserCommandHandler.HandleAsync(command, dataSource, cancellationToken);
SQL Query with Custom Parameter Names
[DbCommand(sql: "SELECT * FROM users WHERE created_date >= @from_date AND status = @user_status",
paramsCase: DbParamsCase.SnakeCase)]
public record GetRecentUsersQuery(
[Column("from_date")] DateTime Since,
[Column("user_status")] string Status) : IQuery<IEnumerable<User>>;
// Generated ToDbParams() creates: { from_date = Since, user_status = Status }
Database Function Call
[DbCommand(fn: "$get_user_orders")]
public record GetUserOrdersQuery(int UserId, bool IncludeInactive = false) : IQuery<IEnumerable<Order>>;
// Generated SQL: "SELECT * FROM get_user_orders(@UserId, @IncludeInactive)"
Non-Query Command (INSERT/UPDATE/DELETE)
[DbCommand(sql: "UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = @UserId", nonQuery: true)]
public record UpdateLastLoginCommand(int UserId) : ICommand<int>;
// Returns number of affected rows
Repository Integration Pattern
public class UserRepository
{
private readonly DbDataSource _dataSource;
public UserRepository(DbDataSource dataSource) => _dataSource = dataSource;
public async Task<int> CreateUserAsync(string name, string email, CancellationToken ct = default)
{
var command = new CreateUserCommand(name, email);
return await CreateUserCommandHandler.HandleAsync(command, _dataSource, ct);
}
public async Task<IEnumerable<User>> GetRecentUsersAsync(DateTime since, string status, CancellationToken ct = default)
{
var query = new GetRecentUsersQuery(since, status);
return await GetRecentUsersQueryHandler.HandleAsync(query, _dataSource, ct);
}
}
Dependency Injection with Named Data Sources
[DbCommand(sp: "archive_old_orders", dataSource: "ArchiveDatabase")]
public record ArchiveOldOrdersCommand(DateTime OlderThan) : ICommand<int>;
// Generated handler will resolve keyed service: [FromKeyedServices("ArchiveDatabase")] DbDataSource dataSource
Remarks
DbCommandAttribute Detailed Guide
Generated Code Behavior
This attribute triggers the source generator to create:
- ToDbParams() Extension Method: Converts class properties to Dapper-compatible parameter objects
- Command Handler Method: Static async method that executes the database command (when sp/sql/fn provided)
Command Handler Generation
Handlers are generated as static methods in a companion class (e.g., CreateUserCommandHandler.HandleAsync) that:
- Accept the command object, DbDataSource, and CancellationToken
- Open database connections automatically
- Map parameters using the generated ToDbParams() method
- Execute appropriate Dapper methods based on return type and nonQuery setting
- Handle connection disposal and async patterns correctly
Parameter Mapping Rules
- Record properties and primary constructor parameters are automatically mapped
- Parameter names follow the paramsCase setting (None, SnakeCase, or global default)
- Use [Column("custom_name")] attribute to override specific parameter names
- MSBuild property DbCommandParamPrefix adds global prefixes to all parameters
Return Type Handling
ICommand<int/long>
: Returns row count (ExecuteAsync) or scalar value (ExecuteScalarAsync)ICommand<TResult>
: Returns single object (QueryFirstOrDefaultAsync<TResult>
)ICommand<IEnumerable<TResult>>
: Returns collection (QueryAsync<TResult>
)ICommand
(no return type): Executes command without returning data (ExecuteAsync)
MSBuild Integration
Global configuration through MSBuild properties:
DbCommandDefaultParamCase
: Sets default parameter case conversion (None, SnakeCase)DbCommandParamPrefix
: Adds prefix to all generated parameter names
Requirements
- Target class must implement
ICommand<TResult>
orIQuery<TResult>
(or parameterless versions) - Class must be partial if nested within another type
- Only one of sp, sql, or fn can be specified per command
- Assembly must reference Momentum.Extensions.SourceGenerators
Constructors
DbCommandAttribute(string?, string?, string?, DbParamsCase, bool, string?)
Marks a class for database command code generation, creating efficient data access patterns with minimal boilerplate.
public DbCommandAttribute(string? sp = null, string? sql = null, string? fn = null, DbParamsCase paramsCase = DbParamsCase.Unset, bool nonQuery = false, string? dataSource = null)
Parameters
sp
string?
The name of the stored procedure to execute. Mutually exclusive with sql
and fn
.
sql
string?
The SQL query text to execute. Mutually exclusive with sp
and fn
.
fn
string?
The database function name to call. Parameters are auto-generated from record properties. Mutually exclusive with sp
and sql
. Use '$' prefix (e.g., "$get_user_orders") to generate "SELECT * FROM get_user_orders(...)" syntax.
paramsCase
DbParamsCase
Specifies how property names are converted to database parameter names. Defaults to global MSBuild configuration.
nonQuery
bool
Controls the execution strategy for database commands. Default is false.
If true: Uses Dapper's ExecuteAsync() for commands returning row counts (INSERT/UPDATE/DELETE operations). If false: Uses appropriate query methods (QueryAsync, QueryFirstOrDefaultAsync, ExecuteScalarAsync) based on the return type.
dataSource
string?
The keyed data source name for dependency injection. If null, uses the default registered data source.
Examples
DbCommand Attribute Examples
Basic Stored Procedure Command
[DbCommand(sp: "create_user")]
public record CreateUserCommand(string Name, string Email) : ICommand<int>;
// Generated usage:
var command = new CreateUserCommand("John Doe", "john@example.com");
var userId = await CreateUserCommandHandler.HandleAsync(command, dataSource, cancellationToken);
SQL Query with Custom Parameter Names
[DbCommand(sql: "SELECT * FROM users WHERE created_date >= @from_date AND status = @user_status",
paramsCase: DbParamsCase.SnakeCase)]
public record GetRecentUsersQuery(
[Column("from_date")] DateTime Since,
[Column("user_status")] string Status) : IQuery<IEnumerable<User>>;
// Generated ToDbParams() creates: { from_date = Since, user_status = Status }
Database Function Call
[DbCommand(fn: "$get_user_orders")]
public record GetUserOrdersQuery(int UserId, bool IncludeInactive = false) : IQuery<IEnumerable<Order>>;
// Generated SQL: "SELECT * FROM get_user_orders(@UserId, @IncludeInactive)"
Non-Query Command (INSERT/UPDATE/DELETE)
[DbCommand(sql: "UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = @UserId", nonQuery: true)]
public record UpdateLastLoginCommand(int UserId) : ICommand<int>;
// Returns number of affected rows
Repository Integration Pattern
public class UserRepository
{
private readonly DbDataSource _dataSource;
public UserRepository(DbDataSource dataSource) => _dataSource = dataSource;
public async Task<int> CreateUserAsync(string name, string email, CancellationToken ct = default)
{
var command = new CreateUserCommand(name, email);
return await CreateUserCommandHandler.HandleAsync(command, _dataSource, ct);
}
public async Task<IEnumerable<User>> GetRecentUsersAsync(DateTime since, string status, CancellationToken ct = default)
{
var query = new GetRecentUsersQuery(since, status);
return await GetRecentUsersQueryHandler.HandleAsync(query, _dataSource, ct);
}
}
Dependency Injection with Named Data Sources
[DbCommand(sp: "archive_old_orders", dataSource: "ArchiveDatabase")]
public record ArchiveOldOrdersCommand(DateTime OlderThan) : ICommand<int>;
// Generated handler will resolve keyed service: [FromKeyedServices("ArchiveDatabase")] DbDataSource dataSource
Remarks
DbCommandAttribute Detailed Guide
Generated Code Behavior
This attribute triggers the source generator to create:
- ToDbParams() Extension Method: Converts class properties to Dapper-compatible parameter objects
- Command Handler Method: Static async method that executes the database command (when sp/sql/fn provided)
Command Handler Generation
Handlers are generated as static methods in a companion class (e.g., CreateUserCommandHandler.HandleAsync) that:
- Accept the command object, DbDataSource, and CancellationToken
- Open database connections automatically
- Map parameters using the generated ToDbParams() method
- Execute appropriate Dapper methods based on return type and nonQuery setting
- Handle connection disposal and async patterns correctly
Parameter Mapping Rules
- Record properties and primary constructor parameters are automatically mapped
- Parameter names follow the paramsCase setting (None, SnakeCase, or global default)
- Use [Column("custom_name")] attribute to override specific parameter names
- MSBuild property DbCommandParamPrefix adds global prefixes to all parameters
Return Type Handling
ICommand<int/long>
: Returns row count (ExecuteAsync) or scalar value (ExecuteScalarAsync)ICommand<TResult>
: Returns single object (QueryFirstOrDefaultAsync<TResult>
)ICommand<IEnumerable<TResult>>
: Returns collection (QueryAsync<TResult>
)ICommand
(no return type): Executes command without returning data (ExecuteAsync)
MSBuild Integration
Global configuration through MSBuild properties:
DbCommandDefaultParamCase
: Sets default parameter case conversion (None, SnakeCase)DbCommandParamPrefix
: Adds prefix to all generated parameter names
Requirements
- Target class must implement
ICommand<TResult>
orIQuery<TResult>
(or parameterless versions) - Class must be partial if nested within another type
- Only one of sp, sql, or fn can be specified per command
- Assembly must reference Momentum.Extensions.SourceGenerators
Properties
DataSource
Gets the data source key.
public string? DataSource { get; }
Property Value
Fn
If set, a command handler will be generated using this function SQL query. Parameters will be automatically appended based on record properties.
public string? Fn { get; }
Property Value
NonQuery
Indicates the nature of the command. This flag primarily influences behavior for ICommand<int/long>.
If true:
- For ICommand<int/long>: The generated handler will use Dapper's ExecuteAsync (expecting rows affected).
- For ICommand<TResult> where TResult is not int: A warning will be issued by the source generator, as using NonQuery=true with a command expecting a specific data structure is atypical. The handler will default to execute a Query or QueryFirstOrDefault call and return default(TResult).
If false:
- For ICommand<int>: The generated handler will use Dapper's ExecuteScalarAsync<int> (expecting a scalar integer query result).
- For ICommand<TResult> where TResult is not int: The handler will perform a query (e.g., QueryFirstOrDefault or Query).
public bool NonQuery { get; }
Property Value
ParamsCase
Specifies how property names are converted to database parameter names in the generated ToDbParams() method.
- : Uses the global default specified by the DbCommandDefaultParamCase MSBuild property
- : Uses property names as-is without any conversion
- : Converts property names to snake_case (e.g., FirstName -> first_name)
Individual properties can override this behavior using the [Column("custom_name")] attribute.
public DbParamsCase ParamsCase { get; }
Property Value
Sp
If set, a command handler will be generated using this stored procedure.
public string? Sp { get; }
Property Value
Sql
If set, a command handler will be generated using this SQL query.
public string? Sql { get; }