开发者

Entity framework context and structure map disposing

开发者 https://www.devze.com 2023-04-03 03:52 出处:网络
I have strange problem with disposing entity framework connection in asp.net mvc application. I have simple structure for example :

I have strange problem with disposing entity framework connection in asp.net mvc application.

I have simple structure for example :

Entity :

public class EmployeeReport
{
    public int EmployeeReportId { get; set; }

    public DateTime Created { get; set; }

    public Decimal Hours { get; set; }

    public string Comment { get; set; }

    public int EmployeeId { get; set; }

    public int ContractId { get; set; }

    public int ServiceId { get; set; }

    public virtual ReportContract Contract { get; set; }

    public virtual ReportService Service { get; set; }

    public virtual Employee Employee { get; set; }

}
开发者_Python百科

Entity mapper :

public class EmployeeReportMapper : EntityTypeConfiguration<EmployeeReport>
{
    public EmployeeReportMapper()
    {
        ToTable("intranet_employee_reports");
        HasKey(x => x.EmployeeReportId);

        Property(x => x.Created).HasColumnName("Created").IsRequired();
        Property(x => x.Comment).HasColumnName("Comment").IsOptional();
        Property(x => x.Hours).HasColumnName("Hours").IsRequired();

        HasRequired(x => x.Employee).WithMany().HasForeignKey(x => x.EmployeeId);
        HasRequired(x => x.Service).WithMany().HasForeignKey(x => x.ServiceId);
        HasRequired(x => x.Contract).WithMany().HasForeignKey(x => x.ServiceId);
    }
}

DbContext - interface

public interface IDbContext : IDisposable
{
    IDbSet<EmployeeReport> EmployeeReports { get; }
}

DbContext - implementation

public class IntranetDbContext : DbContext,IDbContext
{
    public IDbSet<EmployeeReport> EmployeeReports { get; set; }
    ...


    public IntranetDbContext() : base("IntranetDb")
    {
        Database.SetInitializer<IntranetDbContext>(null); 
    }

    public void Commit()
    {
        SaveChanges();
    }

    public void ChangeEntityState(object entity, EntityState entityState)
    {
        Entry(entity).State = entityState;
    }

    public void ExecuteSql(string query, SqlParameterCollection parameterCollection)
    {
        Database.ExecuteSqlCommand(query, parameterCollection);
    }


    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        /* Register custom mapping class */
        modelBuilder.Configurations.Add(new EmployeeReportMapper());
         ....
        base.OnModelCreating(modelBuilder);
    }

}

Finally my structure map configuration :

public class CoreRegistry : Registry
{
    public CoreRegistry()
    {
        For<IDbContext>().HttpContextScoped().Use<IntranetDbContext>();
        ...
    }
}

and Global.asax :

    protected void Application_EndRequest(object sender, EventArgs e)
    {
        ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
    }

Ok, now the problem, in my application i using standard constructor dependency injection or call ObjectFactory.GetInstance().

In one of my controller i call service class, which has access to dbcontext and fetch some entites.

Unfortunately i get classic exception :

The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.

This is strange, because service is called during request and all data are forced to client in controller...

Any idea, where I do mistake?

EDIT :

Service code :

public class EmployeeService : IEmployeeService
{
    /// <summary>
    /// IDbContext reference
    /// </summary>
    private readonly IDbContext _dbContext;

    public EmployeeService(IDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public List<Employee> GetSubordinateEmployees(Employee employee)
    {
        List<Employee> employees = new List<Employee>();

        foreach (var unit in employee.OrganizationUnits.ToList()) /* throw exception*/
        {
            foreach (var childrenUnit in unit.ChildrenUnits)
            {
                employees.AddRange(childrenUnit.Employees);
            }
        }
        return employees.Distinct().ToList();
    }

Controller :

    private readonly IEmployeeService _employeeService;

    public EmployeeReportController(IEmployeeService employeeService)
    {
        _employeeService = employeeService;
    }

    [HttpGet]
    public ActionResult SearchReports()
    {
        List<Employee> employees = _employeeService.GetSubordinateEmployee(IntranetSession.Current.LoggedAccount.Employee).ToList(); // Exception!
       ...

        return View();
    }


}   


Your code doesn't use current DbContext at all. The problem is:

IntranetSession.Current.LoggedAccount.Employee

Followed by:

employee.OrganizationUnits.ToList()

Your employee stored in session was loaded with context which is already disposed but it still keeps reference to that context. When you loaded that employee you didn't eager load his organizations so once you access her OrganizationUnits it will try to trigger lazy loading on disposed context.

There are two ways to avoid this problem:

  • Eager load all information you need to use from your employee stored in session. It means retrieving employee like context.Employees.Include(e => e.OrganizationUnits).Single(...)
  • Store only employee's Id in session and load on-demand data you need

If you want to cache whole employee in session make sure that you will disable proxy creation for objects stored in session by calling :

 context.Configuration.ProxyCreationEnabled = false;

It will ensure that cached data will not keep reference to disposed context (which btw. prevent garbage collector to collect context and all its referenced objects).

0

精彩评论

暂无评论...
验证码 换一张
取 消