Home Unexpected Exceptions in EF Core 7+ Migrations
Post
Cancel

Unexpected Exceptions in EF Core 7+ Migrations

I recently encountered an intriguing issue with EF Core migrations that I hadn’t seen before — suddenly getting exceptions when trying to create or apply migrations. Interestingly, the migrations would still succeed. Have you been experiencing this error? In this post, I’ll explain why it happens and what to do about it.

EF Core Migrations and the Generic Host

When the CLI runs commands like dotnet ef database update, it starts up your application to access your DbContext and its configuration. After completing its tasks, it intentionally aborts the host to shut everything down, achieved by throwing an exception.

This exception is intended to be caught and handled by the CLI itself, not by your application code. However, if you have global exception handling in place, it might inadvertently catch this exception and log it, leading to potential confusion.

For example, consider this Program.cs file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try
{
    var builder = WebApplication.CreateBuilder(args);

    // ...

    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Unhandled exception");
}
finally
{
    Log.Information("Shut down complete");
    Log.CloseAndFlush();
}

In this example, the entire host is wrapped in a try/catch block to log any exceptions. However, this approach means the exception intentionally thrown by the EF Core CLI to shut down the host will be caught and logged here.

Excluding This Exception

Since this exception is expected and doesn’t cause any problem, the simplest thing to do is ignore it. As you can see in this GitHub discussion, if you’re globally catching exceptions, you should exclude this particular one, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try
{
    var builder = WebApplication.CreateBuilder(args);

    // ...

    app.Run();
}
catch (Exception ex) when (ex.GetType().Name != "StopTheHostException")
{
    Log.Fatal(ex, "Unhandled exception");
}
finally
{
    Log.Information("Shut down complete");
    Log.CloseAndFlush();
}

In my case, I was working on an IdentityServer deployed from one of the IdentityServer templates, which do contain this code (exactly this code, in fact). So what went wrong?

What Changed Between EF Core 6 and EF Core 7?

The IdentityServer that I was working on had been deployed from a template back on .NET 6. However, as you can see in this comment on the above mentioned GitHub issue, StopTheHostException, (the exception used by the EF Core CLI to terminate the host), was renamed to HostAbortedException. Consequently, the exception filter was no longer working.

The fix is straightforward - simply filter for the correct exception:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try
{
    var builder = WebApplication.CreateBuilder(args);

    // ...

    app.Run();
}
catch (Exception ex) when (ex.GetType().Name != "HostAbortedException")
{
    Log.Fatal(ex, "Unhandled exception");
}
finally
{
    Log.Information("Shut down complete");
    Log.CloseAndFlush();
}

Alternatively:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try
{
    var builder = WebApplication.CreateBuilder(args);

    // ...

    app.Run();
}
catch (HostAbortedException)
{
    // Do nothing, or optionally log an informational message:
    Log.Information("Host terminated expectedly by EF Core CLI");
}
catch (Exception ex)
{
    Log.Fatal(ex, "Unhandled exception");
}
finally
{
    Log.Information("Shut down complete");
    Log.CloseAndFlush();
}

Duende have in fact updated the IdentityServer template to accomodate this change:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ... (previous code)

try
{
    var builder = WebApplication.CreateBuilder(args);

    // ...

    app.Run();
}
catch (Exception ex) when(
                            // https://github.com/dotnet/runtime/issues/60600
                            ex.GetType().Name is not "StopTheHostException"
                            // HostAbortedException was added in .NET 7, but since we target .NET 6 we
                            // need to do it this way until we target .NET 8
                            && ex.GetType().Name is not "HostAbortedException" 
                        )
{
    Log.Fatal(ex, "Unhandled exception");
}
finally
{
    Log.Information("Shut down complete");
    Log.CloseAndFlush();
}

In this case, the current version supports .NET 6 and above, so the template now accomodates both exception names.

Conclusion

If you’ve been seeing unexpected exceptions when creating or applying migrations recently, it’s likely you have some global exception handling and you’ve updated from .NET 6 to .NET 7 (or, more specifically, EF Core 6 to EF Core 7) or above. Although these particular exceptions are harmless, filtering them from your logs can simplify troubleshooting and is easy to do.

Have you been seeing this exception? If so, I hope you found this useful. Let me know!

This post is licensed under CC BY 4.0 by the author.

Book review: The Phoenix Project

Remembering Mike