![](/img/trans.png)
[英].net postgres EF core Cannot write DateTime with Kind=Local to PostgreSQL type 'timestamp with time zone'
[英].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 创建迁移文件之后,将所有日期时间从“带时区”更改为“不带时区”
我希望它对你有用
注释:
更新:
另外或之后您需要检查 DateTime 的种类,如果不是 Utc,您可以使用 DateTime 的静态 SpecifyKind 方法强制它
问候
我在“没有时区的时间戳”列中尝试使用 Kind = UTC (我实际上正在使用DateTime.UtcNow
)坚持 DateTime 时也偶然发现了这个错误。 IMO,该错误没有意义,因为 UTC 日期时间应该是
我的解决方法是切换到“带时区的时间戳”,因为:
也许有点晚了,但对我来说,我刚刚创建了这个转换器
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.