简体   繁体   English

.NET6 和 DateTime 问题。 无法将 Kind=UTC 的 DateTime 写入 PostgreSQL 类型“没有时区的时间戳”

[英].NET6 and DateTime problem. Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone'

I have common problem.我有一个共同的问题。

Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone'无法将 Kind=UTC 的 DateTime 写入 PostgreSQL 类型“没有时区的时间戳”

And I want to enable Legacy Timestamp behavoour as is documented here: https://github.com/npgsql/doc/blob/main/conceptual/Npgsql/types/datetime.md/我想启用 Legacy Timestamp 行为,如下所述: https://github.com/npgsql/doc/blob/main/conceptual/Npgsql/types/datetime.md/

public MyDbContext(DbContextOptions<MyDbContext> contextOptions) : base(contextOptions)
        {
            AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
            AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
        }

But doesn't work.但不起作用。 I still get same error.我仍然得到同样的错误。

What I am doing wrong.我做错了什么。 Why legacy behaviour doesn't work?为什么遗留行为不起作用?

A .一个 Solved by adding通过添加解决

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
to the Startup Configure method.Startup Configure方法。


B . Or in case you have no Startup class at all and all your initialization is inside Program.cs with a host builder then your file ending might look like:或者,如果您根本没有 Startup 类,并且您的所有初始化都在带有主机构建器的 Program.cs 中,那么您的文件结尾可能如下所示:

... //adding services etc
var host = builder.Build();
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
... //your other scoped code
await host.RunAsync();

To query database using System.Linq.Dynamic we need to specify time kind too.要使用System.Linq.Dynamic查询数据库,我们还需要指定时间类型。
Filter example: $"User.BirthDate>={time.ToStringUtc()}"过滤器示例: $"User.BirthDate>={time.ToStringUtc()}"

public static string ToStringUtc(this DateTime time)
{
    return $"DateTime({time.Ticks}, DateTimeKind.Utc)";
}

At the same time the answer https://stackoverflow.com/a/70142836/7149454 by @istvan-kardkovacs applies.同时,@istvan-kardkovacs 的答案https://stackoverflow.com/a/70142836/7149454适用。 Basically to add an .基本上是添加一个 . SetKindUtc() to every = new DateTime() you are creating.. The switch above didn't obviously worked for me in a background hosted service that was populating database before any other code was executed. SetKindUtc() to every = new DateTime()您正在创建.. 在执行任何其他代码之前,在填充数据库的后台托管服务中,上面的开关显然对我不起作用。

You have to set the DateTimeKind for all DateTime fields in the create,insert,update operations and for the DateTime comparisons in the Linq queries.您必须为创建、插入、更新操作中的所有 DateTime 字段以及 Linq 查询中的 DateTime 比较设置 DateTimeKind。 I have created a small extension method and add to all date fields.我创建了一个小型扩展方法并添加到所有日期字段。

public static class DateTimeExtensions
{
    public static DateTime? SetKindUtc(this DateTime? dateTime)
    {
        if (dateTime.HasValue)
        {
            return dateTime.Value.SetKindUtc();
        }
        else
        {
            return null;
        }
    }
    public static DateTime SetKindUtc(this DateTime dateTime)
    {
        if (dateTime.Kind == DateTimeKind.Utc) { return dateTime; }
        return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
    }
}

And unit-tests to show functionality:并进行单元测试以显示功能:

using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MyNamespace;

[TestClass]
[ExcludeFromCodeCoverage]
public class DateTimeExtensionsTests
{
    [TestMethod]
    public void SetKindUtcNullInputTest()
    {
        DateTime? input = null;
        DateTime? result = input.SetKindUtc();
        Assert.IsNull(result);
    }

    [TestMethod]
    public void SetKindUtcNonNullRegularDateInputTest()
    {
        DateTime? input = DateTime.Now;
        DateTime? result = input.SetKindUtc();
        Assert.IsNotNull(result);
        /* below is the primary functionality.  if the input did not have a "Kind" set, it gets set to DateTimeKind.Utc */
        Assert.AreEqual(DateTimeKind.Utc, result.Value.Kind);
    }

    [TestMethod]
    public void SetKindUtcNonNullOffsetDateInputTest()
    {
        DateTime? input = DateTime.Now;
        DateTime withKindUtcInput = DateTime.SpecifyKind(input.Value, DateTimeKind.Utc);
        DateTime? result = withKindUtcInput.SetKindUtc();
        Assert.IsNotNull(result);
        /* Utc "in" remains "Utc" out */
        Assert.AreEqual(DateTimeKind.Utc, result.Value.Kind);
    }
    
