From 5ea1398670334636b0385f97861524f3b388dccd Mon Sep 17 00:00:00 2001 From: cihataydin Date: Mon, 19 May 2025 00:55:15 +0300 Subject: [PATCH] refactor(37): enhances repository pattern with additional methods and remove unit of work implementation --- src/Api/Program.cs | 3 +- src/Domain/Interfaces/IRepository.cs | 47 ++++++++++-- src/Domain/Interfaces/IUnitOfWork.cs | 9 --- src/Infra/Repositories/Repository.cs | 106 +++++++++++++++++++-------- src/Infra/Repositories/UnitOfWork.cs | 22 ------ 5 files changed, 116 insertions(+), 71 deletions(-) delete mode 100644 src/Domain/Interfaces/IUnitOfWork.cs delete mode 100644 src/Infra/Repositories/UnitOfWork.cs diff --git a/src/Api/Program.cs b/src/Api/Program.cs index 19eec38..b80aa88 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -19,8 +19,7 @@ builder.Services .AddDbContext(opts => opts.UseInMemoryDatabase("DemoDb")) - .AddScoped(typeof(IRepository<>), typeof(Repository<>)) - .AddScoped(); + .AddScoped(typeof(IRepository<>), typeof(Repository<>)); builder.Services.AddApiVersioning(options => { diff --git a/src/Domain/Interfaces/IRepository.cs b/src/Domain/Interfaces/IRepository.cs index 29d76b4..eedf958 100644 --- a/src/Domain/Interfaces/IRepository.cs +++ b/src/Domain/Interfaces/IRepository.cs @@ -8,15 +8,50 @@ public interface IRepository : IAsyncDisposable where TEntity : class Task GetByIdAsync(TKey id, CancellationToken cancellationToken = default); - Task> ListAsync(Expression>? predicate = null, CancellationToken cancellationToken = default); + Task> ListAsync(Expression>? predicate = null, bool asNoTracking = true, CancellationToken cancellationToken = default); - Task AddAsync(TEntity entity, CancellationToken cancellationToken = default); + Task AnyAsync(Expression> predicate, CancellationToken cancellationToken = default); - Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default); + Task CountAsync(Expression>? predicate = null, CancellationToken cancellationToken = default); - Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default); + TEntity Add(TEntity entity); + Task AddAsync( + TEntity entity, + CancellationToken cancellationToken = default); - Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default); + void AddRange(params TEntity[] entities); - Task UpdateAsync(Expression> predicate, object updateDefinition, CancellationToken cancellationToken = default); + void AddRange(IEnumerable entities); + + Task AddRangeAsync(params TEntity[] entities); + + Task AddRangeAsync( + IEnumerable entities, + CancellationToken cancellationToken = default); + + TEntity Attach(TEntity entity); + void AttachRange(params TEntity[] entities); + + void AttachRange(IEnumerable entities); + + TEntity Update(TEntity entity); + + void UpdateRange(params TEntity[] entities); + + void UpdateRange(IEnumerable entities); + + TEntity Remove(TEntity entity); + + void RemoveRange(params TEntity[] entities); + + void RemoveRange(IEnumerable entities); + + Task ExecuteDeleteAsync(Expression> predicate, CancellationToken cancellationToken = default); + + Task ExecuteUpdateAsync( + Expression> predicate, + object updateDefinition, + CancellationToken cancellationToken = default); + + Task SaveChangesAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Domain/Interfaces/IUnitOfWork.cs b/src/Domain/Interfaces/IUnitOfWork.cs deleted file mode 100644 index 96d8ea8..0000000 --- a/src/Domain/Interfaces/IUnitOfWork.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Domain.Interfaces; - -public interface IUnitOfWork : IAsyncDisposable -{ - Task SaveChangesAsync(CancellationToken cancellationToken = default); - - // TODO: implement transaction management - // Task BeginTransactionAsync(CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/Infra/Repositories/Repository.cs b/src/Infra/Repositories/Repository.cs index bc7bf59..4f82710 100644 --- a/src/Infra/Repositories/Repository.cs +++ b/src/Infra/Repositories/Repository.cs @@ -6,7 +6,7 @@ namespace Infra.Repositories; -public class Repository : IRepository where TEntity : class +public class Repository : IRepository where TEntity : class, new() { private readonly DataContext _dataContext; private readonly DbSet _dbSet; @@ -18,61 +18,103 @@ public Repository(DataContext dataContext) _dbSet = dataContext.Set(); } - public IQueryable Query(bool asNoTracking = true) => (asNoTracking ? _dbSet.AsNoTracking() : _dbSet).AsQueryable(); + public IQueryable Query(bool asNoTracking = true) => + asNoTracking ? _dbSet.AsNoTracking() : _dbSet.AsQueryable(); public Task GetByIdAsync(TKey id, CancellationToken cancellationToken = default) - { - return _dbSet.FindAsync(new object?[] { id }!, cancellationToken).AsTask(); - } + => _dbSet.FindAsync(new object?[] { id }!, cancellationToken).AsTask(); - public Task> ListAsync(Expression>? predicate = null, CancellationToken cancellationToken = default) + public Task> ListAsync( + Expression>? predicate = null, + bool asNoTracking = true, + CancellationToken cancellationToken = default) { - IQueryable query = _dbSet; - if (predicate is not null) query = query.Where(predicate); + IQueryable query = Query(asNoTracking); + if (predicate != null) query = query.Where(predicate); return query.ToListAsync(cancellationToken); } - public async Task AddAsync(TEntity entity, CancellationToken cancellationToken = default) - { - await _dbSet.AddAsync(entity, cancellationToken); + public Task AnyAsync(Expression> predicate, CancellationToken cancellationToken = default) + => _dbSet.AnyAsync(predicate, cancellationToken); - return entity; - } + public Task CountAsync(Expression>? predicate = null, CancellationToken cancellationToken = default) + => predicate == null + ? _dbSet.CountAsync(cancellationToken) + : _dbSet.CountAsync(predicate, cancellationToken); - public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) - { - _dbSet.Update(entity); + public TEntity Add(TEntity entity) + => _dbSet.Add(entity).Entity; - return Task.CompletedTask; - } + public async Task AddAsync( + TEntity entity, + CancellationToken cancellationToken = default) + => (await _dbSet.AddAsync(entity, cancellationToken)).Entity; - public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) - { - _dbSet.Remove(entity); + public void AddRange(params TEntity[] entities) + => _dbSet.AddRange(entities); - return Task.CompletedTask; - } + public void AddRange(IEnumerable entities) + => _dbSet.AddRange(entities); - public Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default) - { - return _dbSet.Where(predicate).ExecuteDeleteAsync(cancellationToken); - } + public Task AddRangeAsync(params TEntity[] entities) + => _dbSet.AddRangeAsync(entities); + + public Task AddRangeAsync( + IEnumerable entities, + CancellationToken cancellationToken = default) + => _dbSet.AddRangeAsync(entities, cancellationToken); - public Task UpdateAsync(Expression> predicate, object updateDefinition, CancellationToken cancellationToken = default) + public TEntity Attach(TEntity entity) + => _dbSet.Attach(entity).Entity; + + public void AttachRange(params TEntity[] entities) + => _dbSet.AttachRange(entities); + + public void AttachRange(IEnumerable entities) + => _dbSet.AttachRange(entities); + + public TEntity Update(TEntity entity) + => _dbSet.Update(entity).Entity; + + public void UpdateRange(params TEntity[] entities) + => _dbSet.UpdateRange(entities); + + public void UpdateRange(IEnumerable entities) + => _dbSet.UpdateRange(entities); + + public TEntity Remove(TEntity entity) + => _dbSet.Remove(entity).Entity; + + public void RemoveRange(params TEntity[] entities) + => _dbSet.RemoveRange(entities); + + public void RemoveRange(IEnumerable entities) + => _dbSet.RemoveRange(entities); + + public Task ExecuteDeleteAsync(Expression> predicate, CancellationToken cancellationToken = default) + => _dbSet.Where(predicate).ExecuteDeleteAsync(cancellationToken); + + public Task ExecuteUpdateAsync( + Expression> predicate, + object updateDefinition, + CancellationToken cancellationToken = default) { - if (updateDefinition is not Expression, SetPropertyCalls>> expression) + if (updateDefinition is not Expression, SetPropertyCalls>> updateExpression) { - throw new InvalidOperationException($"Invalid update definition for type '{typeof(TEntity).Name}'. Expected an expression of type Expression, SetPropertyCalls<{typeof(TEntity).Name}>>>."); + throw new ArgumentException("Invalid update expression for EF Core", nameof(updateDefinition)); } - return _dbSet.Where(predicate).ExecuteUpdateAsync(expression, cancellationToken); + return _dbSet.Where(predicate).ExecuteUpdateAsync(updateExpression, cancellationToken); } + public Task SaveChangesAsync(CancellationToken cancellationToken = default) => _dataContext.SaveChangesAsync(cancellationToken); + public async ValueTask DisposeAsync() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { await _dataContext.DisposeAsync(); + GC.SuppressFinalize(this); } } -} \ No newline at end of file +} diff --git a/src/Infra/Repositories/UnitOfWork.cs b/src/Infra/Repositories/UnitOfWork.cs deleted file mode 100644 index e422049..0000000 --- a/src/Infra/Repositories/UnitOfWork.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Infra.Data; -using Domain.Interfaces; - -namespace Infra.Repositories; - -public class UnitOfWork : IUnitOfWork -{ - private readonly DataContext _dataContext; - private int _disposed; - - public UnitOfWork(DataContext dataContext) => _dataContext = dataContext; - - public Task SaveChangesAsync(CancellationToken cancellationToken = default) => _dataContext.SaveChangesAsync(cancellationToken); - - public async ValueTask DisposeAsync() - { - if (Interlocked.Exchange(ref _disposed, 1) == 0) - { - await _dataContext.DisposeAsync(); - } - } -} \ No newline at end of file