Skip to content

Commit

Permalink
added new design of repository and unit of work pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
suxrobGM committed Nov 22, 2023
1 parent 14ca599 commit 12fa81c
Show file tree
Hide file tree
Showing 12 changed files with 487 additions and 15 deletions.
65 changes: 65 additions & 0 deletions src/Core/Logistics.Domain/Persistence/IMasterRepository2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Linq.Expressions;
using Logistics.Domain.Core;
using Logistics.Domain.Specifications;

namespace Logistics.Domain.Persistence;

/// <summary>
/// Generic repository.
/// </summary>
/// <typeparam name="TEntity">Class that implements the <see cref="IEntity{TKey}"/> interface</typeparam>
public interface IMasterRepository2<TEntity> where TEntity : class, IEntity<string>
{
/// <summary>
/// Asynchronously counts number of entities.
/// </summary>
/// <param name="predicate">Predicate to filter query</param>
/// <returns>Number of elements that satisfies the specified condition</returns>
Task<int> CountAsync(Expression<Func<TEntity, bool>>? predicate = default);

/// <summary>
/// Gets an entity object by ID.
/// </summary>
/// <param name="id">Entity primary key</param>
/// <returns>Entity object</returns>
Task<TEntity?> GetByIdAsync(string id);

/// <summary>
/// Gets an entity object filtered by predicate.
/// </summary>
/// <param name="predicate">Predicate to filter query</param>
/// <returns>Entity object</returns>
Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate);

/// <summary>
/// Gets a list of the entity objects
/// </summary>
/// <param name="predicate">Predicate to filter query</param>
/// <returns>List of entity objects</returns>
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate);

/// <summary>
/// Gets a list of the entity objects
/// </summary>
/// <param name="specification">Specification</param>
/// <returns>List of entity objects</returns>
Task<List<TEntity>> GetListAsync(ISpecification<TEntity>? specification = default);

/// <summary>
/// Adds new entry to database.
/// </summary>
/// <param name="entity">Entity object</param>
Task AddAsync(TEntity entity);

/// <summary>
/// Updates existing entry.
/// </summary>
/// <param name="entity">Entity object</param>
void Update(TEntity entity);

/// <summary>
/// Deletes entity object from database.
/// </summary>
/// <param name="entity">Entity object</param>
void Delete(TEntity? entity);
}
14 changes: 14 additions & 0 deletions src/Core/Logistics.Domain/Persistence/IMasterUnityOfWork.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Logistics.Domain.Core;

namespace Logistics.Domain.Persistence;

public interface IMasterUnityOfWork : IDisposable
{
IMasterRepository2<TEntity> Repository<TEntity>() where TEntity : class, IEntity<string>;

/// <summary>
/// Save changes to database
/// </summary>
/// <returns>Number of rows modified after save changes.</returns>
Task<int> CommitAsync();
}
27 changes: 27 additions & 0 deletions src/Core/Logistics.Domain/Persistence/ITenantRepository2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Logistics.Domain.Core;
using Logistics.Domain.Entities;

namespace Logistics.Domain.Persistence;

/// <summary>
/// Tenant's repository.
/// </summary>
public interface ITenantRepository2<TEntity> : IMasterRepository2<TEntity> where TEntity : class, ITenantEntity
{
/// <summary>
/// Gets the current tenant data.
/// </summary>
Tenant GetCurrentTenant();

/// <summary>
/// Manually set the current tenant by its ID
/// </summary>
/// <param name="tenantId">Tenant ID</param>
void SetCurrentTenantById(string tenantId);

/// <summary>
/// Manually set the current tenant by directly passing the instance
/// </summary>
/// <param name="tenant">An instance of Tenant class</param>
void SetCurrentTenant(Tenant tenant);
}
14 changes: 14 additions & 0 deletions src/Core/Logistics.Domain/Persistence/ITenantUnityOfWork.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Logistics.Domain.Core;

namespace Logistics.Domain.Persistence;

public interface ITenantUnityOfWork : IDisposable
{
ITenantRepository2<TEntity> Repository<TEntity>() where TEntity : class, ITenantEntity;

/// <summary>
/// Save changes to database
/// </summary>
/// <returns>Number of rows modified after save changes.</returns>
Task<int> CommitAsync();
}
2 changes: 1 addition & 1 deletion src/Core/Logistics.Domain/Persistence/IUnitOfWork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ public interface IUnitOfWork
/// </summary>
/// <returns>Number of rows modified after save changes.</returns>
Task<int> CommitAsync();
}
}
48 changes: 36 additions & 12 deletions src/Core/Logistics.Domain/Specifications/BaseSpecification.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,45 @@
using System.Linq.Expressions;
using Logistics.Domain.Core;

namespace Logistics.Domain.Specifications;

public abstract class BaseSpecification<TEntity> :
ISpecification<TEntity> where TEntity : class, IEntity<string>
public abstract class BaseSpecification<T> : ISpecification<T>
{
protected BaseSpecification()
public Expression<Func<T, bool>>? Criteria { get; protected set; }
public List<Expression<Func<T, object>>> Includes { get; } = new();
public List<string> IncludeStrings { get; } = new();
public Expression<Func<T, object?>>? OrderBy { get; protected set; }
public Expression<Func<T, object>>? GroupBy { get; protected set; }
public int PageSize { get; private set; }
public int Page { get; private set; }
public bool IsPagingEnabled { get; private set; }
public bool Descending { get; protected set; }

protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
{
Criteria = entity => true;
OrderBy = entity => entity.Id;
Includes = new List<Expression<Func<TEntity, object>>>();
Descending = false;
Includes.Add(includeExpression);
}

public Expression<Func<TEntity, bool>> Criteria { get; protected init; }
public List<Expression<Func<TEntity, object>>> Includes { get; }
public Expression<Func<TEntity, object?>> OrderBy { get; protected init; }
public bool Descending { get; protected init; }
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}

