Search code examples
c#asp.net-corerepository-patternunit-of-work

How to implement unit of work pattern with IServiceProvider injected in repositories?


I have to implement unit of work pattern in my project, but I coudn't find proper way to do it using in memory database. I have two repositories:

public class RepositoryA : IRepositoryA
{
    private readonly IServiceScope _scope;
    private readonly ApiContext _apiContext;

    public RepositoryA(IServiceProvider services)
    {
        _scope = services.CreateScope();
        _apiContext = _scope.ServiceProvider.GetRequiredService<ApiContext>();
    }

    //some methods
}

public class RepositoryB : IRepositoryB
{
    private readonly IServiceScope _scope;
    private readonly ApiContext _apiContext;

    public RepositoryB(IServiceProvider services)
    {
        _scope = services.CreateScope();
        _apiContext = _scope.ServiceProvider.GetRequiredService<ApiContext>();
    }

    //some methods
}

I found some good example of unit of work pattern which is working on other project.

public interface IUnitOfWork
{
   public IRepositoryA IRepositoryA { get; }
   public IRepositoryB IRepositoryB { get; }

   void SaveChanges();
}

 public class UnitOfWork : IUnitOfWork
    {
        private ApiContext _apiContext;
        private IRepositoryA _repositoryA ;
        private IRepositoryB _repositoryB ;

        public UnitOfWork(ApiContext apiContext)
        {
            _apiContext = apiContext;
        }

        public IRepositoryA RepositoryA 
        {
            get
            {
                if(_repositoryA == null)
                {
                    _repositoryA = new RepositoryA(_apiContext);
                }
                return _repositoryA ;
            }
        }

       public IRepositoryB RepositoryB 
        {
            get
            {
                if(_repositoryB == null)
                {
                    _repositoryB = new RepositoryB(_apiContext);
                }
                return _repositoryB;
            }
        }

        public void SaveChanges()
        {
            _apiContext.SaveChangesAsync();
        }
    }

In my Startup.cs I have this in my ConfigureServices method:

services.AddSingleton<IUnitOfWork, UnitOfWork>();
services.AddSingleton<IRepositoryA, RepositoryA>();
services.AddSingleton<IRepositoryB, RepositoryB>();
services.AddDbContext<ApiContext>(options => options.UseInMemoryDatabase("Database"));

In this example, unit of work is trying to pass apiContext to the repositories constructors. I cannot do this because both repositories are not expecting apiContext in constructor, but IServiceProvider. When I tried to create my repositories like this:

public RepositoryA(ApiContext apiContext)
{
    _apiContext = apiContext;
}

I got an error:

InvalidOperationException: Cannot consume scoped service Project.Data.ApiContext' from singleton Project.Repositories.Interface.IRepositoryA.

This error is because of in memory database. So I need to inject IServiceProvider inside constructors of the repositories and to create scope and this is working without unit of work. But now I need unit of work and I don't know how to implement it. Is there any way to implement unit of work differently, or to change my repositories?


Solution

  • As the error shows, you should not inject scoped lifetime dependencies into singletons. DbContexts are usually registered as scoped.

    If the repositories depend on the context then they should be scoped as well.

    For example.

    services.AddScope<IUnitOfWork, UnitOfWork>(); 
    services.AddScope<IRepositoryA, RepositoryA>(); 
    services.AddScope<IRepositoryB, RepositoryB>();
    services.AddDbContext<ApiContext>(options => options.UseInMemoryDatabase("Database"));