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;";
}
}