protected virtual void ApplyPaging(int page, int pageSize)
{
Page = page;
PageSize = pageSize;
IsPagingEnabled = true;
}

protected virtual void ApplyOrderBy(Expression<Func<T, object?>> orderByExpression, bool descending = false)
{
OrderBy = orderByExpression;
Descending = descending;
}


protected virtual void ApplyGroupBy(Expression<Func<T, object>> groupByExpression)
{
GroupBy = groupByExpression;
}
}
9 changes: 7 additions & 2 deletions src/Core/Logistics.Domain/Specifications/ISpecification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ namespace Logistics.Domain.Specifications;

public interface ISpecification<T>
{
Expression<Func<T, bool>> Criteria { get; }
Expression<Func<T, bool>>? Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
Expression<Func<T, object?>> OrderBy { get; }
List<string> IncludeStrings { get; }
Expression<Func<T, object?>>? OrderBy { get; }
public Expression<Func<T, object>>? GroupBy { get; }
int PageSize { get; }
int Page { get; }
bool IsPagingEnabled { get; }
bool Descending { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Linq.Expressions;
using Logistics.Domain.Core;
using Logistics.Domain.Persistence;
using Logistics.Domain.Specifications;
using Logistics.Infrastructure.EF.Data;
using Microsoft.EntityFrameworkCore;

namespace Logistics.Infrastructure.EF.Persistence;

public class MasterRepository2<TEntity> : IMasterRepository2<TEntity>
where TEntity : class, IEntity<string>
{
private readonly MasterDbContext _masterDbContext;

public MasterRepository2(MasterDbContext masterDbContext)
{
_masterDbContext = masterDbContext;
}

public Task<int> CountAsync(Expression<Func<TEntity, bool>>? predicate = default)
{
if (predicate is null)
{
return _masterDbContext.Set<TEntity>().CountAsync();
}

return _masterDbContext.Set<TEntity>().CountAsync(predicate);
}

public Task<TEntity?> GetByIdAsync(string id)
{
return _masterDbContext.Set<TEntity>().FindAsync(id).AsTask();
}

public Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate)
{
return _masterDbContext.Set<TEntity>().FirstOrDefaultAsync(predicate);
}

public Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate)
{
return _masterDbContext.Set<TEntity>().Where(predicate).ToListAsync();
}

public Task<List<TEntity>> GetListAsync(ISpecification<TEntity>? specification = default)
{
if (specification is null)
{
return _masterDbContext.Set<TEntity>().ToListAsync();
}

return SpecificationEvaluator<TEntity>.GetQuery(_masterDbContext.Set<TEntity>(), specification).ToListAsync();
}

public Task AddAsync(TEntity entity)
{
return _masterDbContext.Set<TEntity>().AddAsync(entity).AsTask();
}

public void Update(TEntity entity)
{
_masterDbContext.Set<TEntity>().Update(entity);
}

public void Delete(TEntity? entity)
{
if (entity is null)
{
return;
}

_masterDbContext.Set<TEntity>().Remove(entity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Collections;
using Logistics.Domain.Core;
using Logistics.Domain.Persistence;
using Logistics.Infrastructure.EF.Data;

namespace Logistics.Infrastructure.EF.Persistence;

public class MasterUnitOfWork : IMasterUnityOfWork
{
private readonly MasterDbContext _masterDbContext;
private readonly Hashtable _repositories = new();

public MasterUnitOfWork(MasterDbContext masterDbContext)
{
_masterDbContext = masterDbContext;
}

public IMasterRepository2<TEntity> Repository<TEntity>() where TEntity : class, IEntity<string>
{
var type = typeof(TEntity).Name;

if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(MasterRepository2<>);

var repositoryInstance =
Activator.CreateInstance(repositoryType
.MakeGenericType(typeof(TEntity)), _masterDbContext);

_repositories.Add(type, repositoryInstance);
}

if (_repositories[type] is not IMasterRepository2<TEntity> repository)
{
throw new InvalidOperationException("Could not create a repository");
}

return repository;
}

public Task<int> CommitAsync()
{
return _masterDbContext.SaveChangesAsync();
}

public void Dispose()
{
_masterDbContext.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Logistics.Domain.Core;
using Logistics.Domain.Specifications;
using Microsoft.EntityFrameworkCore;

namespace Logistics.Infrastructure.EF.Persistence;

public static class SpecificationEvaluator<TEntity> where TEntity : class, IEntity<string>
{
public static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery, ISpecification<TEntity> specification)
{
var query = inputQuery;

// modify the IQueryable using the specification's criteria expression
if (specification.Criteria != null)
{
query = query.Where(specification.Criteria);
}

// Includes all expression-based includes
query = specification.Includes.Aggregate(query,
(current, include) => current.Include(include));

// Include any string-based include statements
query = specification.IncludeStrings.Aggregate(query,
(current, include) => current.Include(include));

// Apply ordering if expressions are set
if (specification.OrderBy != null)
{
query = specification.Descending
? query.OrderByDescending(specification.OrderBy)
: query.OrderBy(specification.OrderBy);
}

if (specification.GroupBy != null)
{
query = query.GroupBy(specification.GroupBy).SelectMany(x => x);
}

// Apply paging if enabled
if (specification.IsPagingEnabled)
{
query = query.Skip((specification.Page - 1) * specification.PageSize)
.Take(specification.PageSize);
}
return query;
}
}
Loading

0 comments on commit 12fa81c

Please sign in to comment.