81

I have written a simple application and when I navigate to my edit page the below error pops up.

Microsoft.EntityFrameworkCore.Query[10100]

An exception occurred while iterating over the results of a query for context type 'app.Models.ApplicationDbContext'.

System.ObjectDisposedException: 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.

It seems EF is providing a useful information which I can not understand. The tricky part of this error is that it happens randomly when I navigate to the edit page. Sometime it works, Sometimes it fails to load some properties on Edit.cshtml but still works and sometimes the application crashes with provided error just in my console. Another strange happen is that it dose not generate any 500 or 5xx error. It just simply crashes and stop the application.

Here is my Edit.cshtml content:

@page
@model EditModel
@{
    ViewData["Title"] = "Edit Book";
}

<h2>Edit Book</h2>

<div class="row justify-content-center">
    <div class="col-md-6">
        <form method="post" class="form-border">
            <div asp-validation-summary="All" class="validation-container alert alert-danger"></div>
            <div class="form-group">
                <label asp-for="Book.Name"></label>
                <input asp-for="Book.Name" class="form-control" />
                <span class="form-text text-danger" asp-validation-for="Book.Name"></span>
            </div>
            <div class="form-group">
                <label asp-for="Book.Description"></label>
                <input asp-for="Book.Description" class="form-control" />
            </div>
            <div class="form-group">
                <label asp-for="Book.Author"></label>
                <input asp-for="Book.Author" class="form-control" />
            </div>
            <input asp-for="Book.Id" type="hidden">
            <button type="submit" class="btn btn-primary">Update</button>
            <a asp-page="Index" class="btn btn-success">Back To List</a>
        </form>
    </div>
</div>

Here is My Edit.cshtm.cs OnGet method:

public async void OnGet(int id)
{
    Book = await _db.Books.SingleOrDefaultAsync(x => x.Id == id);

    if(Book == null)
    {
        RedirectToPage("Index");
    }
}

I am using .Net Core 2.2.104

Also when I run command dotnet ef --version it generates Entity Framework Core .NET Command-line Tools 2.2.2-servicing-10034

JohnB
  • 18,046
  • 16
  • 98
  • 110
