Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't write arrays of user-defined ranges #3137

Closed
NazarPolevyi opened this issue Mar 24, 2024 · 7 comments · Fixed by #3342
Closed

Can't write arrays of user-defined ranges #3137

NazarPolevyi opened this issue Mar 24, 2024 · 7 comments · Fixed by #3342
Assignees
Labels
bug Something isn't working
Milestone

Comments

@NazarPolevyi
Copy link

NazarPolevyi commented Mar 24, 2024

Steps to reproduce

Hi, after Npgsql.EntityFrameworkCore.PostgreSQL update from to 7.0.4 to 8.0.2. We have started to receive an exception when saving into db.
InvalidCastException: Writing values of 'NpgsqlTypes.NpgsqlRange`1[[System.TimeSpan, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]][]' is not supported for parameters having NpgsqlDbType '-2147483608'.


public class RestrictionSettings
{
    [Column("id")]
    public Guid Id { get; set; }

    [Column("allowed_time_ranges", TypeName = "timerange[]")]
    public NpgsqlRange<TimeSpan>[]? AllowedTimeRanges { get; set; }
}

private readonly NpgsqlDataSource _dataSource;
private readonly ILoggerFactory _loggerFactory;

public class CustomDbContext : DbContext
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly ILoggerFactory _loggerFactory;

    public CustomDbContext (NpgsqlDataSource dataSource, ILoggerFactory loggerFactory)
    {
        _dataSource = dataSource;
        _loggerFactory = loggerFactory;
    }

    public DbSet<RestrictionSettings> RestrictionSettings{ get; set; }

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
      if (!optionsBuilder.IsConfigured)
      {
          optionsBuilder
              .UseLoggerFactory(_loggerFactory)
              .UseNpgsql(
                  _dataSource,
                  o => o
                      .SetPostgresVersion(new Version(10, 21))
                      .MapRange<TimeSpan>("timerange", subtypeName: "time without time zone"));

          optionsBuilder
              .EnableDetailedErrors()
              .EnableSensitiveDataLogging();
      }
  }

 protected override void OnModelCreating(ModelBuilder modelBuilder)
 {
     modelBuilder.HasPostgresRange(name: "timerange", subtype: "time without time zone");
  }
}

Registration example

  services.AddSingleton<Func<CustomDbContext>>(p => () =>
      {
          var connectionString = "connection string";

          var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
          dataSourceBuilder.EnableDynamicJson().EnableUnmappedTypes();
          var dataSource = dataSourceBuilder.Build();

          return new CustomDbContext (
              dataSource,
              p.GetRequiredService<ILoggerFactory>());
      });

The timerange it's a custom timerange type:

` CREATE FUNCTION time_subtype_diff(x time, y time) RETURNS float8 AS
'SELECT EXTRACT(EPOCH FROM (x - y))' LANGUAGE sql STRICT IMMUTABLE;

CREATE TYPE timerange AS RANGE (
    subtype = time without time zone,
    subtype_diff = time_subtype_diff
);`

How can we solve this error? Before update it was working,

Exception message:
`Microsoft.EntityFrameworkCore.DbUpdateException
  HResult=0x80131500
  Message=An error occurred while saving the entity changes. See the inner exception for details.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.<ExecuteAsync>d__50.MoveNext()
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.<ExecuteAsync>d__9.MoveNext()
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.<ExecuteAsync>d__9.MoveNext()
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.<ExecuteAsync>d__9.MoveNext()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__111.MoveNext()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__115.MoveNext()
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.<ExecuteAsync>d__7`2.MoveNext()
   at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__63.MoveNext()
   at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__63.MoveNext()
   at 

  This exception was originally thrown at this call stack:
    [External Code]
