-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Npgsql 6.0.0-rc.1 DateTime issue #1716
Comments
I'm trying to understand what the issue is - as I understand it, it looks like if the behavior has changed, the Hangfire would need to change to match. What would Dapper do differently here? |
The issue is when I try to execute the above statement with the same parameter types and values using raw
|
It seems that Dapper does not set Kind property to DateTimeKind.Utc when dealing with DateTime. Based on the new breaking changes in npgsql, Kind must be set to DateTimeKind.Utc to use TimeStampzHandler: https://github.com/npgsql/npgsql/blob/3d2a0af5c6d994bb2efad3bf6f456603b077d607/src/Npgsql/TypeMapping/BuiltInTypeHandlerResolver.cs#L459 |
Is this for inputs? Or outputs? If inputs: that seems like something consumer code should be doing, after evaluating what the data represents. If Dapper was able to just blindly convert to UTC, then I would expect Npgsql would already be doing the same internally, so it wouldn't be an issue. If outputs: we don't creat the value - the ADO.NET provider does. Or are we talking about some more specific niche case here? |
This particular example is for inputs and I know for sure that the passed value is indeed DateTime with UTC kind. Npgsql does not do any conversion anymore and instead picks the corresponding time handler based on "Kind: value: https://github.com/npgsql/npgsql/blob/3d2a0af5c6d994bb2efad3bf6f456603b077d607/src/Npgsql/TypeMapping/BuiltInTypeHandlerResolver.cs#L459. The workaround is to pass DateTimeOffset but I just want to find the root cause of issue before the release date of .Net 6 but most likely the bug is on Npgsql side. |
Well, I have located the actual issue but have no idea how can it be resolved. I had to read that dam IL code and it basically does this :
But if you create the parameter without specifying any Edit |
Ok, I think I kind of know what is going on. When DbType is set to DbType.DateTime, Npgsql maps it to NpgsqlDbType.Timestamp (https://github.com/npgsql/npgsql/blob/dba60fb5a87bc6d661bbdcd2a31be687edfc10e9/src/Npgsql/TypeMapping/GlobalTypeMapper.cs#L435) and later in NpgsqlParameter class (https://github.com/npgsql/npgsql/blob/dba60fb5a87bc6d661bbdcd2a31be687edfc10e9/src/Npgsql/NpgsqlParameter.cs#L505), TimestampHanler is selected instead of TimestampZHanlder resulting the issue above. I don't know if this is the expected behaviour or a bug after DateTime breaking changes. |
Hey everyone, I want to do my best to help - Npgsql 6.0 indeed does some pretty serious breaking changes. In case you're not aware, you can set an AppContext compatibility switch to revert to the pre-6.0 behavior. I can see the issue which @3ldar is describing; Dapper sets the DbType on the parameter to the result of LookupDbType, which returns DbType.DateTime. PostgreSQL has two timestamp types: The thing is, in 6.0, we no longer allow UTC timestamps to be written to @mgravell @NickCraver is there a particular reason to set DbType rather than let the driver decide? I'm open to making a change on the Npgsql if absolutely necessary, but would like to understand if Dapper is doing the right thing etc. |
Hi @roji - I'm pretty open to changing it to not specify anything, if that is going to work better. Date time is kinda unique among the primitives in that we keep inventing new ways of handling it. If omitting a type entirely is going to work better for dates/times: that's fine! I can probably try that tomorrow if you like. |
For Npgsql it's definitely better - just hoping there's not some reason it was done this way etc. Basically the ADO.NET driver is in a better position to know what to write based on the CLR type than Dapper is... |
BTW another possibly problematic one is TimeSpan -> DbType.Time... PostgreSQL has an Type mapping is hard... especially date/times... |
- change type-map to allow nullable - change DateTime/TimeSpan to null - do not specify DbType for null - semi-breaking change: swap GetDbType to SetDbType, noting that it is marked [Obsolete] and internal-use-only - semi-breaking change: make LookupDbType return nullable (same internal-use/[Obsolete])
spike started, @roji - should be on MyGet soon, assuming no explosions; what would we need to repro the failure here, to prove the fix? |
huh; tell a lie - looks like we only build MyGet for merges, but: it built and tested cleanly, at least |
The following fails with Npgsql 6.0.0-rc.1: _ = conn.ExecuteScalar("SELECT @Now", new { Now = DateTime.UtcNow }); If you avoid setting DbType, Npgsql should internally infer the right PostgreSQL type and all should be well. ... and thanks for spending time on this!! |
building with that now: https://ci.appveyor.com/project/StackExchange/dapper/builds/41146676 |
well, the good news is that the new [FactPostgresql]
public void TestPostgresqlArrayParameters()
{
using (var conn = GetOpenNpgsqlConnection())
{
IDbTransaction transaction = conn.BeginTransaction();
conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);");
conn.Execute("insert into tcat(breed, name) values(:breed, :name) ", Cats);
var r = conn.Query<Cat>("select * from tcat where id=any(:catids)", new { catids = new[] { 1, 3, 5 } });
Assert.Equal(3, r.Count());
Assert.Equal(1, r.Count(c => c.Id == 1));
Assert.Equal(1, r.Count(c => c.Id == 3));
Assert.Equal(1, r.Count(c => c.Id == 5));
transaction.Rollback();
}
} |
Uhh... I don't know enough about what's going on internally to know... I'm assuming this change does just remove the setting of DbType, right? FWIW for arrays there's no correct DbType anyway... |
should be; checking to see whether I borked anything; never underestimate my ability to bork things - it is unrivaled (FWIW, the same code worked on npgsql v5) |
@mgravell can you try it with a |
I don't have npgsql locally, so I'm a little dependent on CI, but: |
@3ldar to confirm: you're saying that the tests shown all pass for your locally? arrays and lists? |
Well, In a way yes, I crated that table on my local populated it with data. And executed it the exact way in the test. |
@mgravell are you able to isolate the array case to the problematic SQL (and parameter config) being generated? If so I can maybe try to help from there... |
hypothetically ... just wondering; what would happen if yes, obviously nobody would do that because it is stupid, but... humour me... would that break? |
Uhh.... Yeah, that won't work... Since DbType is set, Npgsql will still attempt to resolve the PG type through that, and not by looking at the CLR type. And then we get an exception because we don't know that DbType... I could make Npgsql check for that and fall back to CLR type inference, but that seems like squishy behavior that ignores user error... How is not setting the DbType responsible for modifying the array behavior? |
it is a hack that we can fix; basically, when trying to resolve how to handle args, we call a My hypoethesis, then, is that we should change: if (dbType.HasValue) param.DbType = dbType.GetValueOrDefault(); to something similar which also excludes Trying now... |
Ah I see... FWIW Npgsql specifically doesn't support enumerables, only actual arrays and |
we could special-case it, but in reality: there is no valid scenario where we should be setting the |
I have a similar issue with testing Npgsql 6.0.0-rc.2 I have a column with a 'timestamptz' format and I want to add a C# 'DateTime' of Kind 'UTC' into this column. When I insert data to this table using dapper I get the following error message: As far as I understand, this is also related to the DbType. I think it is currently 'timestamp" for DateTime and Npgsql 6.0 rejects that now. I have inserts with "copy" without dapper that still work. So I will not create another issue for that. |
@KinNeko-De it sounds like you're running into the same issue already described here. I know @mgravell is working on a solution. |
* context: #1716 #1716 - change type-map to allow nullable - change DateTime/TimeSpan to null - do not specify DbType for null - semi-breaking change: swap GetDbType to SetDbType, noting that it is marked [Obsolete] and internal-use-only - semi-breaking change: make LookupDbType return nullable (same internal-use/[Obsolete]) * add test for Npgsql 6.0.0-rc.1 * try list parameters (like arrays) * never assign -1 (sentinel) as the .DbType * postgres tests
Thanks, everyone for the effort, happy to close this out. |
Build for this went to nuget yesterday, btw. |
@roji is this flipped? I just ran a migration on a |
@pdevito3 the interpretation of DbType changed for the final release, see npgsql/npgsql#4106 for more details; tl;dr: DbType.DateTime -> timestamp with time zone However, that isn't related to the migration change you're describing above (which I'm assuming is EF Core). EF Core doesn't care about DbType, and Dapper no longer sets DbType for DateTime. In the Npgsql EF Core provider, DateTime now maps to |
yeah, EF Core thanks. makes sense. sorry for polluting the dapper thread! |
* context: #1716 DapperLib/Dapper#1716 - change type-map to allow nullable - change DateTime/TimeSpan to null - do not specify DbType for null - semi-breaking change: swap GetDbType to SetDbType, noting that it is marked [Obsolete] and internal-use-only - semi-breaking change: make LookupDbType return nullable (same internal-use/[Obsolete]) * add test for Npgsql 6.0.0-rc.1 * try list parameters (like arrays) * never assign -1 (sentinel) as the .DbType * postgres tests
* context: #1716 DapperLib/Dapper#1716 - change type-map to allow nullable - change DateTime/TimeSpan to null - do not specify DbType for null - semi-breaking change: swap GetDbType to SetDbType, noting that it is marked [Obsolete] and internal-use-only - semi-breaking change: make LookupDbType return nullable (same internal-use/[Obsolete]) * add test for Npgsql 6.0.0-rc.1 * try list parameters (like arrays) * never assign -1 (sentinel) as the .DbType * postgres tests
We are using a library named
Hangfire.PostgreSql
and it usesDapper
under the hood. In that library there is a code block like below :DDL that generates the target table is like below :
If we change
timeout = DateTime.UtcNow - options.DistributedLockTimeout
into thistimeout = DateTimeOffset.UtcNow - options.DistributedLockTimeout
it works as expected.In 6.0.0-rc1.0 npgsql there is a breaking change that changes the target type of
DateTime
intotimestamp with time zone
.Related info
The library uses 2.0.78 version of dapper but I've managed the reproduce the same issue with 2.0.90 version.
The text was updated successfully, but these errors were encountered: