簡體   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