Search code examples
asp.net-mvcentity-frameworkautofac

SaveChanges() and adding to DB not working


I am working on adding Entity Framework to our web app, asp.net MVC 5, but I am having a hardtime saving changes and adding to the database. I set up UnitOfWork with a generic BaseRepository, and I have tried a few things attempting to get this to work. first, I thought I could inject, with AutoFac, my repo in UnitOfWork like so

public UnitOfWork(IServiceItem serviceItem
        , ITechServiceItem techServiceItem
        , ITechnicianTime technicianTime
        , ISproc sproc
        , IRepairOrder repairOrder
        , ICustomer customer
         , IRepairOrderStatus repairOrderStatus
        , IRepairOrderUnit repairOrderUnit
        , IFiles files
        , IPartInventory partInventory
        , IRepairOrderItems repairOrderItems
        )
        {
            RepairOrderItems = repairOrderItems;
            PartInventory = partInventory;
            Files = files;
            RepairOrderUnit = repairOrderUnit;
            RepairOrderStatus = repairOrderStatus;
            RepairOrder = repairOrder;
            Customer = customer;
            Sproc = sproc;
            ServiceItem = serviceItem;
            TechServiceItem = techServiceItem;
            TechnicianTime = technicianTime;
        }

and my BaseRepo is like

public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        protected DataDbContext _db;

       public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        protected DataDbContext _db;

        internal void GetData()
        {
            if (_db == null)
            {
                string accountNumber = HttpContext.Current.User.Identity.GetCompanyAccountNumber();
                var connectionToken = ConfigurationManager.AppSettings["LoginSplitToken"];
                _db = new DataDbContext(ConfigurationManager.ConnectionStrings["NameOfConnString"].ConnectionString.Replace(connectionToken, accountNumber));
            }
        }

        public TEntity Get(int id)
        {
            return _db.Set<TEntity>().Find(id);
        }

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

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

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

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

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

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

        public int CompleteData()
        {
            return _db.SaveChanges();
        }

        public TEntity Get(int id)
        {
            return _db.Set<TEntity>().Find(id);
        }

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

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

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

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

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

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

        public int CompleteData()
        {
            return _db.SaveChanges();
        }
}

and my StartUp.Configuration

 public void Configuration(IAppBuilder app)
        {
            var builder = new ContainerBuilder();
            HttpConfiguration config = GlobalConfiguration.Configuration;

            // REGISTER DEPENDENCIES
            builder.RegisterType<EverLogicDbContext>().AsSelf().InstancePerRequest();
            builder.RegisterType<ApplicationUserManager>().AsSelf().InstancePerRequest();
            builder.RegisterType<ApplicationSignInManager>().AsSelf().InstancePerRequest();
            builder.Register(c => HttpContext.Current.GetOwinContext().Authentication).InstancePerRequest();
            builder.Register(c => HttpContext.Current.User).InstancePerRequest();
            builder.Register(c => app.GetDataProtectionProvider()).InstancePerRequest();

            builder.RegisterType<ApplicationUserStore>().As<IUserStore<EverLogicMamber, int>>()
                  .WithParameter(new TypedParameter(typeof(ISecurityOfWork), new SecurityOfWork(new SecurityDbContext())))
                .InstancePerRequest();

            //Database
            builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
            builder.RegisterType<SecurityOfWork>().As<ISecurityOfWork>().InstancePerRequest();
            //Service
            builder.RegisterType<TechnicianTimeService>().As<ITechnicianTimeService>().InstancePerRequest();
            builder.RegisterType<PartService>().As<IPartService>().InstancePerRequest();
            builder.RegisterType<TechServiceItemService>().As<ITechServiceItemService>().InstancePerRequest();
            //Repo
            builder.RegisterType<Company>().As<ICompany>().InstancePerRequest();
            builder.RegisterType<Views>().As<IViews>().InstancePerRequest();

            builder.RegisterType<RepairOrderItems>().As<IRepairOrderItems>().InstancePerRequest();
            builder.RegisterType<PartInventory>().As<IPartInventory>().InstancePerRequest();
            builder.RegisterType<Files>().As<IFiles>().InstancePerRequest();
            builder.RegisterType<TechDashboardService>().As<ITechDashboardService>().InstancePerRequest();
            builder.RegisterType<RepairOrderUnit>().As<IRepairOrderUnit>().InstancePerRequest();
            builder.RegisterType<RepairOrderStatus>().As<IRepairOrderStatus>().InstancePerRequest();
            builder.RegisterType<Customer>().As<ICustomer>().InstancePerRequest();
            builder.RegisterType<ServiceItem>().As<IServiceItem>().InstancePerRequest();
            builder.RegisterType<RepairOrder>().As<IRepairOrder>().InstancePerRequest();
            builder.RegisterType<Sproc>().As<ISproc>().InstancePerRequest();
            builder.RegisterType<TechServiceItem>().As<ITechServiceItem>().InstancePerRequest();
            builder.RegisterType<TechnicianTime>().As<ITechnicianTime>().InstancePerRequest();

            // REGISTER CONTROLLERS SO DEPENDENCIES ARE CONSTRUCTOR INJECTED
            builder.RegisterControllers(typeof(MvcApplication).Assembly);
            builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
            builder.RegisterWebApiFilterProvider(config);
            builder.RegisterWebApiModelBinderProvider();

            var container = builder.Build();

            // REPLACE THE MVC DEPENDENCY RESOLVER WITH AUTOFAC
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

            app.UseAutofacMiddleware(container);
            app.UseAutofacMvc();
            config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

            ConfigureAuth(app);
        }

