André Reichelt
André Reichelt

Reputation: 1631

ObjectDisposedException when returning AsAsyncEnumerable()

In my .NET Core 3 WebAPI project, I have the following, simple method call:

[HttpGet("ViewerRoles")]
public IAsyncEnumerable<ViewerRole> GetViewList() {
    using var db = new MpaContext();

    return db.ViewerRoles.AsAsyncEnumerable();
}

This throws me an ObjectDisposedException. AsAsyncEnumerable() is relatively new and I can't find any appropriate examples of how to use it in such situations. Should I just remove the using keyword and the Entity Framework database connection magically disposes itself? Or is there another trick?

Upvotes: 3

Views: 1017

Answers (5)

Alex Ilin
Alex Ilin

Reputation: 61

I see all answers have its own point and all of them true. So, I can only make some clarifications on them.

You should choose a moment of disposing object depending on how you expose it. For example, your original code implicitly exposes MpaContext db to AspNet pipeline and you can't dispose db until netcore done his work with it. So, you can register disposing of it by Response.RegisterForDispose(), as you mentioned. But, it's uncommon because you don't have access to Response usually - you can do this only inside Controller, or if you share it with Controller dependencies, but it will rise code complexity.

That's why you can avoid this by relying on lifetime of controller. Since, it's in request scope it will live until response was sent. So, you can create your db as controller dependency and hold it within a property. Also you should implement IDisposable on controller.

public class RoleController : IDisposable
{
    private MpaContext DbContext { get; }

    public RoleController()
    {
        DbContext = new MpaContext();
    }

    [HttpGet( "ViewerRoles" )]
    public IAsyncEnumerable<ViewerRole> GetViewList()
    {
        return DbContext.ViewerRoles.AsAsyncEnumerable();
    }

    public void Dispose()
    {
        DbContext.Dispose();
    }
}

In this case you can stick to this pattern even if you will move your logic to some other class (as it supposed to be, I believe). But still, if you manually create disposable objects you should care about disposing them. That's the moment when DI comes to help.

By using DI you can forget about disposing objects that was created by DI. DI will call Dispose() on any dependency when lifecycle of it ends. Register your MpaContext by calling AddDbContextPool<MpaContext>() or AddDbContext<MpaContext>() if you use EntityFramework under MpaContext. With this approach you will get clear code of your controller.

public class RoleController
{
    private MpaContext DbContext { get; }

    public RoleController( MpaContext dbContext )
    {
        DbContext = dbContext;
    }

    [HttpGet( "ViewerRoles" )]
    public IAsyncEnumerable<ViewerRole> GetViewList()
    {
        return DbContext.ViewerRoles.AsAsyncEnumerable();
    }
}

If you don't want to expose MpaContext to controller and want to create it manually inside GetViewList(), you can still enumerate result within the action method and dispose context as Theodor Zoulias answered. But why would you, if you can simply delegate this work to DI.

Upvotes: 1

Andr&#233; Reichelt
Andr&#233; Reichelt

Reputation: 1631

I have also found the approach to use the method Response.RegisterForDispose(). But I still do not know which approach is the most promising.

[HttpGet("ViewerRoles")]
public IAsyncEnumerable<ViewerRole> GetViewList() {
    MpaContext db = new MpaContext();

    Response.RegisterForDispose(db);

    return db.ViewerRoles.AsAsyncEnumerable();
}

Upvotes: 1

Theodor Zoulias
Theodor Zoulias

Reputation: 43545

You have two options. Either enumerate the IAsyncEnumerable inside your GetViewList method:

[HttpGet("ViewerRoles")]
public async IAsyncEnumerable<ViewerRole> GetViewList()
{
    using var db = new MpaContext();
    await foreach (var item in db.ViewerRoles.AsAsyncEnumerable().ConfigureAwait(false))
    {
        yield return item;
    }
}

...or install the System.Interactive.Async package and use the static AsyncEnumerableEx.Using method:

[HttpGet("ViewerRoles")]
public IAsyncEnumerable<ViewerRole> GetViewList()
{
    return AsyncEnumerableEx.Using(() => new MpaContext(),
        db => db.ViewerRoles.AsAsyncEnumerable());
}

Here is the signature of the AsyncEnumerableEx.Using method:

public static IAsyncEnumerable<TSource> Using<TSource, TResource>(
    Func<TResource> resourceFactory,
    Func<TResource, IAsyncEnumerable<TSource>> enumerableFactory)
    where TResource : IDisposable;

Unfortunately it seems that there is no online documentation available for this library.

Upvotes: 4

Mike Dudnik
Mike Dudnik

Reputation: 146

using statement in this case scopes your dbContext to function scope, so the correct way would be to enumerate before returning from action, otherwise you returning something what is cannot be correctly evaluated later (after function returned and context disposed)

alternatively, you can move dbContext creation to controller's scope, which is can be request scope (it is not that hart to implement via framework's DI and DI will take care about everything with IDisposable and scoped to request "magically")

Upvotes: 1

Jonas H&#248;gh
Jonas H&#248;gh

Reputation: 10874

You should implement IDisposable on your controller, and dispose the DbContext in the controller's Dispose method

Upvotes: 1

Related Questions