using System.Data; using System.Data.Common; using Domain.Entities; using Infrastructure.Sql; namespace Features.UserManagement.Repository; /// /// ADO.NET-based implementation of backed by SQL Server /// stored procedures. /// /// The factory used to create database connections. public class UserAccountRepository(ISqlConnectionFactory connectionFactory) : Repository(connectionFactory), IUserAccountRepository { /// /// Retrieves a user account by ID using the usp_GetUserAccountById stored procedure. /// /// The unique identifier of the user account. /// The matching , or null if not found. /// Thrown when the database command fails. public async Task GetByIdAsync(Guid id) { await using DbConnection connection = await CreateConnection(); await using DbCommand command = connection.CreateCommand(); command.CommandText = "usp_GetUserAccountById"; command.CommandType = CommandType.StoredProcedure; AddParameter(command, "@UserAccountId", id); await using DbDataReader reader = await command.ExecuteReaderAsync(); return await reader.ReadAsync() ? MapToEntity(reader) : null; } /// /// Retrieves all user accounts, optionally paginated, using the usp_GetAllUserAccounts /// stored procedure. The @Limit and @Offset parameters are only added when their /// corresponding argument has a value. /// /// The maximum number of records to return, or null for no limit. /// The number of records to skip, or null for no offset. /// The collection of matching records. /// Thrown when the database command fails. public async Task> GetAllAsync( int? limit, int? offset ) { await using DbConnection connection = await CreateConnection(); await using DbCommand command = connection.CreateCommand(); command.CommandText = "usp_GetAllUserAccounts"; command.CommandType = CommandType.StoredProcedure; if (limit.HasValue) AddParameter(command, "@Limit", limit.Value); if (offset.HasValue) AddParameter(command, "@Offset", offset.Value); await using DbDataReader reader = await command.ExecuteReaderAsync(); List users = new(); while (await reader.ReadAsync()) users.Add(MapToEntity(reader)); return users; } /// /// Updates a user account's username, first name, last name, email, and date of birth using /// the usp_UpdateUserAccount stored procedure. /// /// The user account containing updated values. Must have a valid UserAccountId. /// Thrown when the database command fails. public async Task UpdateAsync(UserAccount userAccount) { await using DbConnection connection = await CreateConnection(); await using DbCommand command = connection.CreateCommand(); command.CommandText = "usp_UpdateUserAccount"; command.CommandType = CommandType.StoredProcedure; AddParameter(command, "@UserAccountId", userAccount.UserAccountId); AddParameter(command, "@Username", userAccount.Username); AddParameter(command, "@FirstName", userAccount.FirstName); AddParameter(command, "@LastName", userAccount.LastName); AddParameter(command, "@Email", userAccount.Email); AddParameter(command, "@DateOfBirth", userAccount.DateOfBirth); await command.ExecuteNonQueryAsync(); } /// /// Deletes a user account by ID using the usp_DeleteUserAccount stored procedure. /// /// The unique identifier of the user account to delete. /// Thrown when the database command fails. public async Task DeleteAsync(Guid id) { await using DbConnection connection = await CreateConnection(); await using DbCommand command = connection.CreateCommand(); command.CommandText = "usp_DeleteUserAccount"; command.CommandType = CommandType.StoredProcedure; AddParameter(command, "@UserAccountId", id); await command.ExecuteNonQueryAsync(); } /// /// Retrieves a user account by username using the usp_GetUserAccountByUsername stored procedure. /// /// The username to search for. /// The matching , or null if not found. /// Thrown when the database command fails. public async Task GetByUsernameAsync( string username ) { await using DbConnection connection = await CreateConnection(); await using DbCommand command = connection.CreateCommand(); command.CommandText = "usp_GetUserAccountByUsername"; command.CommandType = CommandType.StoredProcedure; AddParameter(command, "@Username", username); await using DbDataReader reader = await command.ExecuteReaderAsync(); return await reader.ReadAsync() ? MapToEntity(reader) : null; } /// /// Retrieves a user account by email address using the usp_GetUserAccountByEmail stored procedure. /// /// The email address to search for. /// The matching , or null if not found. /// Thrown when the database command fails. public async Task GetByEmailAsync( string email ) { await using DbConnection connection = await CreateConnection(); await using DbCommand command = connection.CreateCommand(); command.CommandText = "usp_GetUserAccountByEmail"; command.CommandType = CommandType.StoredProcedure; AddParameter(command, "@Email", email); await using DbDataReader reader = await command.ExecuteReaderAsync(); return await reader.ReadAsync() ? MapToEntity(reader) : null; } /// /// Maps the current row of a data reader to a entity. /// /// The data reader positioned on the row to map. /// The mapped instance. protected override UserAccount MapToEntity( DbDataReader reader ) { return new UserAccount { UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")), Username = reader.GetString(reader.GetOrdinal("Username")), FirstName = reader.GetString(reader.GetOrdinal("FirstName")), LastName = reader.GetString(reader.GetOrdinal("LastName")), Email = reader.GetString(reader.GetOrdinal("Email")), CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")), UpdatedAt = reader.IsDBNull(reader.GetOrdinal("UpdatedAt")) ? null : reader.GetDateTime(reader.GetOrdinal("UpdatedAt")), DateOfBirth = reader.GetDateTime(reader.GetOrdinal("DateOfBirth")), Timer = reader.IsDBNull(reader.GetOrdinal("Timer")) ? null : (byte[])reader["Timer"] }; } /// /// Helper method to add a parameter to a database command, converting null values to /// . /// /// The command to add the parameter to. /// The parameter name (including any prefix, e.g. "@Username"). /// The parameter value, or null to bind . private static void AddParameter( DbCommand command, string name, object? value ) { DbParameter p = command.CreateParameter(); p.ParameterName = name; p.Value = value ?? DBNull.Value; command.Parameters.Add(p); } }