Reputation: 1631
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
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
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
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
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
Reputation: 10874
You should implement IDisposable
on your controller, and dispose the DbContext
in the controller's Dispose
method
Upvotes: 1