Npgsql EF 6:使用 ExecuteSqlInterpolatedAsync 时时间戳失败

Npgsql EF 6: Timestamp fails when using ExecuteSqlInterpolatedAsync

更新到 6.0.3 后,我在 ExecuteSqlInterpolatedAsync 中遇到错误。 好吧,我知道这是一个重大变化,但我查看了数据库中的所有表,所有列都是“没有时区的时间戳”。通过 model/DbSet 进行更新或插入是可以的。

考虑到这一点,看看这段代码:

        await Database.ExecuteSqlInterpolatedAsync($@"update nfservico set 
                chavenfse = {chave},
                lotenfse = {cLote},
                lotenfse_dh = {dhRecbto},
                lotenfse_sit = {sit},
                lotenfse_mot = {mot}
                where id = {id}");

dhRecbto 这是一个没有指定“种类”的 DateTime 参数。 运行 给出以下错误:

System.InvalidCastException: Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone', only UTC is supported. Note that it's not possible to mix DateTimes with different Kinds in an array/range. See the Npgsql.EnableLegacyTimestampBehavior AppContext switch to enable legacy behavior.
   at Npgsql.Internal.TypeHandlers.DateTimeHandlers.TimestampTzHandler.ValidateAndGetLength(DateTime value, NpgsqlParameter parameter)
   at Npgsql.Internal.TypeHandlers.DateTimeHandlers.TimestampTzHandler.ValidateObjectAndGetLength(Object value, NpgsqlLengthCache& lengthCache, NpgsqlParameter parameter)
   at Npgsql.NpgsqlParameter.ValidateAndGetLength()
   at Npgsql.NpgsqlParameterCollection.ValidateAndBind(ConnectorTypeMapper typeMapper)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken can...

我无法理解“带时区的时间戳”部分,正如我之前所说,该字段没有时区。

我不得不做一个丑陋的工作:

            var sql = new StringBuilder();
            sql.AppendLine("update nfservico set ");
            sql.AppendLine($"    chavenfse = '{chave}',");
            sql.AppendLine($"    lotenfse = '{cLote}',");
            sql.AppendLine($"    lotenfse_dh = '{dhRecbto:yyyy-MM-dd HH:mm:ss}',");
            sql.AppendLine($"    lotenfse_sit = {sit},");
            sql.AppendLine($"    lotenfse_mot = '{mot}'");
            sql.AppendLine($"    where id = {id}");
            await Database.ExecuteSqlRawAsync(sql.ToString());

有人遇到同样的问题吗?不,我不想使用“EnableLegacyTimestampBehavior”开关,想要正确地使用它(至少就像在 Npgsql 6 的发行说明中找到的那样)。

这是 EF Core 原始 SQL 支持的限制。 EF 类型映射 - 管理发送参数的 PG 类型(时间戳与时间戳) - 仅由参数的 CLR 类型 (DateTime) 确定,而不查看其内容(即种类)。而DateTime默认的EF映射是timestamptz,不是timestamp;因此错误。请注意,这与您的实际数据库列类型无关:这纯粹是一个 client-side 问题。

幸运的是,EF 的原始 SQL API 允许直接传入 DbParameter,允许您准确指定所需的 PostgreSQL 类型:

var p = new NpgsqlParameter { Value = new DateTime(2020, 1, 1, 12, 0, 0), NpgsqlDbType = NpgsqlDbType.Timestamp };
_ = ctx.Database.ExecuteSqlInterpolatedAsync($"SELECT {p}");

这是传递参数的安全、正确的方法,其中 CLR 类型 (DateTime) 具有默认的 PG 类型 (timestamptz),这不是您想要的(您需要时间戳)。您上面的解决方法可能在某些方面是错误的,并且可能会引入不需要的时区转换。