none
DbContext Disposing RRS feed

  • Question

  • Hello,

    I use repository pattern where I pass DbContext by constructor. Repository implements IDisposable and dispose DbContext. So I used it as:

    using (var repository = new MyRepository(new DbContext()))
    {
      var data = repository.GetSomeData();
      myBusinness.DoSomeBusinessRules(data);
      repository.SaveChanges();
    }

    It is implementation of my controller in asp.net core application. I use EntityFrameworkCore.Triggers nuget which allows me to know if entity is changed or not and I can set up some properties before saving. I one case I use this triggers to check if entity is changed and if yes I would like to compare version which is in database and which comes from client (view). If I have different versions I throw exception to resolve version conflict.

    public void EntityChange(object sender, EventArgs e)
    {
      var entity = sender as Entity;
      var entityInDb = _repository.DbContext.Entities.Find(entity.Id);
      if (entity.Version != entityInDb.Version)
        throw new ConflictException(entity, entityInDb);

    It works nice. But for first controller action calling only. At second call I got error Cannot access a disposed object. There is stack trace.

       at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
       at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
       at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies.get_EntityFinderFactory()
       at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_Finder()
       at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Find(Object[] keyValues)

    I hit breakpoint in my repository Dispose method. It is called when I try to find entity by primary key. I don't know why DbContext is disposed. Repository SaveChanges method calls DbContext.SaveChanges() only. Not disposing it. I got calling repository.SaveChanges() into try/catch so it means there must be something what dispose DbContext before using block end.


    • Edited by Petr B Saturday, August 18, 2018 6:20 AM
    Saturday, August 18, 2018 6:19 AM

All replies

  • When I don't dispose DbContext in repository class (it implements IDisposable) it works. What could be wrong?
    Saturday, August 18, 2018 6:50 PM
  • If you are using MVC Core and EF Core, then why is the Repository object not DI-ed into the controller? Why is the Dbcontext not instantiated using its own Using statement that disposes Dbcontext? I am not a fan of the generic Repository, becuase it's too generic and you must be using it.

    https://www.infoworld.com/article/3117713/application-development/design-patterns-that-i-often-avoid-repository-pattern.html

    https://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework-core/

    https://blog.sapiensworks.com/post/2012/11/01/Repository-vs-DAO.aspx

    I am also not a fan of an ORM acting as  Data Base Administration tool either. It's total nonsense, and I cringe when I see the usage.

    The below code has the DAO object injected into the API controller. The DAO using the EFDbcontect  has the connection string injected into the DAO, and it in turn the DAO injects the connectionstring object into the Dbcontext. The option object holding the connection string  was create at WebAPI Start.cs. 

    using System.Collections.Generic; using DAL; using Entities; using Microsoft.AspNetCore.Mvc; namespace ProgMgmntCore2Api.Controllers { [Produces("application/json")] [Route("api/[controller]")] [ApiController] public class ProjectController : ControllerBase, IProjectController { private readonly IDaoProject _daoProject; public ProjectController(IDaoProject daoProject) { _daoProject = daoProject; } [HttpGet] [Route("GetProjById")] public DtoProject GetProjectById(int id) { return _daoProject.GetProjectById(id); } [HttpGet] [Route("GetProjsByUserId")] public List<DtoProject> GetProjectsByUserId(string userid) { return _daoProject.GetProjectsByUserId(userid); } [HttpPost] [Route("CreateProject")] public void Post_CreateProject(DtoProject dto) { _daoProject.CreateProject(dto); } [HttpPost] [Route("DeleteProject")] public void Post_DeleteProject(DtoId dto) { _daoProject.DeleteProject(dto.Id); } [HttpPost] [Route("UpdateProject")] public void Post_UpdateProject(DtoProject dto) { _daoProject.UpdateProject(dto); } } } =================================================== using System.Collections.Generic; using System.Linq; using System.Transactions; using DAL.Models.DB; using Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; namespace DAL { public class DaoProject :IDaoProject { private readonly IOptions<ConnectionStrings> _options; public DaoProject(IOptions<ConnectionStrings> options) { _options = options; } public DtoProject GetProjectById(int id) { var dto = new DtoProject(); using (var context = new ProjectManagementContext(_options)) { var project = (context.Projects.Where(a => a.ProjectId == id)).SingleOrDefault(); if (project == null) return dto; dto.ProjectId = project.ProjectId; dto.ClientName = project.ClientName; dto.ProjectName = project.ProjectName; dto.Technology = project.Technology; dto.ProjectType = project.ProjectType; dto.UserId = project.UserId; dto.StartDate = project.StartDate; dto.EndDate = project.EndDate; dto.Cost = project.Cost; } return dto; } public List<DtoProject> GetProjectsByUserId(string userid) { var dtos = new List<DtoProject>(); using (var context = new ProjectManagementContext(_options)) { dtos = (from a in context.Projects.Where(a => a.UserId.Contains(userid)) select new DtoProject { ProjectId = a.ProjectId, ClientName = a.ClientName, ProjectName = a.ProjectName, Technology = a.Technology, ProjectType = a.ProjectType, UserId = a.UserId, StartDate = a.StartDate, EndDate = a.EndDate, Cost = a.Cost }).ToList(); } return dtos; } public void CreateProject(DtoProject dto) { using (var context = new ProjectManagementContext(_options)) { var project = new Projects { ClientName = dto.ClientName, ProjectName = dto.ProjectName, Technology = dto.Technology, ProjectType = dto.ProjectType, UserId = dto.UserId, StartDate = dto.StartDate, EndDate = dto.EndDate, Cost = dto.Cost }; context.Projects.Add(project); context.SaveChanges(); } } public void UpdateProject(DtoProject dto) { var project = new Projects(); using (var context = new ProjectManagementContext(_options)) { project = (context.Projects.Where(a => a.ProjectId == dto.ProjectId)).SingleOrDefault(); } if (project != null) { project.ClientName = dto.ClientName; project.ProjectName = dto.ProjectName; project.Technology = dto.Technology; project.ProjectType = dto.ProjectType; project.UserId = dto.UserId; project.StartDate = dto.StartDate; project.EndDate = dto.EndDate; project.Cost = dto.Cost; } using (var dbcontext = new ProjectManagementContext(_options)) { if (project == null) return; dbcontext.Entry(project).State = EntityState.Modified; dbcontext.SaveChanges(); } } public void DeleteProject(int id) { Projects project; using (var context = new ProjectManagementContext(_options)) { project = (context.Projects.Where(a => a.ProjectId == id)).SingleOrDefault(); } if (project == null) return; using (var newContext = new ProjectManagementContext(_options)) { var tasks = new DaoTask(_options).GetTasksByProjectId(project.ProjectId); using (TransactionScope scope = new TransactionScope()) { foreach (var task in tasks) { new DaoTask(_options).DeleteTask(task.TaskId); } newContext.Entry(project).State = EntityState.Deleted; newContext.SaveChanges(); scope.Complete(); } } } } } ====================================

    using Entities;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Options;
    namespace DAL.Models.DB
    {
        public partial class ProjectManagementContext : DbContext
        {
            private readonly IOptions<ConnectionStrings> _options;
            public ProjectManagementContext(IOptions<ConnectionStrings> options)
            {
                _options = options;
            }
            public ProjectManagementContext(DbContextOptions<ProjectManagementContext> options)
                : base(options)
            {
            }
            public virtual DbSet<Projects> Projects { get; set; }
            public virtual DbSet<Tasks> Tasks { get; set; }
            public virtual DbSet<ProjectTypes> ProjectTypes { get; set; }
            public virtual DbSet<Durations> Durations { get; set; }
            public virtual DbSet<Resources> Resources { get; set; }
            public virtual DbSet<Statuses> Statuses { get; set; }
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                if (!optionsBuilder.IsConfigured)
                {
                    optionsBuilder.UseSqlServer(new AppConfiguration(_options).GetProjectMgmntConn());
                }
            }
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Projects>(entity =>
                {
                    entity.HasKey(e => e.ProjectId);
                    entity.Property(e => e.ClientName)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.Cost).HasColumnType("decimal(18, 0)");
                    entity.Property(e => e.EndDate).HasColumnType("datetime");
                    entity.Property(e => e.ProjectName)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.ProjectType)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.StartDate).HasColumnType("datetime");
                    entity.Property(e => e.Technology)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.UserId)
                        .IsRequired()
                        .HasMaxLength(50);
                });
                modelBuilder.Entity<Tasks>(entity =>
                {
                    entity.HasKey(e => e.TaskId);
                    entity.Property(e => e.EndDate).HasColumnType("datetime");
                    entity.Property(e => e.Note)
                        .IsRequired()
                        .HasMaxLength(2000);
                    entity.Property(e => e.ResourceId)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.StartDate).HasColumnType("datetime");
                    entity.Property(e => e.Status)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.TaskDuration)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.TaskName)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.TaskSpent)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.HasOne(d => d.Project)
                        .WithMany(p => p.Tasks)
                        .HasForeignKey(d => d.ProjectId)
                        .OnDelete(DeleteBehavior.ClientSetNull)
                        .HasConstraintName("FK_Tasks_Projects");
                });
                modelBuilder.Entity<ProjectTypes>(entity =>
                {
                    entity.HasKey(e => e.ProjectTypeId);
                    entity.Property(e => e.Value)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.Text)
                        .IsRequired()
                        .HasMaxLength(50);
                });
                modelBuilder.Entity<Durations>(entity =>
                {
                    entity.HasKey(e => e.DurationId);
                    entity.Property(e => e.Value)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.Text)
                        .IsRequired()
                        .HasMaxLength(50);
                });
                modelBuilder.Entity<Resources>(entity =>
                {
                    entity.HasKey(e => e.ResourceId);
                   
                    entity.Property(e => e.Value)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.Text)
                        .IsRequired()
                        .HasMaxLength(50);
                });
                modelBuilder.Entity<Statuses>(entity =>
                {
                    entity.HasKey(e => e.StatusId);
                    entity.Property(e => e.Value)
                        .IsRequired()
                        .HasMaxLength(50);
                    entity.Property(e => e.Text)
                        .IsRequired()
                        .HasMaxLength(50);
                });

            }
        }
    }

    ================================================================

    using Entities;
    using Microsoft.Extensions.Options;
    namespace DAL
    {
        public class AppConfiguration
        {
            private readonly string _projectManagementConnection = "";
            private readonly IOptions<ConnectionStrings> _options;
            public AppConfiguration(IOptions<ConnectionStrings> options)
            {
                _options = options;
                _projectManagementConnection = _options.Value.ProjectManagementConnection;
            }
            public string GetProjectMgmntConn() => $"{_projectManagementConnection}";

        }
    }

    ================================================================

    using System.Net;
    using DAL;
    using Entities;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Diagnostics;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using Serilog;
    namespace ProgMgmntCore2Api
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
                Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
            }
            public IConfiguration Configuration { get; }
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                //DAL
                services.AddTransient<IDaoProject, DaoProject>();
                services.AddTransient<IDaoTask, DaoTask>();
                services.AddTransient<IDaoCache, DaoCache>();
                //Configuration
                services.Configure<ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));
                // Add framework services.
                services.AddMvc(options =>
                {
                    options.Filters.Add(new ErrorHandlingFilter(Configuration));
                });
                services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            }
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory, IApplicationLifetime appLifetime)
            {
                //this is used by Postmon and Global Exception handelling to show exception.
                app.UseExceptionHandler(
                    options =>
                    {
                        options.Run(
                            async context =>
                            {
                                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                                context.Response.ContentType = "text/html";
                                var ex = context.Features.Get<IExceptionHandlerFeature>();
                                if (ex != null)
                                {
                                    var err = $"<h1>Error: {ex.Error.Message} </h1>{ex.Error.StackTrace } {ex.Error.InnerException.Message} ";
                                    await context.Response.WriteAsync(err).ConfigureAwait(false);
                                }
                            });
                    }
                );

                loggerfactory.AddSerilog();
                app.UseHttpsRedirection();
                app.UseMvc();
            }
        }
    }


     
    Saturday, August 18, 2018 8:52 PM
  • My repository is not generic. I don't use generic repository. It takes DbContext as constructor parameter only. Repository could be created by DAO pattern. As you wrote GetProjectsByUserId method, my repository works in same way. My update/insert method is same as you wrote because I need to return ids of created/updated records and in this case I use DAO pattern to save.

    What is different I have one context for whole repository. This construction allows me to change DbContext implementation and to create fake repository which could return fake data.

    It is more complex because of I use repository extension method for controller actions. It means when controller return some data from database without business logic it calls extension method (i.e. GetById(int id)) which returns model object back to controller. It allows me to write method with pagination or MVC features without to business layer knows about these methods. 

    I don't use DI because controller can operate with many repository. 

    This is my repository class.

    public abstract class Repository : IDisposable
    {
        protected IDbContext DbContext {get;}
        protected Repository(IDbContext dbContext)
        {
            DbContext = dbContext;
        }
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    DbContext.Dispose();
                }
                disposedValue = true;
            }
        }
        public void Dispose
        {
            Dispose(true);
        }
    }
    
    public class EntityRepository : Repository
    {
        public EntityRepository(IDbContext dbContext):base(dbContext) { }
        public EntityDao GetAll()
        {
            return from k in DbContext.Entities
            select new EntityDao 
            {
                Id = k.Id,
                Name = k.Name
            };
        }
    }
    

    It is not generic repository. It can work as Dao pattern but dependency to IDbContext allows me to create fake repositories.

    But problem is another. I have save method in repository. Ok, I read repository should not have Save method but as I mentioned I need to return Id of saved record so it works similar to DAO pattern. Now I have helper class which observe entity event Changed. I use EntityFrameworkCore.Triggers (https://github.com/NickStrupat/EntityFramework.Triggers/tree/master/EntityFrameworkCore.Triggers).

    I need helper class because I have extension method to save data from controller (I don't need business layer because I need save received data only). So I observe Changed event in helper class. 

    class EntityChangeHelper
    {
        private readonly DataLayer.Repositories.EntityRepository _repository;
        public EntityChangeHelper(DataLayer.Repositories.EntityRepository repository)
        {
            _repository = repository;
        }
        public void EntityChange(object sender, EventArgs e)
        {
            DataLayer.Entities.entity entity = sender as DataLayer.Entities.Entity;
            DataLayer.Entities.Entity inDb = _repository.DbContext.Entity.Find(entity.Id);
    
            if (entity.Version != inDb.Version)
            {
                //conflict
                Models.EntityDto localVersion = AutoMapper.Mapper.Map(entity, typeof(DataLayer.Entities.Entity), typeof(Models.EntityDto)) as Models.EntityDto;
                Models.EntityDto serverVersion = AutoMapper.Mapper.Map(inDb, typeof(DataLayer.Entities.Entity), typeof(Models.EntityDto)) as Models.EntityDto;
                throw new EntityConflictException(localVersion, serverVersion);
            }
            else
                entity.Version = inDb.Version + 1;
        }
    }
    public void Post([FromBody] Models.EntityDto zakazka)
    {
        using (DataLayer.Repositories.EntityRepository repository = new DataLayer.Repositories.EntityRepository(new DataLayer.DbContext()))
        {
            EntityChangeHelper helper = new EntityChangeHelper(repository);
            repository.Add(entity, helper);
            try
            {
               repository.SaveChanges();
            }
            catch (EntityConflictException e)
            {
               HttpContext.Response.ContentType = "application/json";
                HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
                var result = JsonConvert.SerializeObject(new { Local = e.LocalVersion, Server = e.ServerVersion });
                byte[] output = System.Text.UTF8Encoding.UTF8.GetBytes(result);
                HttpContext.Response.Body.Write(output);
            }
        }
    }

    When I want to find entity in HelperClass at first call of controller action it works fine. At second on Find method I get exception. I don't know why because helper method is called within SaveChanges method. All methods in repository work fine at second call. But in helper class is problem.

    Sunday, August 19, 2018 6:51 AM
  • It is very interesting. I solved it. I instantiate Helper class with context. When I try to find entity at second controller action calls I got error in Helper class. I don't know why because I think it is save context as controller. But maybe not. I saw context value is in parameter of update trigger so I remove helper class and transfer update trigger parameter into method which was in helper class originally (I move this method into repository class) and I use context which is passed in parameter. It works. 

    Maybe context in helper class when I tried to find entity is from DI but I don't know it exactly. I think it should be same instance as repository used. 

    Sunday, August 19, 2018 8:39 AM
  • helper=null should dispose of the object and any objects it was using inside of it.
    Sunday, August 19, 2018 12:45 PM
  • I catch exception and I try in catch block get data by same context. No problem. Maybe problem is on another place than I think and context is given by IoC inside third party library but I don't have any context defined in IoC. It could be problem.
    Sunday, August 26, 2018 3:13 PM
  • I catch exception and I try in catch block get data by same context. No problem. Maybe problem is on another place than I think and context is given by IoC inside third party library but I don't have any context defined in IoC. It could be problem.

    A couple of FYI(s) here.

    1) concerning the Repository pattern

    https://programmingwithmosh.com/entity-framework/common-mistakes-with-the-repository-pattern/

    2) Global exception handling, that eliminates try/catches all over the code.

    1) the first one is for MVC Core where the statment in the Startup.cs points to the controller for GEH.

     app.UseExceptionHandler("/Error/Error");
    
    ========================================
    
    using System;
    using System.Diagnostics;
    using Microsoft.AspNetCore.Diagnostics;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using ProgMgmntCore2UserIdentity.Models;
    
    namespace ProgMgmntCore2UserIdentity.Controllers
    {
        public class ErrorController : Controller
        {
            private readonly ILogger _logger;
    
            public ErrorController(ILogger<ErrorController> logger)
            {
                _logger = logger;
            }
    
            public IActionResult Index()
            {
                return View();
            }
    
            [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
            public IActionResult Error()
            {
                var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
    
                // Get which route the exception occurred at
    
                string routeWhereExceptionOccurred = exceptionFeature.Path;
    
                // Get the exception that occurred
    
                var requestid = "";
    
                requestid = Activity.Current?.Id != null ? Activity.Current?.Id : HttpContext.TraceIdentifier;
                    
                Exception exceptionThatOccurred = exceptionFeature.Error;
    
                _logger.LogError("Request Id: " + requestid + " " + routeWhereExceptionOccurred + " " + exceptionThatOccurred);
    
                return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
            }
        }
    }
    

    This is go GEH in a Core WebAPI.

    https://stackify.com/csharp-catch-all-exceptions/

    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.Extensions.Configuration;
    using Serilog;
    
    namespace ProgMgmntCore2Api
    {
        public class ErrorHandlingFilter : ExceptionFilterAttribute
        {
            private readonly IConfiguration _configuration;
    
            public ErrorHandlingFilter(IConfiguration configuration)
            {
                _configuration = configuration;
            }
    
            public override void OnException(ExceptionContext context)
            {
                var log = new LoggerConfiguration().ReadFrom.Configuration(_configuration).CreateLogger();
    
                var exception = context.Exception;
    
                if (exception.InnerException != null)
                {
                    log.Error("Message = " + exception.Message + " Inner.Exception.Message = " +
                              exception.InnerException.Message
                              + " Stack Trace = " + exception.StackTrace);
                }
                else
                {
                    log.Error("Message = " + exception.Message + " Stack Trace = " + exception.StackTrace);
                }
    
                context.ExceptionHandled = false; //optional 
            }
        }
    }
    


    Sunday, August 26, 2018 10:51 PM
  • I don't know how to close this topic. I solved it but triggers are written by a lot of non-intuitive way. It is very hard to work with it because it is static in all. So it is independent at entity and each entity can observe another entity changed. It is very stressfull because a lot of implementation is necessary to recognize which object was changed.

    It begins because in entity framework core it was very hard to get original instance of object which reflect data in database. Because when you change your object and then try to find it changed object is returned. You cannot compare it. Maybe in next version of entity framework it can works fine.

    Entity framework core is very interesting. I tried to sum values in one column:

    var sum = dbContext.Table.Sum(k=>k.Column1);

    It creates nice SQL query as expected: select sum(Column1) from table;

    But when I tried to do somethink like:

    var sum = dbContext.Table.Sum(k=>k.Column1 * k.Column2);
    it procudes more queries => for each row it multiplies two columns and it sums together in memory instead to produce select sum(Column1 * Column2) from table.

    So I would like to close this topic but it is very hard to describe what I have to do to achieve correct behavior.

    Monday, December 10, 2018 4:47 AM