Soheil
  • 1,201
  • 2
  • 11
  • 18
  • 2
    read this [article](https://thinkrethink.net/2017/12/22/cannot-access-a-disposed-object-in-asp-net-core-when-injecting-dbcontext/) – NaDeR Star Apr 06 '19 at 00:15

9 Answers9

159

This is because of your method return type async void. In general, when you are using async void in your code it’s bad news, because:

  • You can’t wait for its completion
  • Any unhandled exceptions will terminate your process (ouch!)

So return async Task instead of async void from your method as follows:

public async Task OnGet(int id)
{
    Book = await _db.Books.SingleOrDefaultAsync(x => x.Id == id);

    if(Book == null)
    {
       RedirectToPage("Index");
    }
}

For more details:

TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
42

What I am about to post is NOT the answer to this particular question. But it is related so just to save somebody headache I am posting it. I was encountering this same error

System.ObjectDisposedException: Cannot access a disposed object. etc

The following was the code with the bug (can you see it?):

[HttpGet("processs/oxxo-spei/ticket-email/{paymentIdx}")]
public StatusCodeResult ProcessOxxoSpeiTicketEmailAsync(string paymentIdx)
{
    var paymentId = paymentIdx.DecodeRef();
            
    var response = _orderEngine.ProcessOxxoSpeiTicketEmailAsync(paymentId);

    return StatusCode(200);
}

The following change fixed it:

[HttpGet("processs/oxxo-spei/ticket-email/{paymentIdx}")]
public async Task<StatusCodeResult> ProcessOxxoSpeiTicketEmailAsync(string paymentIdx)
{
    var paymentId = paymentIdx.DecodeRef();
            
    var response = await _orderEngine.ProcessOxxoSpeiTicketEmailAsync(paymentId);
                // ^^^^I HAD FORGOTTEN TO PUT AWAIT
    return StatusCode(200);
}

Yes that's right I had forgotten to put "await" before a function that used an EF Core dbcontext. Adding 'await' fixed it. So easy to miss it, especially if you're tired and under deadline.

brando
  • 8,215
  • 8
  • 40
  • 59
  • 4
    Thanks dude, I had a controller hitting a non async method and then changed the service method it was hitting to async and never added aysnc method or await on the controller side, duhhh........ – Tim Maxey Oct 21 '20 at 17:49
  • 3
    I was also not using async in my controller method and I feel like a complete moron, wrestled with this for two days. Sometimes you just need to get up and take a walk. Thanks for posting this. – snejame Feb 02 '21 at 07:01
  • 1
    Just to add, in short returning a Task will fix the issue and make sure object instance is not disposed – Waleed Nov 06 '21 at 20:29
  • Wouldn't this be picked up in literally any editor...? – fullStackChris Dec 07 '21 at 11:05
  • @fullStackChris I think now it is – brando Dec 07 '21 at 18:09
  • I'm running into this problem today, with DbContext, I think it has much more to do with how classes are injected and when. – fullStackChris Dec 07 '21 at 18:41
  • 1
    That was exactly it and in the exact circumstance that you suggested. Client testing tomorrow and I added in an async method in a controller that I did not place await in front of. Thanks, luckily I got here quickly and did not waste too much time on it. – Dermo909 Oct 10 '22 at 10:19
  • Answer 8 and your answer are connected with each other need to make it async. The issue will fix. Thank you all. – zinsat Nov 25 '22 at 11:26
42

UPDATED ANSWER WHEN THE PROBLEM IS NOT A TASK OF TYPE VOID The accepted answer is fine if you are using a voided method. There is another potential problem with a simple solution if your method is not a void.

  1. Did you implement a pattern or some type of separation of concerns? Controller => Service => Repository

Verify that your controller method is async Task if the methods down the line are also async Task. In my case the problem was the fact that I did not put async on the controller method, but the downline methods were async Task.

Cheers!

Michael Howard
  • 431
  • 4
  • 5
  • I had the same issue, thanks a lot for this. I did a global search and found a couple more async void that I replaced with async Task to avoid potential future issues. – GerardF Jan 15 '22 at 14:54
  • 2
    I literally dug up all my services functions and Repo as well. My Controller was not async. Adding it helped me.... Thanks man! I was getting an "System.ObjectDisposedException: 'Cannot access a disposed context instance. ..... " upon Calling savechanges method from Repo. – Aakash Mar 01 '22 at 13:44
  • I was just returning IActionResult in my controller and had async methods down the line. I changed the return type to Task and all is good now. Thanks, I would never have gotten that! – Dermo909 Aug 01 '23 at 08:25
7

Alternative Solution to async Task conversions


You've tried...

  • Making all methods in the callstack async Task instead of async void

After a careful look at my call stack, I had overridden the SaveChangeAsync method in my DbContext, and made a call to change the behavior of how async operates by default. This which resulted in my context being disposed and still trying to access it.


Some code omitted for brevity

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
  int result = await base.SaveChangesAsync(cancellationToken);

  // ignore events if no dispatcher provided
  if (dispatcher is null) return result;

  // dispatch events only if save was successful
  await DispatchEventsIfSaveSuccessful();
  return result;
}

private async Task DispatchEventsIfSaveSuccessful()
{
    var entitiesWithEvents = ChangeTracker
        .Entries<Entity>()
        .Select(e => e.Entity)
        .Where(e => e.Events.Any())
        .ToArray();
...
    foreach (var entity in entitiesWithEvents)
    {
        var events = entity.Events.ToArray();
        entity.Events.Clear();
        foreach (var domainEvent in events)
        {
            await dispatcher.Dispatch(domainEvent).ConfigureAwait(false);
        }
    }
}

Problem way

await dispatcher.Dispatch(domainEvent).ConfigureAwait(continueOnCapturedContext: false);

The solution

omit the ConfigureAwait(false)

await dispatcher.Dispatch(domainEvent);

Explanation

ASP.NET Core does not use a [SynchronizationContext].(https://devblogs.microsoft.com/dotnet/configureawait-faq/).

When using ConfigureAwait(false) options on an async Task, the executing task will not resume on this context, but instead on a thread pool thread. Essentially by configuring that await call, when the thread finished dispatching the events, the context was already disposed.

Advice

Continue looking through each method/function invocation from the beginning of your request all the way to when it fails and see if you may be altering the default behavior of async/await via ConfigureAwait(false) or a similar altering change.

For more information, I highly recommend following this link by Stephen Cleary on his blog about async more.

Reap
  • 1,047
  • 13
  • 16
  • Can you clarify what kind of problem this created? The fact that the continuation is resuming on a thread pool thread shouldn't allow the context to be disposed before it finishes (unless you're not awaiting your top-level SaveChangesAsync() call before disposing the context, in which case it would be functionally equivalent to async void).I feel like there must be more to the story than is reflected in your code snippets, like use of [ThreadStatic] fields or Task.Run() or something. – Jeremy Todd Dec 10 '21 at 22:41
  • @JeremyTodd If I recall correctly, in the *entire* request pipeline that I used to construct this answer, there was no `ThreadStatic` or `Task.Runs`, etc. or anything other than standard `await/async`. For reasons I've tried to understand from the documentation I linked in my answer, it appears an `async` call to the `SaveChangesAsync` that has a `ConfigureAwait` async call to the dispatcher caused this issue because it was the only change made that caused the error to completely disappear. It happened consistently on debug/release mode. – Reap Dec 13 '21 at 06:22
  • Kudos to you! Thanks for brief description – Ahmed Naeem Jan 31 '23 at 22:10
