question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Mapping Postgres tstzrange to NpgsqlRange with linq2db

See original GitHub issue

OK, this one is strange and I can’t find anything that would help me out in resolving the issue, but it’s entirely possible I’m doing something wrong. It seems that if I map the range to NpgsqlRange<DateTimeOffset>, I can save the data just fine but I can’t query it, and if I map it as NpgsqlRange<DateTime> I can’t insert a new row, but I can query existing data set.

Let’s start with the first option: mapping the range to NpgsqlRange<DateTimeOffset> property for reading & writing.

The insert goes fine, but on any query involving the property I get:

LinqToDB.Common.LinqToDBConvertException: Cannot convert value '[01/25/2021 18:37:30,01/26/2021 18:37:30]' to type 'NpgsqlTypes.NpgsqlRange`1[[System.DateTimeOffset, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'
 ---> System.InvalidCastException: Object must implement IConvertible.
   at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at LinqToDB.Common.ConvertBuilder.ConvertDefault(Object value, Type conversionType)
   --- End of inner exception stack trace ---
   at LinqToDB.Common.ConvertBuilder.ConvertDefault(Object value, Type conversionType)
   at lambda_method187(Closure , IDataReader )
   at LinqToDB.Expressions.ConvertFromDataReaderExpression.ColumnReader.GetValue(IDataReader dataReader)
   at lambda_method186(Closure , IQueryRunner , IDataReader )
   at LinqToDB.Linq.QueryRunner.Mapper`1.Map(IDataContext context, IQueryRunner queryRunner, IDataReader dataReader)
   at LinqToDB.Linq.QueryRunner.ExecuteQueryAsync[T](Query query, IDataContext dataContext, Mapper`1 mapper, Expression expression, Object[] ps, Object[] preambles, Int32 queryNumber, Func`2 func, TakeSkipDelegate skipAction, TakeSkipDelegate takeAction, CancellationToken cancellationToken)
   at LinqToDB.Linq.QueryRunner.ExecuteQueryAsync[T](Query query, IDataContext dataContext, Mapper`1 mapper, Expression expression, Object[] ps, Object[] preambles, Int32 queryNumber, Func`2 func, TakeSkipDelegate skipAction, TakeSkipDelegate takeAction, CancellationToken cancellationToken)
   at LinqToDB.Linq.Builder.FirstSingleBuilder.FirstSingleContext.<>c__DisplayClass3_0`1.<<GetFirstElement>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at LinqToDB.Linq.ExpressionQuery`1.LinqToDB.Async.IQueryProviderAsync.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at linq2db_repro.Database.Program.Main(String[] args) in /app/Program.cs:line 51
   at linq2db_repro.Database.Program.Main(String[] args) in /app/Program.cs:line 52
   at linq2db_repro.Database.Program.<Main>(String[] args)

Steps to reproduce

https://github.com/pcwiek/linq2db-repro

For the ease of use & future-proofing, I’ll also put it here inline


namespace linq2db_repro
{
    [Table("test")]
    public class TestEntity
    {

        [NotNull]
        [Column("timestamp_range")]
        public NpgsqlRange<DateTimeOffset> RangeMappedAsDateTimeOffset { get; set; }

        [NotNull]
        [Column("timestamp_range")]
        public NpgsqlRange<DateTime> RangeMappedAsDateTime { get; set; }
    }

    public class Database : DataConnection
    {
        public Database(string connectionString) : base(
            new PostgreSQLDataProvider(PostgreSQLVersion.v95),
            connectionString
        )
        {
        }

        public ITable<TestEntity> TestEntities => GetTable<TestEntity>();
    }

    class Program
    {
        async static Task Main(string[] args)
        {
            const string connectionString = "Host=db;Database=repro;Username=postgres;Password=postgres;";
            await using var db = new Database(connectionString);
            await db.ExecuteAsync(@"
                CREATE TABLE IF NOT EXISTS test (
                  timestamp_range tstzrange not null 
                )
            ");
            // The insert works
            await db.TestEntities.InsertAsync(() => new TestEntity
            {
                RangeMappedAsDateTimeOffset = new NpgsqlRange<DateTimeOffset>(
                    DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)),
                    DateTime.UtcNow)
            });
            // This fails
            var range = await db.TestEntities.Select(a => a.RangeMappedAsDateTimeOffset).FirstAsync();
            Console.WriteLine("Range: {0}", range);
        }
    }
}

OK, this fails with the exception above, how about DateTime mapping? If we use RangeMappedAsDateTime property with DateTime type, on insert we get:

Npgsql.PostgresException (0x80004005): 42804: column "timestamp_range" is of type tstzrange but expression is of type tsrange
   at Npgsql.NpgsqlConnector.<ReadMessage>g__ReadMessageLong|194_0(NpgsqlConnector connector, Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
   at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteNonQuery(Boolean async, CancellationToken cancellationToken)
   at LinqToDB.Data.DataConnection.ExecuteNonQueryAsync(CancellationToken cancellationToken)
   at LinqToDB.Data.DataConnection.QueryRunner.ExecuteNonQueryAsync(CancellationToken cancellationToken)
   at LinqToDB.Linq.QueryRunner.NonQueryQueryAsync(Query query, IDataContext dataContext, Expression expression, Object[] ps, Object[] preambles, CancellationToken cancellationToken)
   at LinqToDB.Linq.ExpressionQuery`1.LinqToDB.Async.IQueryProviderAsync.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at LinqToDB.LinqExtensions.InsertAsync[T](ITable`1 target, Expression`1 setter, CancellationToken token)
   at linq2db_repro.Program.Main(String[] args) in /app/Program.cs:line 47
   at linq2db_repro.Program.Main(String[] args) in /app/Program.cs:line 54
   at linq2db_repro.Program.<Main>(String[] args)
  Exception data:
    Severity: ERROR
    SqlState: 42804
    MessageText: column "timestamp_range" is of type tstzrange but expression is of type tsrange
    Hint: You will need to rewrite or cast the expression.
    Position: 49
    File: parse_target.c
    Line: 592
    Routine: transformAssignedExpr