    [TestMethod]
    public void UnspecifiedKindIsOverwrittenTest()
    {
        DateTime? input = DateTime.Now;
        DateTime withKindUtcInput = DateTime.SpecifyKind(input.Value, DateTimeKind.Unspecified);
        DateTime? result = withKindUtcInput.SetKindUtc();
        Assert.IsNotNull(result);
        /* note the behavior.  "DateTimeKind.Unspecified" with overwritten with DateTimeKind.Utc */
        Assert.AreEqual(DateTimeKind.Utc, result.Value.Kind);
    }
    
    [TestMethod]
    public void LocalKindIsOverwrittenTest()
    {
        DateTime? input = DateTime.Now;
        DateTime withKindUtcInput = DateTime.SpecifyKind(input.Value, DateTimeKind.Local);
        DateTime? result = withKindUtcInput.SetKindUtc();
        Assert.IsNotNull(result);
        /* note the behavior.  "DateTimeKind.Local" with overwritten with DateTimeKind.Utc */
        Assert.AreEqual(DateTimeKind.Utc, result.Value.Kind);
    }    
}

I added code to my DbContext to set this on all date properties on my models:我在我的DbContext中添加了代码,以便在我的模型上的所有日期属性上进行设置:

//dbcontext
public override int SaveChanges()
{
    _changeTrackerManager?.FixupEntities(this);
    return base.SaveChanges();
}

//don't forget the async method!
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
    _changeTrackerManager?.FixupEntities(this);
    return base.SaveChangesAsync();
}

This IChangeTrackerManager dependency will be injected, and then any time entities are saved it will call this method below which will fixup all the utc date time kinds.将注入此IChangeTrackerManager依赖项,然后在保存实体时,它将在下面调用此方法,该方法将修复所有 utc 日期时间类型。

public void FixupEntities(DbContext context)
{
    var dateProperties = context.Model.GetEntityTypes()
        .SelectMany(t => t.GetProperties())
        .Where(p => p.ClrType == typeof(DateTime))
        .Select(z => new
        {
            ParentName = z.DeclaringEntityType.Name,
            PropertyName = z.Name
        });

    var editedEntitiesInTheDbContextGraph = context.ChangeTracker.Entries()
        .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)
        .Select(x => x.Entity);

    foreach (var entity in editedEntitiesInTheDbContextGraph)
    {
        var entityFields = dateProperties.Where(d => d.ParentName == entity.GetType().FullName);

        foreach (var property in entityFields)
        {
            var prop = entity.GetType().GetProperty(property.PropertyName);

            if (prop == null)
                continue;

            var originalValue = prop.GetValue(entity) as DateTime?;
            if (originalValue == null)
                continue;

            prop.SetValue(entity, DateTime.SpecifyKind(originalValue.Value, DateTimeKind.Utc));
        }
    }
}

modifying https://stackoverflow.com/a/71179214/507421 from @DLeh slightly从@DLeh 稍微修改https://stackoverflow.com/a/71179214/507421

    private void ConvertDateTimesToUniversalTime()
    {
        var modifiedEntites = ChangeTracker.Entries<IHaveAggregateRootId>()
                .Where(e => (e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted)).ToList();
        foreach (var entry in modifiedEntites)
        {
            foreach (var prop in entry.Properties)
            {
                if (prop.Metadata.ClrType == typeof(DateTime))
                {
                    prop.Metadata.FieldInfo.SetValue(entry.Entity, DateTime.SpecifyKind((DateTime)prop.CurrentValue, DateTimeKind.Utc));
                }
                else if (prop.Metadata.ClrType == typeof(DateTime?) && prop.CurrentValue != null)
                {
                    prop.Metadata.FieldInfo.SetValue(entry.Entity, DateTime.SpecifyKind(((DateTime?)prop.CurrentValue).Value, DateTimeKind.Utc));
                }
            }
        }
    }

Nick has already answered this question, I just want to add another solution to this time zone problem.尼克已经回答了这个问题,我只是想为这个时区问题添加另一个解决方案。

Instead of enabling that option, you can just convert all datetime before being written using this extension.您可以在使用此扩展程序编写之前转换所有日期时间,而不是启用该选项。 This is what I did.这就是我所做的。

Create this extension class:创建这个扩展类:

public static class UtcDateAnnotation
{
    private const string IsUtcAnnotation = "IsUtc";
    private static readonly ValueConverter<DateTime, DateTime> UtcConverter = new ValueConverter<DateTime, DateTime>(convertTo => DateTime.SpecifyKind(convertTo, DateTimeKind.Utc), convertFrom => convertFrom);

