npgsql / efcore.pg

Entity Framework Core provider for PostgreSQL

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

InvalidCastException with Instant field (using NodaTime) when many database connections are open

draudrau opened this issue · comments

Hi,

I have some projects with many DbContext, or one DbContext and a Npgsql health check.
If one DbContext is using NodaTime and anonther not, with the same connection string for the both, I can get this exception :

System.InvalidCastException: Reading as 'NodaTime.Instant' is not supported for fields having DataTypeName 'timestamp with time zone'  
   at Npgsql.Internal.AdoSerializerHelpers.<GetTypeInfoForReading>g__ThrowReadingNotSupported|0_0(Type type, String displayName, Exception inner)  
   at Npgsql.Internal.AdoSerializerHelpers.GetTypeInfoForReading(Type type, PostgresType postgresType, PgSerializerOptions options)  
   at Npgsql.BackendMessages.FieldDescription.<GetInfo>g__GetInfoSlow|50_0(Type type, ColumnInfo& lastColumnInfo)  
   at Npgsql.BackendMessages.FieldDescription.GetInfo(Type type, ColumnInfo& lastColumnInfo)  
   at Npgsql.NpgsqlDataReader.<GetInfo>g__Slow|133_0(ColumnInfo& info, PgConverter& converter, Size& bufferRequirement, Boolean& asObject, <>c__DisplayClass133_0&)  
   at Npgsql.NpgsqlDataReader.GetFieldValueCore[T](Int32 ordinal)  
   at Npgsql.NpgsqlDataReader.GetFieldValue[T](Int32 ordinal)  
   at lambda_method241(Closure, DbDataReader, Int32])  
   at Microsoft.EntityFrameworkCore.Query.Internal.BufferedDataReader.BufferedDataRecord.ReadObject(DbDataReader reader, Int32 ordinal, ReaderColumn column)  
   at Microsoft.EntityFrameworkCore.Query.Internal.BufferedDataReader.BufferedDataRecord.ReadRow()  
   at Microsoft.EntityFrameworkCore.Query.Internal.BufferedDataReader.BufferedDataRecord.InitializeAsync(DbDataReader reader, IReadOnlyList`1 columns, CancellationToken cancellationToken)  
   at Microsoft.EntityFrameworkCore.Query.Internal.BufferedDataReader.InitializeAsync(IReadOnlyList`1 columns, CancellationToken cancellationToken)  
   at Microsoft.EntityFrameworkCore.Query.Internal.BufferedDataReader.InitializeAsync(IReadOnlyList`1 columns, 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.SplitQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)  
   at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.<>c__DisplayClass30_0`2.<<ExecuteAsync>b__0>d.MoveNext()  

The DbContext without NodaTime must be the first to make a connection to the database.
The the second DbContext using NodaTime will reuse the database connection and will have the InvalidCastException.

You can reproduce with this demo project :
https://github.com/draudrau/IssueSamples/tree/main/NodaTimeIssue

You will find three sample queries here :
https://github.com/draudrau/IssueSamples/blob/main/NodaTimeIssue/NodaTimeIssue/NodaTimeIssue.http

Test then in different order, restarting the debug at each time.

Best regards

Yes, that is unfortunately the expected behavior; if you use a simple connection string to configure EF, then at the lower Npgsql level the same connection pool is used, and the first time it's initialized (e.g. without NodaTime) is what takes effect for all subsequent use.

The right way forward here is to create two NpgsqlDataSource instances (see the docs) - one with NodaTime, and one without. Then, pass the correct NpgsqlDataSource to EF's UseNpgsql() instead of the connection string. This creates a simple, clear separation between usage/non-usage of NodaTime all the way down the stack.

Another workaround is to simply always turn on NodaTime. Even if you don't use it, it shouldn't interfere with regular DateTime usage.

Finally, you can use different connection strings to have separate connection pools at the lower Npgsql level (as you see to be doing in your demo project). That ensures having different pools, similar to what NpgsqlDataSource achieves, but in a hackier worse way.

I'll go ahead and close this issue as the behavior is by-design, but don't hesitate to post back with more questions etc. I can reopen as necessary.

Thanks,

I tried this on one of my projects and it seems to work.
Added a data source in the services collection and reused it on health checks and DbContext.