But with this set up, the database does not update or add new entitys. Then i tryed removing Dependcy injection from UnitOfWork and set UnitOfWork up like

 protected DataDbContext _db;
      public UnitOfWork(DataDbContext context)
        {
            GetData();
            RepairOrderItems = new RepairOrderItems(_db);
            PartInventory = new PartInventory(_db);
            Files = new Files(_db);
            RepairOrderUnit = new RepairOrderUnit(_db);
            RepairOrderStatus = new RepairOrderStatus(_db);
            RepairOrder = new RepairOrder(_db);
            Customer = new Customer(_db);
            Sproc = new Sproc(_db);
            ServiceItem = new ServiceItem(_db);
            TechServiceItem = new TechServiceItem(_db);
            TechnicianTime = new TechnicianTime(_db);
        }
  internal void GetData()
        {
            if (_db == null)
            {
                string accountNumber = HttpContext.Current.User.Identity.GetCompanyAccountNumber();
                var connectionToken = ConfigurationManager.AppSettings["LoginSplitToken"];
                _db = new DataDbContext(ConfigurationManager.ConnectionStrings["NameOfConnString"].ConnectionString.Replace(connectionToken, accountNumber));
            }
        }

and moving SaveChanges from the BaseRepo to UnitOfWork, but still nothing is saving or adding to the database. What am i missing????


Solution

  • TL;DR the problem is that all your repositories are using separate, independent DbContexts, so the DbContext injected into your UnitOfWork has no pending changes when you call SaveChanges on it, so that's why you aren't seeing any change to the database.

    In order for the Unit of Work to function correctly, your UnitOfWork class, and all the repository classes which your code needs to perform data persistence, must all share the same DbContext instance. In your code, it's clear that each repository has a factory method to create it's own, independent DbContext instance.

    • Remove the GetData() factory method from your BaseRepository class, and instead, require an instance of your EverLogicDbContext instance to injected to the constructor of BaseRepository by AutoFac. This will require that all your Repository subclasses also need to have a constructor accepting this same EverLogicDbContext.
    • As per your last edit, the UnitOfWork class must accept the same, shared EverLogicDbContext that the repositories use. Since you've tagged with asp.net-mvc then RequestPerInstance lifetime scope is correct for your scenario.
    • Your UnitOfWork class needs to control the SaveChanges(Async) method, so remove the CompleteData method from the BaseRepository class.
    • As you already seem to have done, the DbContext needs to be registered InstancePerRequest:

    builder.RegisterType<EverLogicDbContext>().AsSelf().InstancePerRequest();
    

    If all this is tied together correctly:

    • AutoFac will create an instance of your concrete DbContext the first time it is needed during processing of each Request.
    • All Repositories will then share the same DbContext instance for the lifetime of the Request, and the DbContext will track interim changes made by your services.
    • The UnitOfWork injected into your main "business logic" (e.g. Controller, or Orchestrator / Handler) will then be able to Commit the actions taken by simply calling SaveChangesAsync on the shared DbContext. This will all happen under a single database connection, so will be a lightweight transaction.

    As per other comments above, IMO Entity Framework is a already high level framework with transactional support built-in, so there's little point in over-engineering a "UnitOfWork" pattern if all the ACID activity will be conducted against the same Database (and can be wrapped into the same DbContext).