Search code examples
c#entity-framework-coreasp.net-core-webapiautofac

Implementing unit of work in Entity Framework Core


I have implemented unit of work in the next way in Entity Framework Core.

Context:

public class DaleContext : DbContext, IDaleContext
{
    private readonly IConnectionStringProvider _connectionStringProvider;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connectionStringProvider.ConnectionString);
        base.OnConfiguring(optionsBuilder);
    }

    public DaleContext(IConnectionStringProvider connectionStringProvider)
    {
        _connectionStringProvider = connectionStringProvider;
    }
    
    public DbSet<ProductProducts { get; set; }

    public override int SaveChanges()
    {
        ChangeTracker.DetectChanges();

        var modifiedEntries = ChangeTracker.Entries()
            .Where(x =x.State == EntityState.Added || x.State == EntityState.Modified).ToList();

        return base.SaveChanges();
    }
}

Unit of work:

public class UnitOfWork : IUnitOfWork
{
    public UnitOfWork(DbContext dbContext)
    {
        DbContext = dbContext;
    }

    public DbContext DbContext { get; set; }

    public int Commit()
    {
        return DbContext.SaveChanges();
    }

    public async Task<intCommitAsync()
    {
        return await DbContext.SaveChangesAsync();
    }
}

Repository:

public class Repository<TEntity: IDisposable, IRepository<TEntity>
where TEntity : class
{
    private readonly UnitOfWork _unitOfWork;

    public Repository(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork as UnitOfWork;
    }

    public void Dispose()
    {
        _unitOfWork.DbContext.Dispose();
    } 

    public void Create(TEntity entity)
    {
        _unitOfWork.DbContext.Entry(entity).State = EntityState.Added;
    }
}

I have injected all with autofac:

public static class Container
{
    public static ContainerBuilder RegisterInfraestructure(this ContainerBuilder containerBuilder)
    {
        containerBuilder.RegisterType<UnitOfWork>().As<IUnitOfWork>();

        return containerBuilder;
    }
}

public static class Container
{
    public static ContainerBuilder RegisterDataResources(this ContainerBuilder containerBuilder)
    {
        var configurationBuilder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json");

        var configuration = configurationBuilder.Build();

        containerBuilder.Register(x =new ConnectionStringProvider(configuration.GetConnectionString("Fgcm.Dale"))
        ).As<IConnectionStringProvider>();

        containerBuilder.RegisterType<DaleContext>().As<DbContext>().As<IDaleContext>();

        return containerBuilder;
    }
} 

public static class Container
{
    public static ContainerBuilder RegisterRepository(this ContainerBuilder containerBuilder)
    {
        containerBuilder.RegisterType<CustomerRepository>().As<ICustomerRepository>();
        containerBuilder.RegisterType<ProductRepository>().As<IProductRepository>();
        containerBuilder.RegisterType<SaleDetailRepository>().As<ISaleDetailRepository>();
        containerBuilder.RegisterType<SaleRepository>().As<ISaleRepository>();
        
        containerBuilder.RegisterDataResources();
        containerBuilder.RegisterInfraestructure();

        return containerBuilder;
    }
}

public static class Container
{
    public static ContainerBuilder RegisterApplicationServiceResources(this ContainerBuilder
    containerBuilder)
    {
        containerBuilder.RegisterRepository();

        containerBuilder.RegisterType<DaleApplicationService>().As<IDaleApplicationService>();

        return containerBuilder;
    }
}

When I try to save data it doesn't works (doesn't insert data) ... I would like to know why ? Here are when I try to save:

    public Product Create(Product product)
    {
        try
        {
            _productRepository.Create(product);
            _unitOfWork.Commit();
            return product;
        }
        catch (Exception e)
        {
            Debug.WriteLine(e);
            return null;
        }
    }

And of course all are injected:

    private readonly ICustomerRepository _customerRepository;
    private readonly IProductRepository _productRepository;
    private readonly ISaleDetailRepository _saleDetailRepository;
    private readonly ISaleRepository _saleRepository;
    private readonly IUnitOfWork _unitOfWork;

    public DaleApplicationService(IProductRepository productRepository, ICustomerRepository customerRepository,
        ISaleRepository saleRepository, ISaleDetailRepository saleDetailRepository, IUnitOfWork unitOfWork)
    {
        _productRepository = productRepository;
        _customerRepository = customerRepository;
        _saleRepository = saleRepository;
        _saleDetailRepository = saleDetailRepository;
        _unitOfWork = unitOfWork;
    }

What am I missing?

PS: all this works with .NET Core Web Api.


Solution

  • As @zolty13 sort of hinted the Instance scope of your DbContext (DaleContext) is probably incorrect. By default Autofac sets the instance scope to Instance per dependency (also known as a "transient" lifetime) which means a new instance of DaleContext is created for every class that depend on it. So your UnitOfWork receives a different instance of DaleContext than IProductRepository. So changes in IProductRepository are not reflected in UnitOfWork.

    One way to solve this is to avoid this convoluted wrapping of your DbContext like @Igor suggest. Do you really need this UnitOfWork? Instead, use a repository class that has one instance of DaleContext and make all the DB changes in there and save them.

    Alternatively (if you really think you need a UnitOfWork) you can register your DaleContext with an instance per request scope. Do note: Entity Framework's DbContext is not thread safe, so if you need to do concurrent work, this is not a safe approach.

    Otherwise, read up on Instance scope.