Database exception when using AspNetCore.HealthChecks.NpgSql with EFCore.NamingConventions
Hey! What happened:
While running the .NET 8 application I got the error:
Npgsql.PostgresException (0x80004005): 42P01: relation "configurations" does not exist.
The error is thrown when using method from AspNetCore.HealthChecks.NpgSql.
Something like that:
builder.Services
.AddHealthChecksUI()
.AddPostgreSqlStorage(
databaseConfiguration.GetReadConnectionString(),
options =>
{
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
options.UseSnakeCaseNamingConvention(); // this line breaks the whole code
})
.AddPostgreSqlStorage(databaseConfiguration.GetWriteConnectionString(),
options =>
{
options.UseSnakeCaseNamingConvention(); // this line breaks the whole code
});
Exception stack:
info: HealthChecks.UI.Core.HostedService.UIInitializationHostedService[0]
Initializing UI Database
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
Failed executing DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT EXISTS (
SELECT 1
FROM configurations AS c)
fail: Microsoft.EntityFrameworkCore.Query[10100]
An exception occurred while iterating over the results of a query for context type 'HealthChecks.UI.Data.HealthChecksDb'.
Npgsql.PostgresException (0x80004005): 42P01: relation "configurations" does not exist
POSITION: 41
at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
Exception data:
Severity: ERROR
SqlState: 42P01
MessageText: relation "configurations" does not exist
Position: 41
File: parse_relation.c
Line: 1452
Routine: parserOpenTable
Npgsql.PostgresException (0x80004005): 42P01: relation "configurations" does not exist
POSITION: 41
at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
Exception data:
Severity: ERROR
SqlState: 42P01
MessageText: relation "configurations" does not exist
Position: 41
File: parse_relation.c
Line: 1452
Routine: parserOpenTable
fail: Microsoft.Extensions.Hosting.Internal.Host[11]
Hosting failed to start
Npgsql.PostgresException (0x80004005): 42P01: relation "configurations" does not exist
POSITION: 41
at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
at HealthChecks.UI.Core.HostedService.UIInitializationHostedService.InitializeDatabaseAsync(IServiceProvider sp) in /_/src/HealthChecks.UI/Core/HostedService/UIInitializationHostedService.cs:line 62
at HealthChecks.UI.Core.HostedService.UIInitializationHostedService.StartAsync(CancellationToken cancellationToken) in /_/src/HealthChecks.UI/Core/HostedService/UIInitializationHostedService.cs:line 34
at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__15_1(IHostedService service, CancellationToken token)
at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
Exception data:
Severity: ERROR
SqlState: 42P01
MessageText: relation "configurations" does not exist
Position: 41
File: parse_relation.c
Line: 1452
Routine: parserOpenTable
Unhandled exception. Npgsql.PostgresException (0x80004005): 42P01: relation "configurations" does not exist
POSITION: 41
at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
at HealthChecks.UI.Core.HostedService.UIInitializationHostedService.InitializeDatabaseAsync(IServiceProvider sp) in /_/src/HealthChecks.UI/Core/HostedService/UIInitializationHostedService.cs:line 62
at HealthChecks.UI.Core.HostedService.UIInitializationHostedService.StartAsync(CancellationToken cancellationToken) in /_/src/HealthChecks.UI/Core/HostedService/UIInitializationHostedService.cs:line 34
at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__15_1(IHostedService service, CancellationToken token)
at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Program.<Main>$(String[] args) in C:\Users\Sh0w3D\RiderProjects\XXX-XXX\XXX.Api\Program.cs:line 94
at Program.<Main>(String[] args)
Exception data:
Severity: ERROR
SqlState: 42P01
MessageText: relation "configurations" does not exist
Position: 41
File: parse_relation.c
Line: 1452
Routine: parserOpenTable
Process finished with exit code -532,462,766.
I am entirely sure that adding this convention would require creating a migration.
What you expected to happen:
The convention should be applied and app should run without any error.
How to reproduce it (as minimally and precisely as possible):
Create API project with class library, add 2 database instances - one for read and one for write.
Add HealthChecks for both instances, they should use the UseSnakeCaseNamingConvention() method.
NuGet references below
Source code sample:
Service registration:
- Add both databases using
AddDbContextFactory:
var databaseConfiguration = configuration.GetRequiredSection(DatabaseConfiguration.JsonNode)
.Get<DatabaseConfiguration>();
ArgumentNullException.ThrowIfNull(databaseConfiguration);
services.AddDbContextFactory<XXXReadDbContext>((_, options) =>
{
options.UseNpgsql(databaseConfiguration.GetReadConnectionString(),
opt => { opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); })
.UseSnakeCaseNamingConvention()
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}, ServiceLifetime.Scoped);
services.AddDbContextFactory<XXXWriteDbContext>(
(_, options) =>
{
options.UseNpgsql(databaseConfiguration.GetWriteConnectionString(),
opt => { opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); })
.UseSnakeCaseNamingConvention();
}, ServiceLifetime.Scoped);
return services;
- Register HealthChecks.UI:
var databaseConfiguration = builder.Configuration.GetRequiredSection(DatabaseConfiguration.JsonNode)
.Get<DatabaseConfiguration>();
ArgumentNullException.ThrowIfNull(databaseConfiguration);
builder.Services
.AddHealthChecksUI()
.AddPostgreSqlStorage(
databaseConfiguration.GetReadConnectionString(),
options =>
{
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
options.UseSnakeCaseNamingConvention(); // this line breaks the whole code
})
.AddPostgreSqlStorage(databaseConfiguration.GetWriteConnectionString(),
options =>
{
options.UseSnakeCaseNamingConvention(); // this line breaks the whole code
});
- Register actual HealthChecks:
builder.Services.AddHealthChecks()
.AddNpgSql(databaseConfiguration.GetReadConnectionString(),
name: "Read Database")
.AddNpgSql(databaseConfiguration.GetWriteConnectionString(),
name: "Write Database");
- After
builder.Build()register app.HealthChecks
app.UseHealthChecks("/health");
app.UseHttpsRedirection();
app
.UseRouting()
.UseEndpoints(config =>
{
config.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
});
Anything else we need to know?:
Project configuration: API:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="8.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="8.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.PostgreSQL.Storage" Version="8.0.0" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.0"/>
</ItemGroup>
</Project>
DataStorage:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LinqKit" Version="1.2.5"/>
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.1"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0"/>
</ItemGroup>
</Project>
Environment:
- .NET version: 8
- Healthchecks version:
Include="AspNetCore.HealthChecks.NpgSql" Version="8.0.0" />
Include="AspNetCore.HealthChecks.UI" Version="8.0.0" />
Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.0" />
Include="AspNetCore.HealthChecks.UI.PostgreSQL.Storage" Version="8.0.0" />
- Operative system: Windows 11
- Others:
- Postgres 16
I just bumped into this issue myself. @kamil-oberaj - Do you have a workaround for this issue yet, or are you still stuck?
Same setup as you and same issue. The tables and columns made by the migration use pascal case but when the health checks data context queries for data it then uses the snake case naming convention.
It appears migrations are custom per db provider. Afaik this can't be done in any other way than to make the table and column names snake_case in their migrations, for example the initial migration is here https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/blob/master/src/HealthChecks.UI.PostgreSQL.Storage/Migrations/20200410133103_initial.cs
this is not a huge deal and I could prepare PR for it, but the authors should let us know if it's ok to do it this way