Inner Exception 1:
InvalidCastException: Writing values of 'NpgsqlTypes.NpgsqlRange`1[[System.TimeSpan, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]][]' is not supported for parameters having NpgsqlDbType '-2147483608'.
`
@NinoFloris
Copy link
Member

NinoFloris commented Mar 25, 2024

@roji this might be related to https://github.com/npgsql/npgsql/pull/5123/files#r1325747341

If so setting the actual datatypename of the range on the param should solve it.

EDIT: traced the regstration to

var unquotedRangeStoreType = rangeDefinition.SchemaName is null
which suggests that's already happening. Not sure how we end up with a is not supported for parameters having NpgsqlDbType kind of error. That only happens if it was set somehow.

@NinoFloris
Copy link
Member

NinoFloris commented Mar 25, 2024

@NazarPolevyi

I notice your entity has the following attribute

  [Column("allowed_time_ranges", TypeName = "timerange[]")]
    public NpgsqlRange<TimeSpan>? AllowedTimeRanges { get; set; }

Specifically timerange[] while the clr type is not an array/list. Can you remove [] and try again?

@NinoFloris NinoFloris transferred this issue from npgsql/npgsql Mar 25, 2024
@NazarPolevyi
Copy link
Author

NazarPolevyi commented Mar 25, 2024

@NinoFloris
Thanks for answer.
@roji What do you think?

I'm sorry. The issue is actually related to array of ranges NpgSqlRange<TimeSpan>[]. So the entity class should look like this.

public class RestrictionSettings
{
    [Column("id")]
    public Guid Id { get; set; }

    [Column("allowed_time_ranges", TypeName = "timerange[]")]
    public NpgsqlRange<TimeSpan>[]? AllowedTimeRanges { get; set; }
}

Usual NpgSqlRange<TimeSpan> works fine.


public class RestrictionSettings
{
    [Column("id")]
    public Guid Id { get; set; }

    [Column("allowed_time_ranges", TypeName = "timerange")]
    public NpgsqlRange<TimeSpan> AllowedTimeRanges { get; set; }
}

Is it possible to use array(NpgsqlRange[]) now in 8.02 version?
Btw, NpgsqlRange<DateTime>[] works well without specifying custom range type. But for the NpgsqlRange<TimeSpan>[] it's not

@roji
Copy link
Member

roji commented Oct 29, 2024

@NinoFloris going over some untriaged issues - am assigning to you as you've already looked into it and there's a good chance this is an Npgsql-level problem.

EF minimal repro
await using var context = new BlogContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

await using (var connection = await BlogContext.DataSource.OpenConnectionAsync())
{
    await connection.ReloadTypesAsync();
}

context.Blogs.Add(
    new()
    {
        AllowedTimeRanges =
        [
            new(TimeSpan.FromHours(1), TimeSpan.FromHours(2)),
            new(TimeSpan.FromHours(4), TimeSpan.FromHours(6))
        ]
    });
await context.SaveChangesAsync();

public class BlogContext : DbContext
{
    public static readonly NpgsqlDataSource DataSource;

    static BlogContext()
    {
        var dataSourceBuilder = new NpgsqlDataSourceBuilder("Host=localhost;Username=test;Password=test");
        dataSourceBuilder.EnableUnmappedTypes();
        DataSource = dataSourceBuilder.Build();
    }

    public DbSet<Blog> Blogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseNpgsql(DataSource, o => o.MapRange<TimeSpan>("timerange", subtypeName: "time without time zone"))
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.HasPostgresRange(name: "timerange", subtype: "time without time zone");
    }}

public class Blog
{
    public int Id { get; set; }

    [Column(TypeName = "timerange[]")]
    public NpgsqlRange<TimeSpan>[] AllowedTimeRanges { get; set; }
}

@roji roji added the bug Something isn't working label Oct 29, 2024
@roji roji added this to the Backlog milestone Oct 29, 2024
@NinoFloris
Copy link
Member

NinoFloris commented Oct 30, 2024

Quickly ran the repro in the Npgsql project, EF Pg ends up setting NpgsqlDbType.Array | NpgsqlDbType.Unknown onto the parameter's NpgsqlDbType somewhere, though it also sets timerange[] as the datatype name.

As NpgsqlDbType takes precedence we don't know what NpgsqlDbType.Array | NpgsqlDbType.Unknown resolves to. Notably in PG unknown also cannot be composed (unknown[] is an error) so it's also not something we should even want to support.

I expect that when we stop setting NpgsqlDbType this way the issue will be resolved.

This with some composition of the array mapping will cause it:

public static NpgsqlRangeTypeMapping CreatUserDefinedRangeMapping(
string quotedRangeStoreType,
string unquotedRangeStoreType,
Type rangeClrType,
RelationalTypeMapping subtypeMapping)
=> new(quotedRangeStoreType, rangeClrType, rangeNpgsqlDbType: NpgsqlDbType.Unknown, subtypeMapping)
{
UnquotedStoreType = unquotedRangeStoreType
};

@roji
Copy link
Member

roji commented Oct 30, 2024

Quickly ran the repro in the Npgsql project, EF Pg ends up setting NpgsqlDbType.Array | NpgsqlDbType.Unknown onto the parameter's NpgsqlDbType somewhere, though it also sets timerange[] as the datatype name.

EF sets both NpgsqlDbType and DataTypeName? Weird...

roji added a commit to roji/efcore.pg that referenced this issue Oct 31, 2024
@roji roji assigned roji and unassigned NinoFloris Oct 31, 2024
@roji roji modified the milestones: Backlog, 8.0.11 Oct 31, 2024
@roji
Copy link
Member

roji commented Oct 31, 2024

Thanks for looking @NinoFloris, yeah, this was a result of some complexity around using NpgsqlDbType vs. DataTypeName in addition to arrays. The whole area probably needs a bit of cleanup at some point, but submitted #3342 in the meantime as a targeted fix.

@roji roji changed the title Writing values of 'NpgsqlTypes.NpgsqlRange`1[[System.TimeSpan, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]][]' is not supported for parameters having NpgsqlDbType '-2147483608'. Can't write arrays of user-defined ranges Oct 31, 2024
@roji roji closed this as completed in 592588d Oct 31, 2024
roji added a commit that referenced this issue Oct 31, 2024
Fixes #3137

(cherry picked from commit 592588d)
WhatzGames pushed a commit to WhatzGames/efcore.pg that referenced this issue Dec 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants