diff --git a/Database/GenericRepository/GenericRepository.cs b/Database/GenericRepository/GenericRepository.cs new file mode 100644 index 0000000..f4bf7e6 --- /dev/null +++ b/Database/GenericRepository/GenericRepository.cs @@ -0,0 +1,76 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; + +namespace GamificationService.Database.GenericRepository; + +public class GenericRepository : IGenericRepository where TEntity : class +{ + private readonly ApplicationContext _context; + private readonly DbSet _dbSet; + + public GenericRepository(ApplicationContext context) + { + _context = context; + _dbSet = context.Set(); + } + + public virtual IQueryable Get( + Expression>? filter = null, + Func, IOrderedQueryable>? orderBy = null, + params Expression>[] includes) + { + IQueryable 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 GetByIdAsync(object id) => + await _dbSet.FindAsync(id); + + public virtual void Add(TEntity entity) => _dbSet.Add(entity); + + public virtual void AddRange(IEnumerable 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 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); + } +} diff --git a/Database/GenericRepository/IGenericRepository.cs b/Database/GenericRepository/IGenericRepository.cs new file mode 100644 index 0000000..7df7e0b --- /dev/null +++ b/Database/GenericRepository/IGenericRepository.cs @@ -0,0 +1,24 @@ +using System.Linq.Expressions; + +namespace GamificationService.Database.GenericRepository; + +public interface IGenericRepository where TEntity : class +{ + IQueryable Get( + Expression>? filter = null, + Func, IOrderedQueryable>? orderBy = null, + params Expression>[] includes); + + TEntity? GetById(object id); + Task GetByIdAsync(object id); + + void Add(TEntity entity); + void AddRange(IEnumerable entities); + Task AddAsync(TEntity entity); + + void Delete(object id); + void DeleteRange(IEnumerable entities); + void Delete(TEntity entity); + + void Update(TEntity entity); +} diff --git a/Database/Repositories/GenericRepository.cs b/Database/Repositories/GenericRepository.cs deleted file mode 100755 index 1e94db0..0000000 --- a/Database/Repositories/GenericRepository.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore; - -namespace GamificationService.Database.Repositories; - -public class GenericRepository where TEntity : class -{ - internal ApplicationContext context; - internal DbSet dbSet; - - public GenericRepository(ApplicationContext context) - { - this.context = context; - this.dbSet = context.Set(); - } - - public virtual IQueryable Get( - Expression>? filter = null, - Func, IOrderedQueryable>? orderBy = null, - string includeProperties = "") - { - IQueryable 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 GetByIDAsync(object id) - { - return await dbSet.FindAsync(id); - } - public virtual void Insert(TEntity entity) - { - dbSet.Add(entity); - } - public virtual void InsertRange(IEnumerable 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 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; - } -} diff --git a/Database/Repositories/UnitOfWork.cs b/Database/Repositories/UnitOfWork.cs deleted file mode 100755 index 9629621..0000000 --- a/Database/Repositories/UnitOfWork.cs +++ /dev/null @@ -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 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); - } -} diff --git a/Database/UnitOfWork/IUnitOfWork.cs b/Database/UnitOfWork/IUnitOfWork.cs new file mode 100644 index 0000000..46d56ff --- /dev/null +++ b/Database/UnitOfWork/IUnitOfWork.cs @@ -0,0 +1,16 @@ + +namespace GamificationService.Database.UnitOfWork; + +public interface IUnitOfWork +{ + #region Repositories + #endregion + + #region Methods + bool SaveChanges(); + Task SaveChangesAsync(); + Task BeginTransactionAsync(); + Task CommitTransactionAsync(); + Task RollbackTransactionAsync(); + #endregion +} diff --git a/Database/UnitOfWork/UnitOfWork.cs b/Database/UnitOfWork/UnitOfWork.cs new file mode 100644 index 0000000..e143fbe --- /dev/null +++ b/Database/UnitOfWork/UnitOfWork.cs @@ -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 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 + +} diff --git a/Dockerfile b/Dockerfile index 2c06cee..9ac3cbd 100755 --- a/Dockerfile +++ b/Dockerfile @@ -7,17 +7,17 @@ EXPOSE 8081 FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["PurpleHackBackend/PurpleHackBackend.csproj", "PurpleHackBackend/"] -RUN dotnet restore "PurpleHackBackend/PurpleHackBackend.csproj" +COPY ["GamificationService.csproj", "GamificationService/"] +RUN dotnet restore "GamificationService/GamificationService.csproj" COPY . . -WORKDIR "/src/PurpleHackBackend" -RUN dotnet build "PurpleHackBackend.csproj" -c $BUILD_CONFIGURATION -o /app/build +WORKDIR "/src/GamificationService" +RUN dotnet build -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "PurpleHackBackend.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +RUN dotnet publish "GamificationService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "PurpleHackBackend.dll"] +ENTRYPOINT ["dotnet", "GamificationService.dll"] diff --git a/Extensions/DependencyInjectionExtensions.cs b/Extensions/DependencyInjectionExtensions.cs index 6ef3c1c..89c6f22 100755 --- a/Extensions/DependencyInjectionExtensions.cs +++ b/Extensions/DependencyInjectionExtensions.cs @@ -1,7 +1,8 @@ using System.Reflection; using System.Text; using GamificationService.Database; -using GamificationService.Database.Repositories; +using GamificationService.Database.GenericRepository; +using GamificationService.Database.UnitOfWork; using GamificationService.Loggging; using GamificationService.Mapper; using Microsoft.AspNetCore.Authentication.JwtBearer;