1
\$\begingroup\$

I am refactoring my existing product's webapi part of the solution. Current Repo pattern does not allow me to add custom functions to a specific repository. In result, most of the functions directly use the Db context in the controller.

I read various articles and come up a good way of implementation to achieve my targets and also best practice. Here I am posting my sample implementation to review and some more guidelines. I don't know if you can help me to review from git repository as I am new to this forum.

Aim/Targets:

  1. Need a generic way of repository and Unit of work pattern so that code can be reused with the advantage of the Repository pattern.
  2. I also have custom functions to each repository and the pattern should accept this extension.

Current Limitations/Future Plans:

  1. The current implementation does not have dependency injection so I am not making complex now. Will check in near future due to the impact.

  2. The current implementation does not use the business layer. So repository class directly calling from API controller. Here I can't check deep into the business functions now so will look into future.

Code:

Now please look into my implementation

Project Structure

Project structure

Data Project Code:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}
public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
    public PaginatedList(IEnumerable<T> source, int pageNumber, int pageSize, int totalCount)
    {

        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        if (pageSize <= 0)
        {
            throw new ArgumentException("pageSize");
        }

        AddRange(source);

        PageNumber = pageNumber < 1 ? 1 : pageNumber;
        PageSize = pageSize;
        TotalCount = totalCount;
        TotalPageCount = (int)Math.Ceiling(totalCount / (double)pageSize);
    }
    public int PageNumber { get; private set; }
    public int PageSize { get; private set; }
    public int TotalCount { get; private set; }
    public int TotalPageCount { get; private set; }

    public bool HasPreviousPage
    {

        get
        {
            return (PageNumber > 1);
        }
    }

    public bool HasNextPage
    {

        get
        {
            return (PageNumber < TotalPageCount);
        }
    }

}
    public SampleContext() : base("Name=SampleContext")
    {

    }
    public IDbSet<User> Users { get; set; }
    public IDbSet<Item> Items { get; set; }

}

Repository Project Code:

Interfaces

    public interface IRepository<T> where T : class
{
    Task<IEnumerable<T>> GetAll();
    Task<PaginatedList<T>> GetPaged(Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, int pageIndex, int pageSize, Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null);
    Task<IEnumerable<T>> FindByAsync(Expression<Func<T, bool>> expression);
    Task<T> Get(object id);
    void Create(T entity);
    void Update(T entity);
}
public interface IUnitOfWork
{
    IUserRepository Users { get; }
    IItemRepository Items { get; }
    void SaveAsync();
}
public interface IUserRepository : IRepository<User>
{
    User GetByName(string name);
}

public interface IItemRepository : IRepository<Item>
{
}

Repositories

public abstract class Repository<T> : IRepository<T>
    where T : class
{
    private readonly DbSet<T> _dbSet;

    protected SampleContext Context { get; set; }

    protected Repository(SampleContext context)
    {
        Context = context;
        _dbSet = Context.Set<T>();
    }

    public virtual async Task<IEnumerable<T>> GetAll()
    {
        return await _dbSet.ToListAsync();
    }

    public virtual async Task<PaginatedList<T>> GetPaged(Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, int pageIndex, int pageSize, Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null)
    {
        var query = orderBy(_dbSet);
        int totalCount = query.Count();
        var collection = await query.Skip((pageIndex - 1) * pageSize).Take(pageSize).AsNoTracking().ToListAsync();
        return new PaginatedList<T>(collection, pageIndex, pageSize, totalCount);
    }


    public virtual async Task<IEnumerable<T>> FindByAsync(Expression<Func<T, bool>> expression)
    {
        return await _dbSet.Where(expression).ToListAsync();
    }

    public virtual async Task<T> Get(object id)
    {
        return await _dbSet.FindAsync(id);
    }


    public virtual void Create(T entity)
    {
        _dbSet.Add(entity);
    }

    public virtual void Update(T entity)
    {
        _dbSet.Attach(entity);
        Context.Entry(entity).State = EntityState.Modified;
    }

}
public class UserRepository : Repository<User>, IUserRepository
{
    public UserRepository(SampleContext context) : base(context)
    {

    }
    public User GetByName(string name)
    {
        return Context.Users.FirstOrDefault(x => x.Name == name);
    }
}
    public ItemRepository(SampleContext context) : base(context)
    {
    }

API ProjectCode:

    public class BaseController : ApiController
{
    protected IUnitOfWork UnitOfWork { get; set; }
    public BaseController()
    {
        UnitOfWork = new UnitOfWork();
    }
}
public class UserController : BaseController
{
    public UserController()
    {
    }

    [Route("api/users/all", Name = "UsersCollection")]
    public async Task<IHttpActionResult> GetAll([FromUri]int page = 1, int pageSize = 20)
    {
        var items = await UnitOfWork.Users.GetPaged(x => x.OrderBy(y => y.Id), page, pageSize);
        //Add mapping to DTO 
        return Ok(items);
    }

    [Route("api/users/{name}", Name = "UserByName")]
    public IHttpActionResult GetByName(string name)
    {
        var item = UnitOfWork.Users.GetByName(name);
        if(item == null)
        {
            return StatusCode(HttpStatusCode.NoContent);
        }
        //Add mapping to DTO 
        return Ok(item);
    }

}

My Questions:

  1. Please review my project structure, namings, and implementation for any code smell?

  2. I didn't create an interface for Paginated List because of the private set. Please suggest any good approach for this if any? Also, this class is not part of the database but I keep it in the Model folder itself.

  3. Any code smell in the use of UnitOfWork class initialization in the base controller? If so, while initializing all repositories in the UnitOFWork class is initialized unnecessarily?

  4. UnitOfWork class is implemented IDisposable so can I implement dispose in the base controller to dispose UniOfWork class? As I implement IDisposable in the class itself, I can't use dispose method from IUnitOfWork interface object

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.