refactor: modernized GenericRepository and UnitOfWork with newer code from Roma

This commit is contained in:
2025-09-21 00:02:26 +03:00
parent 3b321d0ff4
commit ee94ffa373
8 changed files with 192 additions and 176 deletions

View File

@@ -0,0 +1,76 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
namespace GamificationService.Database.GenericRepository;
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
private readonly ApplicationContext _context;
private readonly DbSet<TEntity> _dbSet;
public GenericRepository(ApplicationContext context)
{
_context = context;
_dbSet = context.Set<TEntity>();
}
public virtual IQueryable<TEntity> Get(
Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = _dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (includes != null)
{
foreach (var include in includes)
{
query = query.Include(include);
}
}
return orderBy != null ? orderBy(query) : query;
}
public virtual TEntity? GetById(object id) => _dbSet.Find(id);
public virtual async Task<TEntity?> GetByIdAsync(object id) =>
await _dbSet.FindAsync(id);
public virtual void Add(TEntity entity) => _dbSet.Add(entity);
public virtual void AddRange(IEnumerable<TEntity> entities) => _dbSet.AddRange(entities);
public virtual async Task AddAsync(TEntity entity) => await _dbSet.AddAsync(entity);
public virtual void Delete(object id)
{
var entity = _dbSet.Find(id);
if (entity == null)
{
throw new KeyNotFoundException($"Entity of type {typeof(TEntity).Name} with id '{id}' was not found.");
}
Delete(entity);
}
public virtual void DeleteRange(IEnumerable<TEntity> entities) => _dbSet.RemoveRange(entities);
public virtual void Delete(TEntity entity)
{
if (_context.Entry(entity).State == EntityState.Detached)
{
_dbSet.Attach(entity);
}
_dbSet.Remove(entity);
}
public virtual void Update(TEntity entity)
{
_dbSet.Update(entity);
}
}

View File

@@ -0,0 +1,24 @@
using System.Linq.Expressions;
namespace GamificationService.Database.GenericRepository;
public interface IGenericRepository<TEntity> where TEntity : class
{
IQueryable<TEntity> Get(
Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
params Expression<Func<TEntity, object>>[] includes);
TEntity? GetById(object id);
Task<TEntity?> GetByIdAsync(object id);
void Add(TEntity entity);
void AddRange(IEnumerable<TEntity> entities);
Task AddAsync(TEntity entity);
void Delete(object id);
void DeleteRange(IEnumerable<TEntity> entities);
void Delete(TEntity entity);
void Update(TEntity entity);
}

View File

@@ -1,97 +0,0 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
namespace GamificationService.Database.Repositories;
public class GenericRepository<TEntity> where TEntity : class
{
internal ApplicationContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(ApplicationContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IQueryable<TEntity> Get(
Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query);
}
else
{
return query;
}
}
public virtual TEntity? GetByID(object id)
{
return dbSet.Find(id);
}
public async virtual Task<TEntity?> GetByIDAsync(object id)
{
return await dbSet.FindAsync(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void InsertRange(IEnumerable<TEntity> entities)
{
dbSet.AddRange(entities);
}
public async virtual Task InsertAsync(TEntity entity)
{
await dbSet.AddAsync(entity);
}
public virtual void Delete(object id)
{
TEntity? entityToDelete = dbSet.Find(id);
if (entityToDelete == null)
{
// It's probably a good idea to throw an error here
// but I'm leaving it as is for now
return;
}
Delete(entityToDelete);
}
public virtual void DeleteRange(IEnumerable<TEntity> entities)
{
dbSet.RemoveRange(entities);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}

View File

@@ -1,72 +0,0 @@
using Microsoft.EntityFrameworkCore.Storage;
namespace GamificationService.Database.Repositories;
public class UnitOfWork : IDisposable
{
#region fields
protected ApplicationContext _context;
private IDbContextTransaction? _transaction;
#endregion
public UnitOfWork(ApplicationContext context)
{
_context = context;
}
public bool Save()
{
return _context.SaveChanges() > 0;
}
public async Task<bool> SaveAsync()
{
return await _context.SaveChangesAsync() > 0;
}
public async Task BeginTransactionAsync()
{
if (_transaction is not null)
throw new InvalidOperationException("A transaction has already been started.");
_transaction = await _context.Database.BeginTransactionAsync();
}
public async Task CommitAsync()
{
if (_transaction is null)
throw new InvalidOperationException("A transaction has not been started.");
try
{
await _transaction.CommitAsync();
}
finally
{
await _transaction.DisposeAsync();
_transaction = null;
}
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
_transaction?.Dispose(); // Dispose transaction if it exists
_context.Dispose();
}
disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,16 @@
namespace GamificationService.Database.UnitOfWork;
public interface IUnitOfWork
{
#region Repositories
#endregion
#region Methods
bool SaveChanges();
Task<bool> SaveChangesAsync();
Task BeginTransactionAsync();
Task CommitTransactionAsync();
Task RollbackTransactionAsync();
#endregion
}

View File

@@ -0,0 +1,68 @@
using Microsoft.EntityFrameworkCore.Storage;
namespace GamificationService.Database.UnitOfWork;
public class UnitOfWork : IUnitOfWork
{
#region Fields
private readonly ApplicationContext _context;
private IDbContextTransaction? _transaction;
#endregion
public UnitOfWork(ApplicationContext context)
{
_context = context;
}
#region Repositories
#endregion
#region Save Methods
public bool SaveChanges() => _context.SaveChanges() > 0;
public async Task<bool> SaveChangesAsync() => (await _context.SaveChangesAsync()) > 0;
#endregion
#region Transactions
public async Task BeginTransactionAsync()
{
if (_transaction is not null)
throw new InvalidOperationException("A transaction has already been started.");
_transaction = await _context.Database.BeginTransactionAsync();
}
public async Task CommitTransactionAsync()
{
if (_transaction is null)
throw new InvalidOperationException("A transaction has not been started.");
try
{
await _transaction.CommitAsync();
}
catch
{
await RollbackTransactionAsync();
throw;
}
finally
{
await _transaction.DisposeAsync();
_transaction = null;
}
}
public async Task RollbackTransactionAsync()
{
if (_transaction is null)
return;
await _transaction.RollbackAsync();
await _transaction.DisposeAsync();
_transaction = null;
}
#endregion
}