繁体   English   中英

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

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

我有一个共同的问题。

无法将 Kind=UTC 的 DateTime 写入 PostgreSQL 类型“没有时区的时间戳”

我想启用 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);
        }

但不起作用。 我仍然得到同样的错误。

我做错了什么。 为什么遗留行为不起作用?

一个 通过添加解决

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


或者,如果您根本没有 Startup 类,并且您的所有初始化都在带有主机构建器的 Program.cs 中,那么您的文件结尾可能如下所示:

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

要使用System.Linq.Dynamic查询数据库,我们还需要指定时间类型。
过滤器示例: $"User.BirthDate>={time.ToStringUtc()}"

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

同时,@istvan-kardkovacs 的答案https://stackoverflow.com/a/70142836/7149454适用。 基本上是添加一个 . SetKindUtc() to every = new DateTime()您正在创建.. 在执行任何其他代码之前,在填充数据库的后台托管服务中,上面的开关显然对我不起作用。

您必须为创建、插入、更新操作中的所有 DateTime 字段以及 Linq 查询中的 DateTime 比较设置 DateTimeKind。 我创建了一个小型扩展方法并添加到所有日期字段。

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);
    }
}

并进行单元测试以显示功能:

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);
    }    
}

我在我的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();
}

将注入此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));
        }
    }
}

从@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));
                }
            }
        }
    }

尼克已经回答了这个问题,我只是想为这个时区问题添加另一个解决方案。

您可以在使用此扩展程序编写之前转换所有日期时间,而不是启用该选项。 这就是我所做的。

创建这个扩展类:

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; }
}

并将该转换器添加到您的 DbContext 文件中:

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

这将导致您所有的 DateTime 和 DateTime? 对象在写入 Db 之前已转换为 Utc 类型的日期。

这将是我支持这个 PostgreSql Db 的一种方式,因为我需要支持一些数据库(Sql Server、PostgreSql 以及很快的 MySql)。 手动将每个日期时间值转换为 Utc 并不是一个好的解决方案。

我们的应用程序还没有对时区的要求,但是使用该扩展,我们可以轻松地在其中添加时区支持。

当我的控制器反序列化对象并且我尝试使用 EF 和 Npgsql.EntityFrameworkCore.PostgreSQL 插入/更新它时,我也发生了同样的事情。 我对所有日期都使用了 ToUniversalTime(),它对我有用。

我找到了答案。 不要将这些行添加到您的 dB 上下文中。 而是在 WFP 应用程序中添加到 MainWindow.xaml.cs,如下所示:

在公共 MainWindow 方法中的 InitializeComponent 语句之前添加“EnableLegacyTimestampBehavior”行。

您不需要“DisableDateTimeInfinityConversions”语句。

您的 DateTime 代码现在可以工作了。

你需要添加

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

就我而言,这是我犯的一个错误

InvitedOn = DateTime.Now

本来应该

InvitedOn = DateTime.UtcNow

它奏效了

对我来说,我已经用 dotnet ef migrations add 更新了项目

命令参考:

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

在它 postgre 创建迁移文件之后,将所有日期时间从“带时区”更改为“不带时区”

我希望它对你有用

注释:

  • 我已经从 net5.0 更新到 net6.0
  • PostgreSQL 6.0.X
  • 对数据库使用代码优先。

更新

另外或之后您需要检查 DateTime 的种类,如果不是 Utc,您可以使用 DateTime 的静态 SpecifyKind 方法强制它

在此处输入图像描述

问候

我在“没有时区的时间戳”列中尝试使用 Kind = UTC (我实际上正在使用DateTime.UtcNow )坚持 DateTime 时也偶然发现了这个错误。 IMO,该错误没有意义,因为 UTC 日期时间应该是

我的解决方法是切换到“带时区的时间戳”,因为:

  • 它似乎按预期工作:我得到 {UTC timestamp}+00
  • “EnableLegacyTimestampBehavior”听起来像是将来可能会被弃用的东西
  • 将所有 DateTime 转换为 UTC 类型,而优雅的声音听起来像是一种 hack,在某些我真的想要另一种类型的 DateTime 的情况下可能会适得其反

也许有点晚了,但对我来说,我刚刚创建了这个转换器

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));
    }

放置设置的好地方是 DB Context 的 static 构造函数。

在这种情况下,启动 class 保持清洁。
如果您有多个项目使用相同的数据库上下文,这也很有用。
例如:

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

我有一个类似的问题,为了解决这个问题,我在 C# 中使用了 DateTime.ToUniversalTime() 方法。

例如,

Date= DateTime.Now.ToUniversalTime();

或者,

DateTime dateTime = DateTime.Now.ToUniversalTime();

要获得更多详细信息,您可以浏览此站点https://www.geeksforgeeks.org/datetime-touniversaltime-method-in-c-sharp/

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM