using System.Data; using System.Reflection; using System.Text; using Dapper; using Galaeth.Core.Attributes.EntityAnnotation; using Galaeth.Core.Exceptions; using Galaeth.Core.Infrastructure; using Galaeth.Core.RepositoryInterfaces; namespace Galaeth.DAL.Repositories; /// public class BaseRepository : IGenericRepository where TEntity : class { /// /// Instance of . /// private readonly IDbConnectionProvider _db; /// /// Initializes a new instance of the class. /// /// Instance of . protected BaseRepository(IDbConnectionProvider db) { _db = db; SetupRepository(); } /// /// Table name. /// protected string Table { get; set; } /// /// Primary key column name. /// private string PrimaryKeyColumn { get; set; } /// /// Whether the primary key should be treated as auto-generated by the database. /// private bool AutoPrimaryKey { get; set; } /// /// Generated select all query string. /// private string SelectAllQuery { get; set; } /// /// Generated find by id query string. /// private string FindByIdQuery { get; set; } /// /// Generated insert query string. /// private string InsertQuery { get; set; } /// /// Generated update query string. /// private string UpdateQuery { get; set; } /// /// Generated delete query string. /// private string DeleteQuery { get; set; } /// public async Task FindByIdAsync(TKeyType id) { return await WithDatabaseAsync(async connection => { var result = await connection.QueryFirstOrDefaultAsync(FindByIdQuery, new { pId = id, }); return result; }); } /// public async Task> GetAllAsync() { return await WithDatabaseAsync(async connection => { var result = await connection.QueryAsync(SelectAllQuery); return result; }); } /// public async Task AddAsync(TEntity entity) { return await WithDatabaseAsync(async connection => { return await connection.QuerySingleAsync( InsertQuery, entity); }); } /// public async Task AddAsync(IEnumerable entities) { await WithDatabaseAsync(async connection => { await connection.ExecuteAsync( InsertQuery, entities); }); } /// public async Task UpdateAsync(TEntity entity) { return await WithDatabaseAsync(async connection => await connection.ExecuteAsync(UpdateQuery, entity) > 0); } /// public async Task DeleteAsync(TKeyType id) { await WithDatabaseAsync(async connection => await connection.ExecuteAsync(DeleteQuery, new { pId = id, })); } /// /// Establish a connection and perform the query function. /// /// Query method to execute. /// The return type. /// Result of query. /// Thrown if an exception occurs. protected async Task WithDatabaseAsync(Func> query) { var connection = await _db.OpenConnectionAsync(); return await query(connection); } /// /// Establish a connection and perform the query function. /// /// Query method to execute. protected async Task WithDatabaseAsync(Func query) { var connection = await _db.OpenConnectionAsync(); await query(connection); } /// /// Fetch a comma separated list of property names. /// /// True to exclude key property. /// Comma separated list of column names. private static string GetPropertyNames(bool excludeKey = false) { var properties = typeof(TEntity).GetProperties() .Where(p => !excludeKey || p.GetCustomAttribute() == null) .Where(p => p.IsDefined(typeof(PrimaryKeyAttribute), true) || p.IsDefined(typeof(ColumnMappingAttribute), true)); var values = string.Join(", ", properties.Select(p => $"@{p.Name}")); return values; } /// /// Setup repository. /// private void SetupRepository() { var entityType = typeof(TEntity); var properties = entityType.GetProperties(); var propertiesExcludingKey = properties .Where(p => p.GetCustomAttribute() == null); // Set up table name. var tableAttribute = entityType.GetCustomAttribute(); if (tableAttribute is null) { Table = typeof(TEntity).Name + "s"; } else { Table = tableAttribute.Name; } // Set up primary key. var primaryKeyProperties = properties.Where(p => p.IsDefined(typeof(PrimaryKeyAttribute), true)).ToList(); if (primaryKeyProperties is null || primaryKeyProperties.Count == 0) { throw new PrimaryKeyMissingException(typeof(TEntity)); } if (primaryKeyProperties.Count > 1) { throw new MultiplePrimaryKeysException(typeof(TEntity)); } var primaryKeyProperty = primaryKeyProperties.First(); var primaryKey = primaryKeyProperty.GetCustomAttribute(); var primaryKeyCol = primaryKeyProperty.GetCustomAttribute(); if (primaryKeyCol is null) { throw new MissingColumnAttributeException(typeof(TEntity)); } PrimaryKeyColumn = primaryKeyCol!.Name; AutoPrimaryKey = primaryKey!.AutoGenerated; SelectAllQuery = $"SELECT * FROM {Table};"; FindByIdQuery = $"SELECT * FROM {Table} WHERE {PrimaryKeyColumn} = @pId;"; // Add query. var columns = string.Join(", ", properties .Where(p => !AutoPrimaryKey || !p.IsDefined(typeof(PrimaryKeyAttribute))) .Where(p => p.IsDefined(typeof(ColumnMappingAttribute))) .Select(p => p.GetCustomAttribute() !.Name)); var propertyNames = GetPropertyNames(AutoPrimaryKey); var insertQuery = new StringBuilder(); insertQuery.Append( $"INSERT INTO {Table} ({columns}) VALUES ({propertyNames}) RETURNING {PrimaryKeyColumn};"); InsertQuery = insertQuery.ToString(); // Update query. var updateQuery = new StringBuilder(); updateQuery.Append($"UPDATE {Table} SET"); foreach (var property in propertiesExcludingKey.Where(p => p.IsDefined(typeof(ColumnMappingAttribute)))) { var columnAttr = property.GetCustomAttribute(); var propertyName = property.Name; updateQuery.Append($" {columnAttr!.Name} = @{propertyName},"); } updateQuery.Remove(updateQuery.Length - 1, 1); updateQuery.Append($" WHERE {PrimaryKeyColumn} = @{primaryKeyProperty.Name};"); UpdateQuery = updateQuery.ToString(); // Delete query. DeleteQuery = $"DELETE FROM {Table} WHERE {PrimaryKeyColumn} = @pId;"; } }