Search code examples
c#asp.net-coredesign-patternsentity-framework-coreunit-of-work

How to combine the three-layer architecture with the unit of work in C#


I use three layer and Unit of work Design pattern together but project have credential bug!

data Read from database but does not save in it. Context Class Can't get changes and CurrentTransaction is null.

Current Transaction

How can I fix this bug?

In this project I have three layer and one protocol for models

  1. Presentation project (dotnet core mvc with name Frontend)
  2. Data project (for save and connect to database and have Repository classes)
  3. Models project (it have my models. it's a global protocol for all layer)
  4. Services project (for connect Presentation to Data and Businesses rules)

Data Code:

it layer have Context and Repository and IRepository

Repository class

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class 
{
    private readonly FrontendContext context;
    private readonly DbSet<TEntity> dbSet;

    public Repository(){
        context = new FrontendContext();
        dbSet = context.Set<TEntity>();
    }

    public void Create (TEntity entity){
        dbSet.Add(entity);
    }

    public IEnumerable<TEntity> GetAll(){
        return  dbSet.ToList();
    }
}

IRepository interface

public interface IRepository<TEntity> where TEntity : class
{
    public void Create (TEntity entity);
    public IEnumerable<TEntity> GetAll();
}

Context class

public class FrontendContext : DbContext
{
    public FrontendContext(): base(){}

    protected override void OnConfiguring(DbContextOptionsBuilder builder){
        builder.UseSqlServer(@"server=(local);Database=CredentialBug;Trusted_Connection=True;TrustServerCertificate=True");
    }

    public DbSet<Person> People { get; set; }
}

Services Code:

it have UnitOfWork and IUnitOfWork

UnitOfWork class

public class UnitOfWork : IUnitOfWork
{
    private readonly FrontendContext context;
    public UnitOfWork(){
        context = new FrontendContext();
    }
    public IRepository<TEntity> RepositoryBase<TEntity>() where TEntity : class {
        return new Repository<TEntity>();
    }
    public void Commit() => context.SaveChanges();
}

IUnitOfWork interface

public interface IUnitOfWork
{
    IRepository<TEntity> RepositoryBase<TEntity>() where TEntity : class ;
    public void Commit();
}

Presentation Code

Add service to program file

builder.Services.AddTransient<IUnitOfWork, UnitOfWork>();

Now I use it in my Presentation like this :

Controller

public class HomeController : Controller
{
    private readonly IUnitOfWork _UW;
    public HomeController(IUnitOfWork UW)
    {
        _UW = UW;
    }

    public IActionResult Index()
    {
        IEnumerable<Person> people = _UW.RepositoryBase<Person>().GetAll();
        return View(people);
    }

    public IActionResult Create(){
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Create(Person model){
        if (!ModelState.IsValid){
            return View(model);
        }
        _UW.RepositoryBase<Person>().Create(model);
        _UW.Commit();
        return RedirectToAction(nameof(Index));
    }
}

I test read data from database and correctly it work but add to database does not work correctly!!!


Solution

  • I found answer and share it.

    its bug created when our sampling Context class for each method. In this moment transaction stay in the previous sample Context and in Context of new method transaction is null, In result commit method does not work.

    we need some changes in this design pattern

    1. Add Context class to Constructor like this

      public class Repository<TEntity> : IRepository<TEntity> where TEntity : class 
      {
          private readonly DbSet<TEntity> dbSet;
      
          //get Context class from UnitOfWork Class
          public Repository(FrontendContext context){
              dbSet = context.Set<TEntity>();
          }
      
          public void Create (TEntity entity){
              dbSet.Add(entity);
          }
      
          public IEnumerable<TEntity> GetAll(){
              return  dbSet.ToList();
          }
      }
      
    2. once for all methods sample Context class and send it to Repository Class

    public class UnitOfWork : IUnitOfWork
    {
        private readonly FrontendContext context;
        public UnitOfWork(){
            context = new FrontendContext();
        }
        public IRepository<TEntity> RepositoryBase<TEntity>() where TEntity : class {
            return new Repository<TEntity>(context); //Send Context class to Repository
        }
        public void Commit() => context.SaveChanges();
    }