Search code examples
asp.net-coreentity-framework-corehotchocolate

HotChocolate w/ EF6: getting an [System.ObjectDisposedException] exception


The title says it all and I can't figure out why.

Before You Response:

  • All methods all returning Tasks (not void) except for the EF overrides
  • All subsequent methods are running w/ await
  • I'm using Cosmos DB as my repo and EF Core 6

I've followed the instructions found here to no resolve.

Architecture

Resolver -> BLL -> DAL -> DbContext

Example: CreateFoo (Mutation) -> Foo:IFoo -> FooRepo:IFooRepo -> FooContext:DbContext

Code

Program.cs

using Foo.Data;
using sql = Foo.Data.Sql;

var builder = WebApplication.CreateBuilder(args);

// Inject DbContext
builder.Services.AddDbContext<sql.FooContext>(options => {
   options.UseCosmos("endpoint", "key", "database");
});

// Inject DAL
builder.Services.AddScoped<IFooRepo, sql.FooRepo>();

// Inject BLL
builder.Services.AddScoped<IFoo, Foo>();

// Add GraphQL
builder.Services
   .AddGraphQLServer()
   .RegisterDbContext<sql.FooContext>()
   .AddMutationType<Mutations>();

var app = builder.Build();
app.MapGraphQL();
app.Run();

Mutations.cs (Resolver)

public class Mutations {
    private IFoo _foo;

    public Mutations(
        IFoo foo
    ) {
        _foo = foo;
    }

    [GraphQLDescription("Adds a new foo to the database.")]
    public async Task<FooItem?> AddFoo(string name) {
        return await _foo.CreateFoo(name);
    }
}

Foo.cs (BLL)

public class Foo: IFoo {
    private IFooRepo _fooRepo;

    public Foo(IFooRepo fooRepo) {
        _fooRepo = fooRepo;
    }
    
    public async Task<FooItem?> CreateFoo(string name) {
        var fooItem = new FooItem() {
            Id = Guid.NewGuid(),
            Name = name
        };
        
        try {
            return await _fooRepo.CreateFoo(fooItem);
        } catch (Exception exc) {
            Console.WriteLine(exc.ToString());
        }

        return null;
    }
}

FooRepo.cs (DAL)

public class FooRepo : IFooRepo {

    private FooContext _context;

    public FooRepo(FooContext context) {
        _context = context;
    }

    public async Task<FooItem> CreateFoo(FooItem fooItem) {
        _context.Add<FooItem>(fooItem);
        await _context.SaveChangesAsync();

        return fooItem;
    }
}

FooContext.cs (DbContext, EF6)

public class FooContext : DbContext {
   
   public DbSet<FooItem> FooItems { get; set; } = default;

   public FooContext(DbContextOptions<FooContext> options) : base(options) {}

   protected override void OnModelCreating(ModelBuilder modelBuilder) {
      modelBuilder.Entity<FooItem>()
         .ToContainer("fooItems")
         .HasNoDiscriminator()
         .HasPartitionKey(r => r.itemId)
         .UseETagConcurrency();

      modelBuilder.Entity<FooItem>().Property(r => r.Id).ToJsonProperty("id");
      modelBuilder.Entity<FootItem>().Property(r => r.Name).ToJsonProperty("name");
   }
}

Error:

If I run the above and try to create two FooItems in a row, I get the error:

System.ObjectDisposedException: Cannot access a disposed context instance. A common cause of this error is disposing a context instance 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 instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

How do I fix this?


Solution

  • UGGHHH! I spent 8 hours on this because this isn't in the docs, but I just stumbled on a blog.

    I have to manually set the Queries and Mutations as Scoped as well:

    // Add GraphQL
    builder.Services.AddScoped<Queries>();
    builder.Services.AddScoped<Mutations>();
    builder.Services
        .AddGraphQLServer()
        .RegisterDbContext<sql.FooContext>()
        .AddQueryType<Queries>()
        .AddMutationType<Mutations>();
    

    Adding the first two lines fixed it. No other changes to the code were required.