Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I getting error: "Cannot access disposed object" in .net core 2 with EF and AutoFac?

First the error:

Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and

then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. Object name: 'MemberContext'.

I have 3 projects, Domain, API and WebSPA app.

Domain has 2 modules, DomainModule and MediatorModule

 public class DomainModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterAssemblyTypes(typeof(MemberContext).Assembly)
                .AsImplementedInterfaces()
                .InstancePerLifetimeScope(); // via assembly scan

            builder.RegisterType<MemberContext>().AsSelf()
                    .InstancePerLifetimeScope();          // or individually
        }
    }

 public class MediatorModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            // enables contravariant Resolve() for interfaces with single contravariant ("in") arg
            builder
                .RegisterSource(new ContravariantRegistrationSource());

            // mediator itself
            builder
                .RegisterType<Mediator>()
                .As<IMediator>()
                .InstancePerLifetimeScope();

            // request handlers
            builder
                .Register<SingleInstanceFactory>(ctx =>
                {
                    var c = ctx.Resolve<IComponentContext>();
                    return t =>
                    {
                        object o;
                        return c.TryResolve(t, out o) ? o : null;
                    };
                })
                .InstancePerLifetimeScope();

            // notification handlers
            builder
                .Register<MultiInstanceFactory>(ctx =>
                {
                    var c = ctx.Resolve<IComponentContext>();
                    return t => (IEnumerable<object>) c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
                })
                .InstancePerLifetimeScope();

        }
    }

In API project I have also 2 modules, ApplicationModule and again MediatorModule same as the one above.

 public class ApplicationModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterAssemblyTypes(typeof(Startup).Assembly)
                        .AsImplementedInterfaces()
                        .InstancePerLifetimeScope(); // via assembly scan

            builder.RegisterType<MemberContext>().AsSelf().InstancePerLifetimeScope();          // or individually
        }
    }

No, when I debug I can see that member context gets newed up on each request, yet on second request, it throws above error. To make sure I am not going crazy, I modified constructor of dbcontext to create an id for context so i can verify they are different. What am I doing wrong?

 public MemberContext(DbContextOptions<MemberContext> options) : base(options)
        {
            MemberContextId = Guid.NewGuid();

            Console.WriteLine("member context created: " + MemberContextId);

        }

Here is the startup in API

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy",
                builder => builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                //    .AllowCredentials()
            );
        });

        services.AddMvc()
                .AddControllersAsServices();//Injecting Controllers themselves thru DI
                        //For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services

        AddSwaggerGen(services);

        //var connection = Configuration["ConnectionString"];

        //services.AddDbContext<MemberContext>(options => options.UseSqlServer(connection),ServiceLifetime.Scoped);


        services.AddEntityFrameworkSqlServer()
            .AddDbContext<MemberContext>(options =>
                {
                    options.UseSqlServer(Configuration["ConnectionString"]
                        //,sqlServerOptionsAction: sqlOptions =>
                        //{
                        //    sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
                        //    sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
                        //}
                        );
                },
                ServiceLifetime.Scoped  //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
            );


        var container = new ContainerBuilder();
        container.Populate(services);

        container.RegisterAssemblyModules(typeof(VIN.Members.Domain.Entities.Member).Assembly,
                                          typeof(Startup).Assembly);


        return new AutofacServiceProvider(container.Build());
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        //NOTE: must be before UseMVC !!!
        app.UseCors("CorsPolicy");
        app.UseMvc();

        app.UseSwagger();
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
        });
    }

    private void AddSwaggerGen(IServiceCollection services)
    {
        services.AddSwaggerGen(options =>
        {
            options.DescribeAllEnumsAsStrings();
            options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
            {
                Title = "VIN Members HTTP API",
                Version = "v1",
                Description = "Members Service HTTP API",
                TermsOfService = "Terms Of Service"
            });
        });
    }
}

UPDATE:

What I am trying to do is delete a record. On client side code looks like this

 onDelete(item: IMember) {
        //TODO: replace this with dialog service component
        if (window.confirm('Are sure you want to delete this member?')) {
            //put your delete method logic here
            this.service.deleteMember(item).subscribe(x => {

                this.getMembers();
            });
        }
    }

this delete request gets mapped to a controller that passes it to mediator

Controller

  // DELETE api/members/5
        [HttpDelete("{id}")]
        public void Delete(Guid id)
        {
            var command = new DeleteMember.Command(id);

            _mediator.Send(command).ConfigureAwait(false);
        }

and finally handler

public class DeleteMember
{
    public class Command : IRequest
    {
        public Command(Guid memberId)
        {
            Guard.NotNull(memberId, nameof(memberId));

            MemberId = memberId;
        }

        public Guid MemberId { get; }

    }

    public class Handler : AsyncRequestHandler<Command>
    {
        private MemberContext _context;

        public Handler(MemberContext context)
        {
            _context = context;
            Console.WriteLine("Delete member context: " + context.MemberContextId);
        }

        protected override async Task HandleCore(Command cmd)
        {
            try
            {
                var member = await _context.FindAsync<Member>(cmd.MemberId);//.ConfigureAwait(false);

               // if (member != null)
               //// {
                    _context.Remove(member);

                    await _context.SaveChangesAsync().ConfigureAwait(false);
               // }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

        }
    }
}

As you can see there is no code that disposes that context. Scratching my head.

See this commented out check for member if null. That was throwing error as well, I commented it out just to see what will happen, and now it throws as SaveChangesAsync.

like image 671
epitka Avatar asked Dec 23 '22 10:12

epitka


1 Answers

As request completes, context gets disposed. Since command handler uses SaveChangesAsync(), context is disposed before save completes. Culprit is controller method :). It should be async as well.

[HttpDelete("{id}")]
public async Task Delete(Guid id)
{
    var command = new DeleteMember.Command(id);

   await _mediator.Send(command).ConfigureAwait(false);
}
like image 154
epitka Avatar answered Apr 05 '23 23:04

epitka