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

mbr*_*brc 87 c# npgsql .net-6.0

我有共同的问题。

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

我想启用旧版时间戳行为,如下所示: 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);
        }
Run Code Online (Sandbox Code Playgroud)

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

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

Nic*_*sky 134

A。通过添加解决

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


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

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

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

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

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

  • 当切换到 .net 6(从 core 3.1)时,我收到此错误,这是解决它的最简单方法 (6认同)
  • 使用 .net6 应用程序和 PostgreSQL 数据库遇到了这个问题。将选项 B 添加到我的启动中并修复它。 (2认同)

小智 29

您必须为创建、插入、更新操作中的所有日期时间字段以及 Linq 查询中的日期时间比较设置 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

和单元测试来显示功能:

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);
    }    
}
Run Code Online (Sandbox Code Playgroud)


Ant*_*yok 23

放置设置的好地方是数据库上下文的静态构造函数。

在这种情况下,启动类仍然更干净。
如果您有多个项目使用相同的数据库上下文,它也很有用。
例如:

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

2023 年 7 月更新 - 解决方案 #2

今天我遇到了一个问题,这个技巧在我的一个项目中不起作用。
经过研究,我找到了另一种方法来确保可以在任何其他代码运行之前设置该标志一次。

这是在ModuleInitializer属性的帮助下完成的:

注意:它需要C#9及更高版本 ( .NET 5+ )

只需将一个新文件添加到 DbContext 所在的项目中即可。
并放置下一个内容:

using System.Runtime.CompilerServices;

namespace Your.Project.Namespace;

public static class MyModuleInitializer
{
    [ModuleInitializer]
    public static void Initialize()
    {
        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
    }
}
Run Code Online (Sandbox Code Playgroud)


Ari*_*awa 15

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

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

创建这个扩展类:

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; }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

  • 有用!我认为这是针对遗留项目的最佳建议解决方案。 (2认同)

小智 12

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

InvitedOn = DateTime.Now
Run Code Online (Sandbox Code Playgroud)

本来应该

InvitedOn = DateTime.UtcNow
Run Code Online (Sandbox Code Playgroud)

它起作用了


DLe*_*Leh 6

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();
}
Run Code Online (Sandbox Code Playgroud)

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

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

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));
    }
Run Code Online (Sandbox Code Playgroud)


小智 -2

我找到了答案。不要将线路添加到您的 dB 上下文中。相反,在 WFP 应用程序中添加到 MainWindow.xaml.cs,如下所示:

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

您不需要“DisableDateTimeInfinityConversions”语句。

您的 DateTime 代码现在可以运行了。