feat: Generic Repository 및 UnitOfWork 패턴 구현 (#18) #19

Merged
seonkyu.kim merged 1 commits from feature/#18-repository-pattern into develop 2026-02-09 05:47:21 +00:00
3 changed files with 125 additions and 0 deletions

View File

@ -1,6 +1,9 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SPMS.API.Middlewares; using SPMS.API.Middlewares;
using SPMS.Domain.Interfaces;
using SPMS.Infrastructure; using SPMS.Infrastructure;
using SPMS.Infrastructure.Persistence;
using SPMS.Infrastructure.Persistence.Repositories;
var builder = WebApplication.CreateBuilder(new WebApplicationOptions var builder = WebApplication.CreateBuilder(new WebApplicationOptions
@ -17,6 +20,8 @@ var connectionString = builder.Configuration.GetConnectionString("DefaultConnect
builder.Services.AddDbContext<AppDbContext>(options => builder.Services.AddDbContext<AppDbContext>(options =>
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))); options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
var app = builder.Build(); var app = builder.Build();

View File

@ -0,0 +1,70 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using SPMS.Domain.Entities;
using SPMS.Domain.Interfaces;
namespace SPMS.Infrastructure.Persistence.Repositories;
public class Repository<T> : IRepository<T> where T : BaseEntity
{
protected readonly AppDbContext _context;
protected readonly DbSet<T> _dbSet;
public Repository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(long id)
=> await _dbSet.FindAsync(id);
public async Task<IReadOnlyList<T>> GetAllAsync()
=> await _dbSet.ToListAsync();
public async Task<IReadOnlyList<T>> FindAsync(Expression<Func<T, bool>> predicate)
=> await _dbSet.Where(predicate).ToListAsync();
public async Task<(IReadOnlyList<T> Items, int TotalCount)> GetPagedAsync(
int page, int size,
Expression<Func<T, bool>>? predicate = null,
Expression<Func<T, object>>? orderBy = null,
bool descending = true)
{
IQueryable<T> query = _dbSet;
if (predicate is not null)
query = query.Where(predicate);
var totalCount = await query.CountAsync();
if (orderBy is not null)
query = descending ? query.OrderByDescending(orderBy) : query.OrderBy(orderBy);
else
query = query.OrderByDescending(e => e.Id);
var items = await query
.Skip((page - 1) * size)
.Take(size)
.ToListAsync();
return (items, totalCount);
}
public async Task<int> CountAsync(Expression<Func<T, bool>>? predicate = null)
=> predicate is null
? await _dbSet.CountAsync()
: await _dbSet.CountAsync(predicate);
public async Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate)
=> await _dbSet.AnyAsync(predicate);
public async Task AddAsync(T entity)
=> await _dbSet.AddAsync(entity);
public void Update(T entity)
=> _dbSet.Update(entity);
public void Delete(T entity)
=> _dbSet.Remove(entity);
}

View File

@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore.Storage;
using SPMS.Domain.Interfaces;
namespace SPMS.Infrastructure.Persistence;
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
private IDbContextTransaction? _transaction;
public UnitOfWork(AppDbContext context)
{
_context = context;
}
public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
=> await _context.SaveChangesAsync(cancellationToken);
public async Task<IDisposable> BeginTransactionAsync(CancellationToken cancellationToken = default)
{
_transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
return _transaction;
}
public async Task CommitTransactionAsync(CancellationToken cancellationToken = default)
{
if (_transaction is null)
throw new InvalidOperationException("Transaction has not been started.");
await _transaction.CommitAsync(cancellationToken);
await _transaction.DisposeAsync();
_transaction = null;
}
public async Task RollbackTransactionAsync(CancellationToken cancellationToken = default)
{
if (_transaction is null)
throw new InvalidOperationException("Transaction has not been started.");
await _transaction.RollbackAsync(cancellationToken);
await _transaction.DisposeAsync();
_transaction = null;
}
public void Dispose()
{
_transaction?.Dispose();
_context.Dispose();
}
}