feat:Initial commit

This commit is contained in:
elar1s
2025-09-25 22:21:41 +03:00
commit 02934b1fd9
35 changed files with 1267 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using StoreService.Database;
namespace StoreService.Repositories;
/// <summary>
/// Generic repository implementation wrapping EF Core DbSet.
/// </summary>
/// <typeparam name="TEntity">Entity type.</typeparam>
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
#region Fields
private readonly ApplicationContext _context;
private readonly DbSet<TEntity> _dbSet;
#endregion
#region Ctor
public GenericRepository(ApplicationContext context)
{
_context = context;
_dbSet = context.Set<TEntity>();
}
#endregion
#region Query
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);
foreach (var include in includes)
query = query.Include(include);
return orderBy != null ? orderBy(query) : query;
}
public virtual async Task<TEntity?> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = _dbSet;
foreach (var include in includes)
query = query.Include(include);
return await query.FirstOrDefaultAsync(predicate);
}
public virtual TEntity? GetById(object id) => _dbSet.Find(id);
public virtual async Task<TEntity?> GetByIdAsync(object id) => await _dbSet.FindAsync(id);
#endregion
#region Mutations
public virtual void Add(TEntity entity) => _dbSet.Add(entity);
public virtual async Task AddAsync(TEntity entity) => await _dbSet.AddAsync(entity);
public virtual void AddRange(IEnumerable<TEntity> entities) => _dbSet.AddRange(entities);
public virtual void Update(TEntity entity) => _dbSet.Update(entity);
public virtual void Delete(object id)
{
var entity = _dbSet.Find(id) ?? throw new KeyNotFoundException($"Entity {typeof(TEntity).Name} with id '{id}' not found");
Delete(entity);
}
public virtual void Delete(TEntity entity)
{
if (_context.Entry(entity).State == EntityState.Detached)
_dbSet.Attach(entity);
_dbSet.Remove(entity);
}
public virtual void DeleteRange(IEnumerable<TEntity> entities) => _dbSet.RemoveRange(entities);
#endregion
}

View File

@@ -0,0 +1,36 @@
using System.Linq.Expressions;
namespace StoreService.Repositories;
/// <summary>
/// Generic repository abstraction for simple CRUD & query operations.
/// </summary>
/// <typeparam name="TEntity">Entity type.</typeparam>
public interface IGenericRepository<TEntity> where TEntity : class
{
#region Query
IQueryable<TEntity> Get(
Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
params Expression<Func<TEntity, object>>[] includes);
Task<TEntity?> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate,
params Expression<Func<TEntity, object>>[] includes);
TEntity? GetById(object id);
Task<TEntity?> GetByIdAsync(object id);
#endregion
#region Mutations
void Add(TEntity entity);
Task AddAsync(TEntity entity);
void AddRange(IEnumerable<TEntity> entities);
void Update(TEntity entity);
void Delete(object id);
void Delete(TEntity entity);
void DeleteRange(IEnumerable<TEntity> entities);
#endregion
}

View File

@@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore.Storage;
using StoreService.Database.Entities;
namespace StoreService.Repositories;
/// <summary>
/// Unit of work pattern abstraction encapsulating repositories and transactions.
/// </summary>
public interface IUnitOfWork : IAsyncDisposable, IDisposable
{
#region Repositories
IGenericRepository<StoreCategory> StoreCategories { get; }
IGenericRepository<StoreItem> StoreItems { get; }
IGenericRepository<StoreDiscount> StoreDiscounts { get; }
IGenericRepository<StoreDiscountItem> StoreDiscountItems { get; }
IGenericRepository<StoreOrder> StoreOrders { get; }
IGenericRepository<StoreOrderItem> StoreOrderItems { get; }
IGenericRepository<StoreOrderItemDiscount> StoreOrderItemDiscounts { get; }
#endregion
#region Save
bool SaveChanges();
Task<bool> SaveChangesAsync(CancellationToken ct = default);
#endregion
#region Transactions
Task BeginTransactionAsync(CancellationToken ct = default);
Task CommitTransactionAsync(CancellationToken ct = default);
Task RollbackTransactionAsync(CancellationToken ct = default);
#endregion
}

View File

@@ -0,0 +1,99 @@
using Microsoft.EntityFrameworkCore.Storage;
using StoreService.Database;
using StoreService.Database.Entities;
namespace StoreService.Repositories;
/// <summary>
/// Coordinates repository access and database transactions.
/// </summary>
public class UnitOfWork : IUnitOfWork
{
#region Fields
private readonly ApplicationContext _context;
private IDbContextTransaction? _transaction;
#endregion
#region Ctor
public UnitOfWork(
ApplicationContext context,
IGenericRepository<StoreCategory> storeCategories,
IGenericRepository<StoreItem> storeItems,
IGenericRepository<StoreDiscount> storeDiscounts,
IGenericRepository<StoreDiscountItem> storeDiscountItems,
IGenericRepository<StoreOrder> storeOrders,
IGenericRepository<StoreOrderItem> storeOrderItems,
IGenericRepository<StoreOrderItemDiscount> storeOrderItemDiscounts)
{
_context = context;
StoreCategories = storeCategories;
StoreItems = storeItems;
StoreDiscounts = storeDiscounts;
StoreDiscountItems = storeDiscountItems;
StoreOrders = storeOrders;
StoreOrderItems = storeOrderItems;
StoreOrderItemDiscounts = storeOrderItemDiscounts;
}
#endregion
#region Repositories
public IGenericRepository<StoreCategory> StoreCategories { get; }
public IGenericRepository<StoreItem> StoreItems { get; }
public IGenericRepository<StoreDiscount> StoreDiscounts { get; }
public IGenericRepository<StoreDiscountItem> StoreDiscountItems { get; }
public IGenericRepository<StoreOrder> StoreOrders { get; }
public IGenericRepository<StoreOrderItem> StoreOrderItems { get; }
public IGenericRepository<StoreOrderItemDiscount> StoreOrderItemDiscounts { get; }
#endregion
#region Save
public bool SaveChanges() => _context.SaveChanges() > 0;
public async Task<bool> SaveChangesAsync(CancellationToken ct = default) => (await _context.SaveChangesAsync(ct)) > 0;
#endregion
#region Transactions
public async Task BeginTransactionAsync(CancellationToken ct = default)
{
if (_transaction != null) throw new InvalidOperationException("Transaction already started");
_transaction = await _context.Database.BeginTransactionAsync(ct);
}
public async Task CommitTransactionAsync(CancellationToken ct = default)
{
if (_transaction == null) throw new InvalidOperationException("No transaction started");
try
{
await _transaction.CommitAsync(ct);
}
catch
{
await RollbackTransactionAsync(ct);
throw;
}
finally
{
await _transaction.DisposeAsync();
_transaction = null;
}
}
public async Task RollbackTransactionAsync(CancellationToken ct = default)
{
if (_transaction == null) return;
await _transaction.RollbackAsync(ct);
await _transaction.DisposeAsync();
_transaction = null;
}
#endregion
#region Dispose
public void Dispose() => _transaction?.Dispose();
public async ValueTask DisposeAsync()
{
if (_transaction != null)
await _transaction.DisposeAsync();
}
#endregion
}