Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.net core - unit of work generic repository pattern

I am Looking to migrate a large project that gathers information about products. The current application uses CSLA as its framework (traditional aspx forms).

I am wanting to migrate / develop the application over to .net core 2.1. I have experience of developing in MVC (4+ years) and some recent exposure to .net core.

I am wanting to use EF Core to call the existing stored procedures. I am looking at using the Unit of Work Generic repository design pattern. I have used the repository pattern before and found it very useful.

As part of the functionality that exists within the current process, it contains a Master table and an Edits table structure format. When someone edits a product, a new row is inserted into the Edits table. Once approved by an admin, it then inserts / updates the current row in the Master table.

Has using the ‘Unit of Work / generic repository pattern’ worked well for other developers within .net core 2.1?

What issues have you come across? My aim is to produce a high performance efficient driven application.

Any other thoughts and suggestions are welcome. Thanks.

like image 262
Paul Avatar asked Sep 21 '18 08:09

Paul


People also ask

What is Unit of Work pattern C# .NET Core?

Unit of Work is like a business transaction. This pattern will merge all CRUD transactions of Repositories into a single transaction. All changes will be committed only once. The main advantage is that application layer will need to know only one class (Unit of Work) to access each repository.

What is relationship between repository and Unit of Work?

Unit of Work is the concept related to the effective implementation of the repository pattern. non-generic repository pattern, generic repository pattern. Unit of Work is referred to as a single transaction that involves multiple operations of insert/update/delete and so on.

What is EF core work unit?

The unit of work class coordinates the work of multiple repositories by creating a single database context class shared by all of them.

Is DbContext a unit of work?

A DbContext instance represents a combination of the Unit Of Work and Repository patterns such that it can be used to query from a database and group together changes that will then be written back to the store as a unit.


2 Answers

Personally, i use Unit of Work to reduce a lot of dependency Injection. I can have a database Unit of work and once i use dependency Injection to inject the Database context in that Unit of work, i don't need to inject each model repository where i want to use them, but will just access the repositories from the unit of work. This also helps me only instantiate a repository only when i need it in a specific method.

public interface IDatabaseUnitOfWork
{
    DbContext DatabaseContext { get; }
    Task<bool> Save();

    IBaseRepository<UserAccount> UserAccountRepository { get; }
}

public class DatabaseUnitOfWork : IDatabaseUnitOfWork
{
    private IBaseRepository<UserAccount> _userAccountRepository;

    public DatabaseUnitOfWork(DbContext databaseContext)
    {
        DatabaseContext = databaseContext;
    }

    public DbContext DatabaseContext { get; private set; }

    public async Task<bool> Save()
    {
        try
        {
            int _save = await DatabaseContext.SaveChangesAsync();
            return await Task.FromResult(true);
        }
        catch (System.Exception e)
        {
            return await Task.FromResult(false);
        }
    }

    public IBaseRepository<UserAccount> UserAccountRepository
    {
        get
        {
            if (_userAccountRepository == null)
            {
                _userAccountRepository = new BaseRepository<UserAccount>(DatabaseContext);
            }
            return _userAccountRepository;
        }
    }
}

Then

services.AddScoped<IDatabaseUnitOfWork, DatabaseUnitOfWork>();
services.AddScoped<IServiceUnitOfWork, ServiceUnitOfWork>();

Finally

public class DemoClass
    {
        private IServiceUnitOfWork _serviceUnitOfWork;
        public DemoClass(IServiceUnitOfWork serviceUnitOfWork)
        {
            _serviceUnitOfWork = serviceUnitOfWork;
        }

        Public bool CreateUserAccount(UserAccount userAccount){
            await _serviceUnitOfWork.UserAccountRepository.Add(userAccount);
            return await _serviceUnitOfWork.Save();
        }

       ----
   }

UPDATE

GENERIC BASE REPOSITORY

public interface IBaseRepository<T> where T : class
{
    Task<bool> Add(T entity);

    Task<List<T>> GetAll();

    Task<List<T>> GetAll(params Expression<Func<T, object>>[] includes);

    Task<List<T>> SearchBy(Expression<Func<T, bool>> searchBy, params Expression<Func<T, object>>[] includes);

    Task<T> FindBy(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes);

    Task<bool> Update(T entity);

    Task<bool> Delete(Expression<Func<T, bool>> identity, params Expression<Func<T, object>>[] includes);

    Task<bool> Delete(T entity);

}

public class BaseRepository<T> : IBaseRepository<T> where T : class
{
    private DbContext _ctx;