In the meantime I figured out that using DateTimeOffset to insert data, and query using the property mapped as DateTime works fine, although it is quite a hacky workaround:

await db.TestEntities.InsertAsync(() => new TestEntity
{
    RangeMappedAsDateTimeOffset = new NpgsqlRange<DateTimeOffset>(
        DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)),
        DateTime.UtcNow)
});
NpgsqlRange<DateTime> range = await db.TestEntities.Select(a => a.RangeMappedAsDateTime).FirstAsync();
Console.WriteLine("Range: {0}", range);

When executed:

root@6846b48528a2:/app# dotnet run dist/linq2db-repro.dll
Range: [01/25/2021 18:37:30,01/26/2021 18:37:30]

Any ideas?

Environment details

linq2db version: 3.2.3 Database Server: Postgres 12 (postgres:12 docker image) Database Provider: Npgsql 5.0.3 Operating system: Debian GNU/Linux 10 (buster) (mcr.microsoft.com/dotnet/sdk:5.0 docker image) .NET Framework: .NET 5.0.102

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
MaceWinducommented, Mar 14, 2021

What I can say. NpgsqlRange is quite a sad story currently and I would recommend to use default types with it. For tstzrange it is DateTime.

Anyway, I’m working on improving this on our side.

0reactions
MaceWinducommented, Mar 14, 2021

Another related issue #3563

Read more comments on GitHub >

github_iconTop Results From Across the Web

Ranges and Multiranges
This allows you to have properties of type NpgsqlRange<float> , which will be mapped to PostgreSQL floatrange . The above does not create...
Read more >
Documentation: 15: 8.17. Range Types
Range types are data types representing a range of values of some element type (called the range's subtype). For instance, ranges of timestamp...
Read more >
Can i use daterange column and work with EF for ...
I want to be able to map my field to some property. I want to be able select, remove and insert new based...
Read more >
C# (CSharp) MappingSchema.SetConvertExpression ...
These are the top rated real world C# (CSharp) examples of MappingSchema.SetConvertExpression extracted from open source projects. You can rate examples to help ......
Read more >
Mapping .NET Timestamps to PostgreSQL - Shay Rojansky
One of the tasks of a database driver is to map two different type systems to one another; in our case, the ....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found