2

Here is what actually worked for me in a situation where I was getting the same error as above when trying to access the data in Startup.cs to run some startup data building functions on server load, rather than a specific Controller action:

        IServiceScope scope = provider.CreateScope();
        YourDbContext context = scope.ServiceProvider.GetRequiredService<YourDbContext>();

In the Configure method of Startup.cs, with IServiceProvider included as a parameter:

public async void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider provider)

Just including that for anyone who wants to do the same in Startup.cs.

John Ernest
  • 785
  • 1
  • 8
  • 20
1

I found something else that causes this error, just posting it here for posterity.

I had cached the result of a query into memory as an IQueryable.

When pulling it out of the cache and calling ToList(), it worked successfully the first time. On page refresh however, it caused this error.

The solution is to not cache IQueryable, but do ToList() first.

Spectric
  • 30,714
  • 6
  • 20
  • 43
Andrew
  • 18,680
  • 13
  • 103
  • 118
0

This is another anwser for who gonna read this topic.

I tried to trigger asnyc method with javascript. Because of that it can't triggered and so result didn't calculated by async method. I was returning json object to view like this in dotnet core mvc.

return Json(_service.AsyncMethod(parameter));

My IActionResult is not async so I can't add "await". I just add ".Result" and it calculate result.

return Json(_service.AsyncMethod(parameter).Result);
Hakkı
  • 811
  • 2
  • 13
  • 23
0

In my case I have resolved the issue taking guidance from https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-5.0&tabs=visual-studio#consuming-a-scoped-service-in-a-background-task I made a scoped service and used method inside in that service as queued task.

fgohil
  • 43
  • 1
  • 7
-1

Create IServiceScopeFactory object and used dependency from IServiceScopeFactory object once it dispose.

private readonly IServiceScopeFactory moserviceScopeFactory;
private readonly InvoicesDataContext moInvoicesDataContext;
public ABCController(InvoicesDataContext diInvoicesDataContext, IServiceScopeFactory serviceScopeFactory)
        {
            moInvoicesDataContext = diInvoicesDataContext;
            moserviceScopeFactory = serviceScopeFactory;
        }

My Object has been dispose after execute below code and I want to access object after execute this code. I have created object from IServiceScopeFactory and used. and working fine for me.

public async Task<int> generateInvoice()
            {
                List<Guid> loSentId = new List<Guid>();
                Invoices loDataContext = new Invoices(moInvoicesDataContext);
                List<Invoices> loInvoices = loInvoiceDataContext.get("", null, "", null, null, 1, "1", "asc", 1, 5);
    
                bool lblIsSuccess = await loSystemEmails.sendInvoice(loInvoice.stInvoiceNumber, loInvoice.stCompanyEmail, string.Empty, loInvoice.stCompanyName, GetFolderPath.loRootPath + lsSystemFilePath, "1");
                                    if (lblIsSuccess)
                                    {
                                        loSentInvoiceId.Add(loInvoice.unCompanyInvoiceId);
    
                                        

using (var scope = moserviceScopeFactory.CreateScope())
                                        {
                                            var context = scope.ServiceProvider.GetRequiredService<InvoicesDataContext>();

    
                                            int liSuccess = new Invoices(context).SaveData(Convert.ToString(loInvoice.unCompanyInvoiceId), 2, Guid.Empty, "New Zealand Standard Time", "+13:00"); //2 = Sent
                                        }
                                    }
                return loSentInvoiceId != null && loSentInvoiceId.Count > 0 ? loSentInvoiceId.Count : 0;
            }
Sujit Patel
  • 155
  • 8
  • How does this (poorly-indented) code answer the question? The correct answer has already been given and is *very* simple. Even when creating a scope `async void` will be an issue so this doesn't solve anything. – Gert Arnold Feb 24 '21 at 16:15