    public BaseRepository(DbContext context)
    {
        _ctx = context;
    }

    public virtual async Task<bool> Add(T entity)
    {
        try
        {
            _ctx.Set<T>().Add(entity);
            return  await Task.FromResult(true);
        }
        catch (Exception e)
        {
            return  await Task.FromResult(false);
        }
    }

    public virtual async Task<List<T>> GetAll()
    {
        return _ctx.Set<T>().ToList();
    }

    public virtual async Task<List<T>> GetAll(params Expression<Func<T, object>>[] includes)
    {
        var result = _ctx.Set<T>().Where(i => true);

        foreach (var includeExpression in includes)
            result = result.Include(includeExpression);

        return await result.ToListAsync();
    }


    public virtual async Task<List<T>> SearchBy(Expression<Func<T, bool>> searchBy, params Expression<Func<T, object>>[] includes)
    {
        var result = _ctx.Set<T>().Where(searchBy);

        foreach (var includeExpression in includes)
            result = result.Include(includeExpression);

        return await result.ToListAsync();
    }

    /// <summary>
    /// Finds by predicate.
    /// http://appetere.com/post/passing-include-statements-into-a-repository
    /// </summary>
    /// <param name="predicate">The predicate.</param>
    /// <param name="includes">The includes.</param>
    /// <returns></returns>
    public virtual async Task<T> FindBy(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes)
    {
        var result = _ctx.Set<T>().Where(predicate);

        foreach (var includeExpression in includes)
            result = result.Include(includeExpression);

        return await result.FirstOrDefaultAsync();
    }

    public virtual async Task<bool> Update(T entity)
    {
        try
        {
            _ctx.Set<T>().Attach(entity);
            _ctx.Entry(entity).State = EntityState.Modified;

            return  await Task.FromResult(true);
        }
        catch (Exception e)
        {
            return  await Task.FromResult(false);
        }
    }

    public virtual async Task<bool> Delete(Expression<Func<T, bool>> identity, params Expression<Func<T, object>>[] includes)
    {
        var results = _ctx.Set<T>().Where(identity);

        foreach (var includeExpression in includes)
            results = results.Include(includeExpression);
        try
        {
            _ctx.Set<T>().RemoveRange(results);
            return  await Task.FromResult(true);
        }
        catch (Exception e)
        {
            return  await Task.FromResult(false);
        }
    }

    public virtual async Task<bool> Delete(T entity)
    {
        _ctx.Set<T>().Remove(entity);
        return await Task.FromResult(true);
    }

}

EXTENDING THE BASE REPOSITORY (eg. DeleteAllAccounts)

public interface IUserAccountRepository : IBaseRepository<UserAccount>
    {
        Task DeleteAllAccounts();
    }

    public class UserAccountRepository : BaseRepository<UserAccount>, IUserAccountRepository
    {
        private DbContext _databaseContext;
        public UserAccountRepository(DbContext databaseContext) : base(databaseContext)
        {
            _databaseContext = databaseContext;
        }

        public async Task DeleteAllAccounts()
        {
            ......
        }
    }

So instead of using _userAccountRepository = new BaseRepository<UserAccount>(DatabaseContext); you would use _userAccountRepository = new UserAccountRepository(DatabaseContext);

like image 50
McKabue Avatar answered Oct 19 '22 22:10

McKabue


I'm going with a classic "it depends". Essentially DbSet<> is a repository, and DbContext is a unit of work. But that doesn't mean you can't use the repository pattern or unit of work pattern at a higher level of abstraction.

My advice is don't use it until you need it. I have used a unit or work pattern with EF Core and found it very useful in the situation where you are working with multiple DbContexts, or multiple database providers. I haven't found any other instance to use it in.

I tend not to use a generic repository ever, because I prefer to encapsulate my queries, and keep them out of my Controller or Pages.


Updated to answer the comment below:

That's a great question, and not so easily answered, and certainly out of the scope of StackOverflow.com, because craftsmanship is generally considered opinion. There's a stack exchange for questions directly related to code craftsmanship https://softwareengineering.stackexchange.com.

I will however say how excited for you I am that you are at least raising the question.

My personal opinion is to recommend 'Domain Driven Design' There are a lot of free resources on the subject, but the go to source would be the book by Eric Evans.

In essence, your business logic is the core, and all dependencies point inward. So even a 'service layer' would not have your business logic in it.

like image 40
Adam Vincent Avatar answered Oct 20 '22 00:10

Adam Vincent