1

I created a custom Health Check that calls an injected service, and that service uses a DbContext to query the DB to get some info. When I launched my application I get the following error:

An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

Is there a way to delay the health check until the DbContext is registered somewhere in the startup?

Below is my health check implementation.

public class HealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
    {
        int userCount = dbService.GetUserCount();  // fails in the dbService here

        if (userCount > 0)
            return Task.FromResult(HealthCheckResult.Healthy("A healthy result."));

        return Task.FromResult(new HealthCheckResult(context.Registration.FailureStatus, "An unhealthy result."));
    }
}

This is how it is registered in the startup after my dbcontext is registered via AddDbContext

services.AddHealthChecks().AddCheck<HealthCheck>("user_health_check");
Los Morales
  • 2,061
  • 7
  • 26
  • 42
  • 1
    When does it fail, application load or when you request the health check endpoint? – SBI Nov 30 '21 at 01:36
  • 1
    How are you getting your db context? Is the service scoped? Do you have any singleton services getting a context without creating a new scope? – Jeremy Lakeman Nov 30 '21 at 04:52
  • @SBI really both-- health check endpoint starts during the time application loads-- all in the ConfigureServices method in startup – Los Morales Nov 30 '21 at 15:20
  • @JeremyLakeman Yes the db context is injected. No on singleton services. – Los Morales Nov 30 '21 at 15:20
  • I got this working by using scopes-- the answer is here: [Scoped answer](https://stackoverflow.com/questions/52603280/console-application-dbcontext-instance-cannot-be-used-inside-onconfiguring) – Los Morales Nov 30 '21 at 17:08
  • Where did you need to create the scope? I don't see the ctor on your healthcheck accepting the dbService repository from the DI. I'm guessing you're either using a service locator pattern to get the service from the `HealthCheckContext`, so you're scoping in your `CheckHealthAsync` impl, or you're injecting it in the ctor and that code is just omitted in your Q, in which case I assume you'd need to use a scoped provider to give the service to the impl when using `AddTypeActivatedCheck`. – Josh Gust Nov 30 '21 at 17:56
  • 1
    You can also explore using MapWhen to restrict activating healthcheck middlewares only when requested. More info [here](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-6.0#restrict-health-checks-with-mapwhen). – SBI Dec 01 '21 at 01:05
  • @SBI Seems interesting, just not sure how to 'mapWhen dbContext has been done initializing'. – Los Morales Dec 01 '21 at 14:53
  • Not sure about what application requirements are, what I think is, it might not be necessary to activate the health check endpoints at application load time. Hence, if we just in time activate health check request pipeline when needed using MapWhen, that would suffice the use case. – SBI Dec 01 '21 at 22:16

1 Answers1

2

You may be able to get around this by adding a DbContextCheck with a custom query? (docs)

My understanding is that you can do something like this:

services.AddHealthChecks()
        .AddDbContextCheck<YourDbContext>(customTestQuery:
            (db, cancel) => Task.FromResult(db.Users.Any()));

With that said, you may have a concurrency problem here with how your DbContext is being used. Perhaps there's an async call being made that isn't awaited, or maybe there's something wrong with how your context lifetime is configured.

Without knowing the details of how you're registering and configuring your dbcontext or how it's being injected (or not) into what looks like a repository (DbService.GetUserCount()) I can point you toward some additional documentation about avoiding DbContext threading issues and hope it is useful.

Josh Gust
  • 4,102
  • 25
  • 41
  • There is nothing to be awaited since the service that uses the dbcontext is not async. As for the AddDbContextCheck, that just tests to see if the connection to the DB is ok. – Los Morales Nov 30 '21 at 15:45
  • As for the parallel dbcontext threading issues, I don't think that is what happening since 1) I'm not getting that type of multiple operations error message, 2) all DbContexts are DI'd 3) I'm getting more of a "can't use DbContext since it is still being configured" error – Los Morales Nov 30 '21 at 15:53
  • It's a little odd that you would want anything more than this. Best practice is to test connection and at most perform a quickly returning query such as `select 1 * from dbo.Users` to prove that your app can connect and can query. What change would you be hoping to detect by probing through your repository? – Josh Gust Nov 30 '21 at 16:33
  • Well if you can test the connection using a query, then why not with a dbcontext and the query being a little more specific than a select 1? Shouldn't it work the same? – Los Morales Nov 30 '21 at 16:55
  • 1
    The "solution" (work around, really) I proposed IS using your dbcontext to execute a query. What it's not doing is using your repository to do it. I do agree with you, the probe you've defined seems like it should be working. Have you proven that you're getting args to the probe as expected (debug)? Maybe look at `.AddTypeActivatedCheck(...)`. I am a little suspicious that this error happens to you at startup. I don't think checks exec on startup. They're supposed to run in conjunction w/ a mapped endpoint and its options. – Josh Gust Nov 30 '21 at 17:05
  • I actually got everything working the way I had it but added scopes. I posted my answer above. Thanks for helping though. – Los Morales Nov 30 '21 at 17:09