    public static PropertyBuilder<TProperty> IsUtc<TProperty>(this PropertyBuilder<TProperty> builder, bool isUtc = true) => builder.HasAnnotation(IsUtcAnnotation, isUtc);

    public static bool IsUtc(this IMutableProperty property)
    {
        if (property != null && property.PropertyInfo != null)
        {
            var attribute = property.PropertyInfo.GetCustomAttribute<IsUtcAttribute>();
            if (attribute is not null && attribute.IsUtc)
            {
                return true;
            }

            return ((bool?)property.FindAnnotation(IsUtcAnnotation)?.Value) ?? true;
        }
        return true;
    }

    /// <summary>
    /// Make sure this is called after configuring all your entities.
    /// </summary>
    public static void ApplyUtcDateTimeConverter(this ModelBuilder builder)
    {
        foreach (var entityType in builder.Model.GetEntityTypes())
        {
            foreach (var property in entityType.GetProperties())
            {
                if (!property.IsUtc())
                {
                    continue;
                }

                if (property.ClrType == typeof(DateTime) ||
                    property.ClrType == typeof(DateTime?))
                {
                    property.SetValueConverter(UtcConverter);
                }
            }
        }
    }
}
public class IsUtcAttribute : Attribute
{
    public IsUtcAttribute(bool isUtc = true) => this.IsUtc = isUtc;
    public bool IsUtc { get; }
}

And add that converter in your DbContext file:并将该转换器添加到您的 DbContext 文件中:

protected override void OnModelCreating(ModelBuilder builder)
{
     builder.ApplyUtcDateTimeConverter();//Put before seed data and after model creation
}

This will result all your DateTime and DateTime?这将导致您所有的 DateTime 和 DateTime? object got converted to Utc kind of date before being written to Db.对象在写入 Db 之前已转换为 Utc 类型的日期。

This will be my one way ticket to support this PostgreSql Db, because I have a requirement to support some database (Sql Server, PostgreSql, and soon MySql).这将是我支持这个 PostgreSql Db 的一种方式,因为我需要支持一些数据库(Sql Server、PostgreSql 以及很快的 MySql)。 Manually convert every datetime value to Utc won't be a good solution.手动将每个日期时间值转换为 Utc 并不是一个好的解决方案。

Our application hasn't had requirement for time zone yet, but using that extension we could easily add time zone support in it.我们的应用程序还没有对时区的要求,但是使用该扩展,我们可以轻松地在其中添加时区支持。

Same thing happened to me when my Controller deserialize the object and I was trying to insert/update it with EF and Npgsql.EntityFrameworkCore.PostgreSQL.当我的控制器反序列化对象并且我尝试使用 EF 和 Npgsql.EntityFrameworkCore.PostgreSQL 插入/更新它时,我也发生了同样的事情。 I used ToUniversalTime() to all dates and it worked for me.我对所有日期都使用了 ToUniversalTime(),它对我有用。

I found the answer.我找到了答案。 Don't add the lines to your dB Context.不要将这些行添加到您的 dB 上下文中。 Instead in a WFP application add to MainWindow.xaml.cs as follows:而是在 WFP 应用程序中添加到 MainWindow.xaml.cs,如下所示:

add the line "EnableLegacyTimestampBehavior" before the InitializeComponent statement in the public MainWindow method.在公共 MainWindow 方法中的 InitializeComponent 语句之前添加“EnableLegacyTimestampBehavior”行。

You don't need the "DisableDateTimeInfinityConversions" statement.您不需要“DisableDateTimeInfinityConversions”语句。

Your code with DateTime will now work.您的 DateTime 代码现在可以工作了。

You need add你需要添加

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

In my case it was a mistake I made就我而言,这是我犯的一个错误

InvitedOn = DateTime.Now

should have been本来应该

InvitedOn = DateTime.UtcNow

and it worked它奏效了

For me, I have updated the proyect with dotnet ef migrations add对我来说,我已经用 dotnet ef migrations add 更新了项目

Command reference:命令参考:

dotnet ef migrations add <MigrationName> --context <YoutContextClassName> --startup-project <../Path to your startup proyect> --verbose

After it postgre have created the migration file changing all datetime from "with time zone" to "without time zone"在它 postgre 创建迁移文件之后,将所有日期时间从“带时区”更改为“不带时区”

I hope It was usefull for you我希望它对你有用

NOTA:注释:

  • I've Updated from net5.0 to net6.0我已经从 net5.0 更新到 net6.0
  • postgresql 6.0.X PostgreSQL 6.0.X
  • Using code-first to database.对数据库使用代码优先。

