mercredi 10 février 2021

Using UnitOfWork, Repository pattern and DI (Composer Umbraco) at the same time in MVC applications

I am just playing around to understand the concepts of UoW, Abstract Repositories and DI (Services) a little bit better and how to mix those techniques, without getting issues with the DataContext.

I created a simple project as following:

Dependency injection First I'am using Umbraco's IoC system, called composer, here I am registered my Services, DataContext and UnitOfWork.

        composition.Register(factory => new DataContext(), Lifetime.Request);
        composition.Register<IUnitOfWork, UnitOfWork>(Lifetime.Request);
        composition.Register<ICalculationService, CalculationService>(Lifetime.Request);
        composition.Register<IReportService, ReportService>(Lifetime.Request);

UnitOfWork (pretty default)

    public class UnitOfWork : IUnitOfWork
{
    private readonly DataContext _context;

    public UnitOfWork(DataContext context)
    {
        _context = context;
        Students = new StudentRepository(_context);
        Groups = new GroupRepository(_context);
    }

    public IStudentRepository Students { get; private set; }
    public IGroupRepository Groups { get; private set; }

    public int Complete()
    {
        return _context.SaveChanges();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

 public interface IUnitOfWork : IDisposable
{
    IStudentRepository Students { get; }
    IGroupRepository Groups { get; }
    int Complete();
}

Repositories

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    protected readonly DbContext Context;

    public Repository(DbContext context)
    {
        Context = context;
    }

    public TEntity Get(Guid id)
    {
        return Context.Set<TEntity>().Find(id);
    }

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

    public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
    {
        return Context.Set<TEntity>().Where(predicate);
    }

    public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
    {
        return Context.Set<TEntity>().SingleOrDefault(predicate);
    }

    public void Add(TEntity entity)
    {
        Context.Set<TEntity>().Add(entity);
    }

    public void AddRange(IEnumerable<TEntity> entities)
    {
        Context.Set<TEntity>().AddRange(entities);
    }

    public void Remove(TEntity entity)
    {
        Context.Set<TEntity>().Remove(entity);
    }

    public void RemoveRange(IEnumerable<TEntity> entities)
    {
        Context.Set<TEntity>().RemoveRange(entities);
    }
}

In the Controllers and Services I inject UnitOfWork.

    private readonly IUnitOfWork _unitOfWork;
    private readonly IReportService _reportService;

    public CalculationService(IUnitOfWork unitOfWork, IReportService reportService)
    {
        _unitOfWork = unitOfWork;
        _reportService = reportService;
    }

This approach is working almost fine. Only in case of using a Controller/Service method where I use UnitOfWork to get some data and pass this data/entity to another service and do some CRUD actions EF is crashing and throwing this exception. An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

Probably because both Services actually has two different instances to DataContext and EF does not like that.

So I changed this line in Composer:

composition.Register<IUnitOfWork, UnitOfWork>(Lifetime.Request); 

To

composition.Register<IUnitOfWork, UnitOfWork>(Lifetime.Scope);

And the issue seems to be gone.. But is it really fine like this? I want to use it in real life projects and want to be sure of using the right approach.

Thanks.

Aucun commentaire:

Enregistrer un commentaire