UPDATE :更新

Also or after that you need to check Kind of DateTime if not is Utc you can force It with static SpecifyKind method of DateTime另外或之后您需要检查 DateTime 的种类,如果不是 Utc,您可以使用 DateTime 的静态 SpecifyKind 方法强制它

在此处输入图像描述

Greetings问候

I have also stumbled across this error while trying to persist a DateTime with Kind = UTC (I am actually using DateTime.UtcNow ) in a "timestamp without time zone" column.我在“没有时区的时间戳”列中尝试使用 Kind = UTC (我实际上正在使用DateTime.UtcNow )坚持 DateTime 时也偶然发现了这个错误。 IMO, the error does not make sense, since UTC datetimes should be IMO,该错误没有意义,因为 UTC 日期时间应该是

My fix was to switch to "timestamp with time zone" because:我的解决方法是切换到“带时区的时间戳”,因为:

  • it seems to work as expected: I get the {UTC timestamp}+00它似乎按预期工作:我得到 {UTC timestamp}+00
  • "EnableLegacyTimestampBehavior" sounds like something that is likely to be deprecated in the future “EnableLegacyTimestampBehavior”听起来像是将来可能会被弃用的东西
  • Converting all DateTime's to UTC Kind while elegant sounds like a hack that might backfire in some scenarios where I really want some DateTime of another Kind将所有 DateTime 转换为 UTC 类型,而优雅的声音听起来像是一种 hack,在某些我真的想要另一种类型的 DateTime 的情况下可能会适得其反

Maybe a little bit late, but for me I just created this converter也许有点晚了,但对我来说,我刚刚创建了这个转换器

public class DateTimeToDateTimeUtc : ValueConverter<DateTime, DateTime>
{
    public DateTimeToDateTimeUtc() : base(c => DateTime.SpecifyKind(c, DateTimeKind.Utc), c => c)
    {

    }
}
 protected sealed override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder.Properties<DateTime>()
            .HaveConversion(typeof(DateTimeToDateTimeUtc));
    }

Good place to put the setting is the static constructor of the DB Context.放置设置的好地方是 DB Context 的 static 构造函数。

In this case startup class remains cleaner.在这种情况下,启动 class 保持清洁。
Also it's useful if you have several projects using the same DB Context.如果您有多个项目使用相同的数据库上下文,这也很有用。
Eg:例如:

public class MyContext : DbContext
{
    static MyContext()
    {
        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
    }
    
    // Other stuff of your context
}

I had a similar problem and to solve this, I used DateTime.ToUniversalTime() Method in C#.我有一个类似的问题,为了解决这个问题,我在 C# 中使用了 DateTime.ToUniversalTime() 方法。

For example,例如,

Date= DateTime.Now.ToUniversalTime();

Or,或者,

DateTime dateTime = DateTime.Now.ToUniversalTime();

To obtain more detail, you can browse this site https://www.geeksforgeeks.org/datetime-touniversaltime-method-in-c-sharp/要获得更多详细信息,您可以浏览此站点https://www.geeksforgeeks.org/datetime-touniversaltime-method-in-c-sharp/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 .net postgres EF 核心无法将 Kind=Local 的 DateTime 写入 PostgreSQL 类型“带时区的时间戳” - .net postgres EF core Cannot write DateTime with Kind=Local to PostgreSQL type 'timestamp with time zone' 怎么说日期时间 - EF Core 6.0 中没有时区的时间戳 - How to say Datetime - timestamp without time zone in EF Core 6.0 在特定时区创建 DateTime 然后转换为 utc - Create DateTime in specific time zone then convert to utc 将 UTC 日期时间转换为另一个时区 - Convert UTC DateTime to another Time Zone 如何设置 DateTime 值的时区(或种类)? - How to set a time zone (or a Kind) of a DateTime value? 如何将DateTimeKind.Unspecified类型的DateTime转换为C#(.NET)中的DateTime.Kind.Utc - How to convert DateTime of type DateTimeKind.Unspecified to DateTime.Kind.Utc in C# (.NET) 使用Nodatime将DateTime从特定时区转换为UTC? - Convert DateTime from specific time zone to UTC using Nodatime? C# 将带时区的字符串转换为日期时间 06:23 (UTC) - C# convert string with time zone to datetime 06:23 (UTC) JSON如何反序列化日期时间并将其从UTC转换为指定的时区? - How JSON deserialize datetime and convert from UTC to a specified time zone? 使用 PST/CEST/UTC/etc 形式的时区解析 DateTime - Parse DateTime with time zone of form PST/CEST/UTC/etc
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM