实体框架日期时间和 UTC

Posted

技术标签:

【中文标题】实体框架日期时间和 UTC【英文标题】:Entity Framework DateTime and UTC 【发布时间】:2011-06-06 14:52:42 【问题描述】:

是否可以让实体框架(我目前使用 CTP5 的代码优先方法)将所有 DateTime 值作为 UTC 存储在数据库中?

或者有没有办法在映射中指定它,例如在 last_login 列中:

modelBuilder.Entity<User>().Property(x => x.Id).HasColumnName("id");
modelBuilder.Entity<User>().Property(x => x.IsAdmin).HasColumnName("admin");
modelBuilder.Entity<User>().Property(x => x.IsEnabled).HasColumnName("enabled");
modelBuilder.Entity<User>().Property(x => x.PasswordHash).HasColumnName("password_hash");
modelBuilder.Entity<User>().Property(x => x.LastLogin).HasColumnName("last_login");

【问题讨论】:

【参考方案1】:

这是您可以考虑的一种方法:

首先,定义以下属性:

[AttributeUsage(AttributeTargets.Property)]
public class DateTimeKindAttribute : Attribute

    private readonly DateTimeKind _kind;

    public DateTimeKindAttribute(DateTimeKind kind)
    
        _kind = kind;
    

    public DateTimeKind Kind
    
        get  return _kind; 
    

    public static void Apply(object entity)
    
        if (entity == null)
            return;

        var properties = entity.GetType().GetProperties()
            .Where(x => x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?));

        foreach (var property in properties)
        
            var attr = property.GetCustomAttribute<DateTimeKindAttribute>();
            if (attr == null)
                continue;

            var dt = property.PropertyType == typeof(DateTime?)
                ? (DateTime?) property.GetValue(entity)
                : (DateTime) property.GetValue(entity);

            if (dt == null)
                continue;

            property.SetValue(entity, DateTime.SpecifyKind(dt.Value, attr.Kind));
        
    

现在将该属性与您的 EF 上下文挂钩:

public class MyContext : DbContext

    public DbSet<Foo> Foos  get; set; 

    public MyContext()
    
        ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
            (sender, e) => DateTimeKindAttribute.Apply(e.Entity);
    

现在在任何DateTimeDateTime? 属性上,您都可以应用此属性:

public class Foo

    public int Id  get; set; 

    [DateTimeKind(DateTimeKind.Utc)]
    public DateTime Bar  get; set; 

有了这个,每当实体框架从数据库加载一个实体时,它都会设置你指定的DateTimeKind,比如UTC。

请注意,保存时这不会做任何事情。在尝试保存之前,您仍然必须将值正确转换为 UTC。但它确实允许您在检索时设置种类,从而可以将其序列化为 UTC,或使用TimeZoneInfo 转换为其他时区。

【讨论】:

如果你不能让它工作,你可能错过了以下使用之一: using System;使用 System.Collections.Generic;使用 System.ComponentModel.DataAnnotations.Schema;使用 System.Linq;使用 System.Reflection; @Saustrup - 您可以在 S.O. 上找到大多数示例。为简洁起见将省略使用,除非它们与问题直接相关。不过谢谢。 @MattJohnson 没有 @Saustrup 的 using 语句,你会得到一些无用的编译错误,例如 'System.Array' does not contain a definition for 'Where' 正如@SilverSideDown 所说,这只适用于.NET 4.5。我创建了一些扩展以使其与gist.github.com/munr/3544bd7fab6615290561 的.NET 4.0 兼容。另一件需要注意的是,这不适用于投影,仅适用于完全加载的实体。 有什么建议可以通过投影进行吗?【参考方案2】:

对于EF Core,GitHub上有关于这个话题的精彩讨论:https://github.com/dotnet/efcore/issues/4711

一种解决方案(归功于Christopher Haws)将导致在将所有日期存储到数据库/从数据库中检索它们时将它们视为UTC,将以下内容添加到DbContext 类的OnModelCreating 方法中:

var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
    v => v.ToUniversalTime(),
    v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

var nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
    v => v.HasValue ? v.Value.ToUniversalTime() : v,
    v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v);

foreach (var entityType in builder.Model.GetEntityTypes())

    if (entityType.IsKeyless)
    
        continue;
    

    foreach (var property in entityType.GetProperties())
    
        if (property.ClrType == typeof(DateTime))
        
            property.SetValueConverter(dateTimeConverter);
        
        else if (property.ClrType == typeof(DateTime?))
        
            property.SetValueConverter(nullableDateTimeConverter);
        
    

另外,check this link 如果您想排除某些实体的某些属性不被视为 UTC。

【讨论】:

对我来说绝对是最好的解决方案!谢谢 @MarkRedman 我认为这没有意义,因为如果您有合法的 DateTimeOffset 用例,您还想保留有关时区的信息。请参阅 docs.microsoft.com/en-us/dotnet/standard/datetime/… 或 ***.com/a/14268167/3979621 了解何时在 DateTime 和 DateTimeOffset 之间进行选择。 IsQueryType 似乎已被IsKeyLess 取代:github.com/dotnet/efcore/commit/… 为什么需要IsQueryType(或IsKeyLess现在)检查? 实际上可能不需要。根据 EF 核心的特定配置,如果他们决定不省略无密钥实体类型,可能会或可能不会遇到问题。【参考方案3】:

我真的很喜欢 Matt Johnson 的方法,但在我的模型中,我的所有 DateTime 成员都是 UTC,我不想用属性来装饰所有成员。所以我概括了 Matt 的方法,允许事件处理程序应用默认的 Kind 值,除非成员显式地用属性修饰。

ApplicationDbContext 类的构造函数包含以下代码:

/// <summary> Constructor: Initializes a new ApplicationDbContext instance. </summary>
public ApplicationDbContext()
        : base(MyApp.ConnectionString, throwIfV1Schema: false)

    // Set the Kind property on DateTime variables retrieved from the database
    ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
      (sender, e) => DateTimeKindAttribute.Apply(e.Entity, DateTimeKind.Utc);

DateTimeKindAttribute 看起来像这样:

/// <summary> Sets the DateTime.Kind value on DateTime and DateTime? members retrieved by Entity Framework. Sets Kind to DateTimeKind.Utc by default. </summary>
[AttributeUsage(AttributeTargets.Property)]
public class DateTimeKindAttribute : Attribute

    /// <summary> The DateTime.Kind value to set into the returned value. </summary>
    public readonly DateTimeKind Kind;

    /// <summary> Specifies the DateTime.Kind value to set on the returned DateTime value. </summary>
    /// <param name="kind"> The DateTime.Kind value to set on the returned DateTime value. </param>
    public DateTimeKindAttribute(DateTimeKind kind)
    
        Kind = kind;
    

    /// <summary> Event handler to connect to the ObjectContext.ObjectMaterialized event. </summary>
    /// <param name="entity"> The entity (POCO class) being materialized. </param>
    /// <param name="defaultKind"> [Optional] The Kind property to set on all DateTime objects by default. </param>
    public static void Apply(object entity, DateTimeKind? defaultKind = null)
    
        if (entity == null) return;

        // Get the PropertyInfos for all of the DateTime and DateTime? properties on the entity
        var properties = entity.GetType().GetProperties()
            .Where(x => x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?));

        // For each DateTime or DateTime? property on the entity...
        foreach (var propInfo in properties) 
            // Initialization
            var kind = defaultKind;

            // Get the kind value from the [DateTimekind] attribute if it's present
            var kindAttr = propInfo.GetCustomAttribute<DateTimeKindAttribute>();
            if (kindAttr != null) kind = kindAttr.Kind;

            // Set the Kind property
            if (kind != null) 
                var dt = (propInfo.PropertyType == typeof(DateTime?))
                    ? (DateTime?)propInfo.GetValue(entity)
                    : (DateTime)propInfo.GetValue(entity);

                if (dt != null) propInfo.SetValue(entity, DateTime.SpecifyKind(dt.Value, kind.Value));
            
        
    

【讨论】:

这是对已接受答案的非常有用的扩展! 也许我遗漏了什么,但这如何默认为 DateTimeKind.Utc 而不是 DateTimeKind.Unspecified? @Rhonage 对此感到抱歉。默认设置在 ApplicationDbContext 构造函数中。我更新了答案以包含它。 @Bob.at.AIPsychLab 谢谢老兄,现在清楚多了。试图弄清楚是否有一些重量反射正在进行 - 但不,非常简单! 如果模型具有 DateTIme 属性而没有(公共)setter 方法,则会失败。建议编辑。另见***.com/a/3762475/2279059【参考方案4】:

此答案适用于 Entity Framework 6

接受的答案不适用于投影或匿名对象。性能也可能是个问题。

为此,我们需要使用DbCommandInterceptor,这是EntityFramework 提供的对象。

创建拦截器:

public class UtcInterceptor : DbCommandInterceptor

    public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    
        base.ReaderExecuted(command, interceptionContext);

        if (interceptionContext?.Result != null && !(interceptionContext.Result is UtcDbDataReader))
        
            interceptionContext.Result = new UtcDbDataReader(interceptionContext.Result);
        
    

interceptionContext.Result 是 DbDataReader,我们将其替换为我们的

public class UtcDbDataReader : DbDataReader

    private readonly DbDataReader source;

    public UtcDbDataReader(DbDataReader source)
    
        this.source = source;
    

    public override DateTime GetDateTime(int ordinal)
    
        return DateTime.SpecifyKind(source.GetDateTime(ordinal), DateTimeKind.Utc);
            

    // you need to fill all overrides. Just call the same method on source in all cases

    public new void Dispose()
    
        source.Dispose();
    

    public new IDataReader GetData(int ordinal)
    
        return source.GetData(ordinal);
    

在你的DbConfiguration注册拦截器

internal class MyDbConfiguration : DbConfiguration

    protected internal MyDbConfiguration ()
               
        AddInterceptor(new UtcInterceptor());
    

最后,在你的DbContext上注册配置

[DbConfigurationType(typeof(MyDbConfiguration ))]
internal class MyDbContext : DbContext

    // ...

就是这样。干杯。

为简单起见,这里是 DbReader 的完整实现:​​

using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace MyNameSpace

    /// <inheritdoc />
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010:CollectionsShouldImplementGenericInterface")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    public class UtcDbDataReader : DbDataReader
    
        private readonly DbDataReader source;

        public UtcDbDataReader(DbDataReader source)
        
            this.source = source;
        

        /// <inheritdoc />
        public override int VisibleFieldCount => source.VisibleFieldCount;

        /// <inheritdoc />
        public override int Depth => source.Depth;

        /// <inheritdoc />
        public override int FieldCount => source.FieldCount;

        /// <inheritdoc />
        public override bool HasRows => source.HasRows;

        /// <inheritdoc />
        public override bool IsClosed => source.IsClosed;

        /// <inheritdoc />
        public override int RecordsAffected => source.RecordsAffected;

        /// <inheritdoc />
        public override object this[string name] => source[name];

        /// <inheritdoc />
        public override object this[int ordinal] => source[ordinal];

        /// <inheritdoc />
        public override bool GetBoolean(int ordinal)
        
            return source.GetBoolean(ordinal);
        

        /// <inheritdoc />
        public override byte GetByte(int ordinal)
        
            return source.GetByte(ordinal);
        

        /// <inheritdoc />
        public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
        
            return source.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
        

        /// <inheritdoc />
        public override char GetChar(int ordinal)
        
            return source.GetChar(ordinal);
        

        /// <inheritdoc />
        public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
        
            return source.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
        

        /// <inheritdoc />
        public override string GetDataTypeName(int ordinal)
        
            return source.GetDataTypeName(ordinal);
        

        /// <summary>
        /// Returns datetime with Utc kind
        /// </summary>
        public override DateTime GetDateTime(int ordinal)
        
            return DateTime.SpecifyKind(source.GetDateTime(ordinal), DateTimeKind.Utc);
        

        /// <inheritdoc />
        public override decimal GetDecimal(int ordinal)
        
            return source.GetDecimal(ordinal);
        

        /// <inheritdoc />
        public override double GetDouble(int ordinal)
        
            return source.GetDouble(ordinal);
        

        /// <inheritdoc />
        public override IEnumerator GetEnumerator()
        
            return source.GetEnumerator();
        

        /// <inheritdoc />
        public override Type GetFieldType(int ordinal)
        
            return source.GetFieldType(ordinal);
        

        /// <inheritdoc />
        public override float GetFloat(int ordinal)
        
            return source.GetFloat(ordinal);
        

        /// <inheritdoc />
        public override Guid GetGuid(int ordinal)
        
            return source.GetGuid(ordinal);
        

        /// <inheritdoc />
        public override short GetInt16(int ordinal)
        
            return source.GetInt16(ordinal);
        

        /// <inheritdoc />
        public override int GetInt32(int ordinal)
        
            return source.GetInt32(ordinal);
        

        /// <inheritdoc />
        public override long GetInt64(int ordinal)
        
            return source.GetInt64(ordinal);
        

        /// <inheritdoc />
        public override string GetName(int ordinal)
        
            return source.GetName(ordinal);
        

        /// <inheritdoc />
        public override int GetOrdinal(string name)
        
            return source.GetOrdinal(name);
        

        /// <inheritdoc />
        public override string GetString(int ordinal)
        
            return source.GetString(ordinal);
        

        /// <inheritdoc />
        public override object GetValue(int ordinal)
        
            return source.GetValue(ordinal);
        

        /// <inheritdoc />
        public override int GetValues(object[] values)
        
            return source.GetValues(values);
        

        /// <inheritdoc />
        public override bool IsDBNull(int ordinal)
        
            return source.IsDBNull(ordinal);
        

        /// <inheritdoc />
        public override bool NextResult()
        
            return source.NextResult();
        

        /// <inheritdoc />
        public override bool Read()
        
            return source.Read();
        

        /// <inheritdoc />
        public override void Close()
        
            source.Close();
        

        /// <inheritdoc />
        public override T GetFieldValue<T>(int ordinal)
        
            return source.GetFieldValue<T>(ordinal);
        

        /// <inheritdoc />
        public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken)
        
            return source.GetFieldValueAsync<T>(ordinal, cancellationToken);
        

        /// <inheritdoc />
        public override Type GetProviderSpecificFieldType(int ordinal)
        
            return source.GetProviderSpecificFieldType(ordinal);
        

        /// <inheritdoc />
        public override object GetProviderSpecificValue(int ordinal)
        
            return source.GetProviderSpecificValue(ordinal);
        

        /// <inheritdoc />
        public override int GetProviderSpecificValues(object[] values)
        
            return source.GetProviderSpecificValues(values);
        

        /// <inheritdoc />
        public override DataTable GetSchemaTable()
        
            return source.GetSchemaTable();
        

        /// <inheritdoc />
        public override Stream GetStream(int ordinal)
        
            return source.GetStream(ordinal);
        

        /// <inheritdoc />
        public override TextReader GetTextReader(int ordinal)
        
            return source.GetTextReader(ordinal);
        

        /// <inheritdoc />
        public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken)
        
            return source.IsDBNullAsync(ordinal, cancellationToken);
        

        /// <inheritdoc />
        public override Task<bool> ReadAsync(CancellationToken cancellationToken)
        
            return source.ReadAsync(cancellationToken);
        

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
        public new void Dispose()
        
            source.Dispose();
        

        public new IDataReader GetData(int ordinal)
        
            return source.GetData(ordinal);
        
    

【讨论】:

到目前为止,这似乎是最好的答案。我首先尝试了属性变化,因为它似乎影响不大,但是我的单元测试会因模拟而失败,因为构造函数事件绑定似乎不知道 OnModelCreating 事件中发生的表映射。这个得到我的投票! 你为什么要关注DisposeGetData 这段代码应该归功于@IvanStoev:***.com/a/40349051/90287 很遗憾,如果您正在映射空间数据,则会失败 @user247702 是的阴影 Dispose 是错误的,覆盖 Dispose(bool)【参考方案5】:

我相信我找到了一个不需要任何自定义 UTC 检查或 DateTime 操作的解决方案。

基本上,您需要更改 EF 实体以使用 DateTimeOffset (NOT DateTime) 数据类型。这会将时区和日期值存储在数据库中(在我的例子中是 SQL Server 2015)。

当 EF Core 从数据库请求数据时,它也会收到时区信息。 当您将此数据传递给 Web 应用程序(在我的情况下为 Angular2)时,日期会自动转换为浏览器的本地时区,这正是我所期望的。

当它被传回我的服务器时,它会再次自动转换为 UTC,也如预期的那样。

【讨论】:

DateTimeOffset 不存储时区,这与通常的看法相反。它存储该值表示的与 UTC 的偏移量。偏移量不能反向映射以确定创建偏移量的实际时区,从而使数据类型几乎无用。 否,但它可以用来正确存储日期时间:medium.com/@ojb500/in-praise-of-datetimeoffset-e0711f991cba 只有 UTC 不需要位置,因为它无处不在。如果您使用 UTC 以外的其他东西,您还需要位置,否则时间信息是无用的,在使用 datetimeoffset 时也是如此。 DATETIMEOFFSET 将执行原始发布者想要的操作:将日期时间存储为 UTC,而无需执行任何(显式)转换。 @Carl DATETIME、DATETIME2 和 DATETIMEOFFSET 都正确存储日期时间值。除了额外存储与 UTC 的偏移量之外,DATETIMEOFFSET 几乎没有任何优势。您在数据库中使用的是您的电话。我只是想强调一点,它不存储时区,正如许多人错误地认为的那样。 @Suncat2000 优点是您可以按原样将此日期从您的 api 发送到客户端浏览器。当客户端浏览器打开此日期时,它知道 UCT 的偏移量,因此能够将其转换为客户端正在查看它的系统上的默认日期。因此,从您的服务器时区到浏览器时区的转换无需开发人员为其编写任何代码。【参考方案6】:

又是一年,又是一个解决方案!这是 EF Core 的。

我有很多 DATETIME2(7) 列映射到 DateTime,并且始终存储 UTC。我不想存储偏移量,因为如果我的代码正确,那么偏移量将始终为零。

同时,我还有其他列存储未知偏移量的基本日期时间值(由用户提供),因此它们只是“按原样”存储/显示,而不是与任何东西进行比较。

因此,我需要一个可以应用于特定列的解决方案。

定义一个扩展方法UsesUtc:

private static DateTime FromCodeToData(DateTime fromCode, string name)
    => fromCode.Kind == DateTimeKind.Utc ? fromCode : throw new InvalidOperationException($"Column name only accepts UTC date-time values");

private static DateTime FromDataToCode(DateTime fromData) 
    => fromData.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(fromData, DateTimeKind.Utc) : fromData.ToUniversalTime();

public static PropertyBuilder<DateTime?> UsesUtc(this PropertyBuilder<DateTime?> property)

    var name = property.Metadata.Name;
    return property.HasConversion<DateTime?>(
        fromCode => fromCode != null ? FromCodeToData(fromCode.Value, name) : default,
        fromData => fromData != null ? FromDataToCode(fromData.Value) : default
    );


public static PropertyBuilder<DateTime> UsesUtc(this PropertyBuilder<DateTime> property)

    var name = property.Metadata.Name;
    return property.HasConversion(fromCode => FromCodeToData(fromCode, name), fromData => FromDataToCode(fromData));

这可以用于模型设置中的属性:

modelBuilder.Entity<CustomerProcessingJob>().Property(x => x.Started).UsesUtc();

与属性相比,它有一个较小的优势,即您只能将其应用于正确类型的属性。

请注意,它假定数据库中的值采用 UTC 格式,但 Kind 是错误的。因此,它会监管您尝试存储在数据库中的值,如果它们不是 UTC,则会引发描述性异常。

【讨论】:

这是一个很好的解决方案,应该更高,尤其是现在大多数新开发都将使用 Core 或 .NET 5。UTC 执行政策的额外想象点 - 如果更多人保持他们的日期 UTC 全部实际用户显示的方式,我们几乎没有任何日期/时间错误。 我喜欢解决方案的简洁性,但在“定义扩展方法”步骤中迷失了......究竟在哪里/如何?【参考方案7】:

我现在正在研究这个问题,这些答案中的大多数都不是很好。据我所知,没有办法告诉 EF6 从数据库中出来的日期是 UTC 格式的。如果是这种情况,确保模型的 DateTime 属性采用 UTC 的最简单方法是在 setter 中进行验证和转换。

这里有一些描述算法的类似 c# 的伪代码

public DateTime MyUtcDateTime 
    
    get 
            
        return _myUtcDateTime;        
    
    set
       
        if(value.Kind == DateTimeKind.Utc)      
            _myUtcDateTime = value;            
        else if (value.Kind == DateTimeKind.Local)         
            _myUtcDateTime = value.ToUniversalTime();
        else 
            _myUtcDateTime = DateTime.SpecifyKind(value, DateTimeKind.Utc);        
        

前两个分支很明显。最后一个是秘制酱汁。

当 EF6 从从数据库加载的数据创建模型时,DateTimes 为 DateTimeKind.Unspecified。如果您知道数据库中的日期都是 UTC,那么最后一个分支将非常适合您。

DateTime.Now 始终为DateTimeKind.Local,因此上述算法适用于代码中生成的日期。大多数时候。

但是,您必须小心,因为DateTimeKind.Unspecified 可以通过其他方式潜入您的代码。例如,您可以从 JSON 数据反序列化您的模型,并且您的反序列化程序默认为这种类型。由您负责防止标记为 DateTimeKind.Unspecified 的本地化日期从除 EF 之外的任何人那里获得该设置器。

【讨论】:

我在与这个问题多年的斗争后发现,如果您将 DateTime 字段分配或选择到其他结构中,例如数据传输对象,EF 会忽略 getter 和 setter 方法。在这些情况下,您仍然需要在生成结果后将 Kind 更改为 DateTimeKind.Utc。示例:from o in myContext.Records select new DTO() BrokenTimestamp = o.BbTimestamp ; 将所有 Kind 设置为 DateTimeKind.Unspecified 我已经将 DateTimeOffset 与实体框架一起使用了一段时间,如果您使用 DateTimeOffset 数据类型指定您的 EF 实体,那么您的所有 EF 查询将返回与 UTC 有偏移的日期,确切地说就像它保存在数据库中一样。因此,如果您将数据类型更改为 DateTimeOffset 而不是 DateTime,则不需要上述解决方法。 很高兴知道!谢谢@Moutono 根据@Suncat2000 的评论,这根本行不通,应该删除【参考方案8】:

无法在实体框架中指定 DataTimeKind。您可能决定在存储到 db 之前将日期时间值转换为 utc,并始终假定从 db 检索的数据为 UTC。但是在查询期间具体化的 DateTime 对象将始终是“未指定”。您还可以使用 DateTimeOffset 对象而不是 DateTime 进行评估。

【讨论】:

【参考方案9】:

如果您在设置值时小心地正确传递 UTC 日期,并且您所关心的只是确保在从数据库中检索实体时正确设置 DateTimeKind,请在此处查看我的答案:https://***.com/a/9386364/279590

【讨论】:

【参考方案10】:

感谢@ajcvickers。从 EF Core 2.1 开始,这将是处理 DateTime.Kind 的一种方式:

modelBuilder
    .Entity<Foo>()
    .Property(e => e.SomeDate)
    .HasConversion(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

这将确保每次从数据库中读取日期时都会自动将其指定为 Utc。

来源:

https://github.com/dotnet/efcore/issues/4711#issuecomment-358695190

【讨论】:

【参考方案11】:

对于像我一样需要使用.net framework 4 实现@MattJohnson 解决方案,反射语法/方法限制的人,需要进行一些修改,如下所示:

     foreach (var property in properties)
             

            DateTimeKindAttribute attr  = (DateTimeKindAttribute) Attribute.GetCustomAttribute(property, typeof(DateTimeKindAttribute));

            if (attr == null)
                continue;

            var dt = property.PropertyType == typeof(DateTime?)
                ? (DateTime?)property.GetValue(entity,null)
                : (DateTime)property.GetValue(entity, null);

            if (dt == null)
                continue;

            //If the value is not null set the appropriate DateTimeKind;
            property.SetValue(entity, DateTime.SpecifyKind(dt.Value, attr.Kind) ,null);
          

【讨论】:

【参考方案12】:

Matt Johnson-Pint 的解决方案有效,但如果您的所有 DateTimes 都应该是 UTC,那么创建属性将过于迂回。以下是我的简化方法:

public class MyContext : DbContext

    public DbSet<Foo> Foos  get; set; 

    public MyContext()
    
        ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
            (sender, e) => SetDateTimesToUtc(e.Entity);
    

    private static void SetDateTimesToUtc(object entity)
    
        if (entity == null)
        
            return;
        

        var properties = entity.GetType().GetProperties();
        foreach (var property in properties)
        
            if (property.PropertyType == typeof(DateTime))
            
                property.SetValue(entity, DateTime.SpecifyKind((DateTime)property.GetValue(entity), DateTimeKind.Utc));
            
            else if (property.PropertyType == typeof(DateTime?))
            
                var value = (DateTime?)property.GetValue(entity);
                if (value.HasValue)
                
                    property.SetValue(entity, DateTime.SpecifyKind(value.Value, DateTimeKind.Utc));
                
            
        
    

【讨论】:

【参考方案13】:

为了在 Entity Framework Core 中为实体属性定义 DateTimeKind,我建议使用以下类:

/// <summary>
/// Класс для преобразования DateTimeKind у сущностей в EF
/// </summary>
public static class DateTimeKindAnnotation

    private const string DateTimeKindAnnotation = "DateTimeKind";

    private static readonly ValueConverter<DateTime, DateTime> UtcConverter =
        new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

    private static readonly ValueConverter<DateTime, DateTime> LocalConverter =
        new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Local));

    private static readonly ValueConverter<DateTime, DateTime> UnspecifiedConverter =
        new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Unspecified));

    /// <summary>
    /// Данное свойство будет иметь заданный DateTimeKind в EF
    /// </summary>
    /// <typeparam name="TProperty"></typeparam>
    /// <param name="builder"></param>
    /// <param name="kind"></param>
    public static PropertyBuilder<DateTime> HasDateTimeKind(this PropertyBuilder<DateTime> builder, DateTimeKind kind) =>
        builder.HasAnnotation(DateTimeKindAnnotation, kind);

    /// <summary>
    /// Данное свойство будет иметь заданный DateTimeKind в EF
    /// </summary>
    /// <typeparam name="TProperty"></typeparam>
    /// <param name="builder"></param>
    /// <param name="kind"></param>
    public static PropertyBuilder<DateTime?> HasDateTimeKind(this PropertyBuilder<DateTime?> builder, DateTimeKind kind) =>
        builder.HasAnnotation(DateTimeKindAnnotation, kind);

    public static DateTimeKind? FindDateTimeKind(this IMutableProperty property)
    
        var attribute = property.PropertyInfo.GetCustomAttribute<DateTimeKindAttribute>();
        if (attribute is not null)
        
            return attribute.Kind;
        
        return (DateTimeKind?)property.FindAnnotation(DateTimeKindAnnotation)?.Value;
    

    /// <summary>
    /// Преобразует DateTimeKind у всех сущностей в EF к значению по умолчанию, заданному через атрибут или анотацию.
    /// </summary>
    /// <remarks>Убедитесь, что это вызывается после настройки всех ваших сущностей.</remarks>
    /// <param name="builder"></param>
    /// <param name="defaultKind">DateTimeKind, который надо использовать по умолчанию.</param>
    public static void ApplyDateTimeKindConverter(this ModelBuilder builder, DateTimeKind defaultKind = DateTimeKind.Utc)
    
        foreach (var entityType in builder.Model.GetEntityTypes())
        
            foreach (var property in entityType.GetProperties())
            
                if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?))
                
                    DateTimeKind kind = property.FindDateTimeKind() ?? defaultKind;
                    switch (kind)
                    
                        case DateTimeKind.Utc:
                            property.SetValueConverter(UtcConverter);
                            break;
                        case DateTimeKind.Local:
                            property.SetValueConverter(LocalConverter);
                            break;
                        case DateTimeKind.Unspecified:
                            property.SetValueConverter(UnspecifiedConverter);
                            break;
                        default:
                            throw new NotSupportedException($"Kind \"kind\" неподдерживается");
                    
                
            
        
    


/// <summary>
/// Задает тот DateTimeKind, который будет применяться EF для поля сущности.
/// </summary>
public class DateTimeKindAttribute : Attribute

    public DateTimeKindAttribute(DateTimeKind kind) => Kind = kind;
    public DateTimeKind Kind  get; 

对于其最简单的应用程序,您需要在 OnModelCreating 的末尾添加对 ApplyDateTimeKindConverter() 的调用。默认情况下,所有日期时间字段都分配有单一种类的 Utc。异常可以通过DateTimeKindAttribute属性或者fluentapi方法HasDateTimeKind(DateTimeKind.)实现

【讨论】:

【参考方案14】:

另一种方法是使用日期时间属性创建一个接口,在部分实体类上实现它们。然后使用 SavingChanges 事件检查对象是否属于接口类型,将这些日期时间值设置为您想要的任何值。事实上,如果这些是在某种日期创建/修改的,您可以使用该事件来填充它们。

【讨论】:

不错,但这些类不会用于匿名选择。【参考方案15】:

就我而言,我只有一张带有 UTC 日期时间的表。这是我所做的:

public partial class MyEntity

    protected override void OnPropertyChanged(string property)
    
        base.OnPropertyChanged(property);            

        // ensure that values coming from database are set as UTC
        // watch out for property name changes!
        switch (property)
        
            case "TransferDeadlineUTC":
                if (TransferDeadlineUTC.Kind == DateTimeKind.Unspecified)
                    TransferDeadlineUTC = DateTime.SpecifyKind(TransferDeadlineUTC, DateTimeKind.Utc);
                break;
            case "ProcessingDeadlineUTC":
                if (ProcessingDeadlineUTC.Kind == DateTimeKind.Unspecified)
                    ProcessingDeadlineUTC = DateTime.SpecifyKind(ProcessingDeadlineUTC, DateTimeKind.Utc);
            default:
                break;
        
    

【讨论】:

【参考方案16】:

这里的解决方案很有用,但我预计很多人会遇到这样一个问题,即他们希望所有的日期时间都在本地时区可用,但他们希望将其翻译,以便将持久版本保存为 UTC。

实现这一点有 3 个挑战:

    以 UTC 格式读取数据并转换为本地 调整查询参数,例如SELECT * From PRODUCT where SALEDATE 将 LocalTime 的数据存储为 UTC

1.以 UTC 格式读取数据并转换为本地

在这种情况下,上述基于 Ivan Stoev DateTime.Kind set to unspecified, not UTC, upon loading from database 工作的解决方案将满足您的需求。

2。调整查询参数

与 Ivan 的拦截器解决方案类似,您可以使用 ReaderExecuting 拦截器。好处是这比 ReaderExecuted 更容易实现。

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    
        foreach (DbParameter dbParameter in command.Parameters)
        
            if (dbParameter.Value is DateTime dtLocal)
            
                if (dtLocal.Kind != DateTimeKind.Utc)
                
                    dbParameter.Value = dtLocal.ToUniversalTime();
                
            
        
        base.ReaderExecuting(command, interceptionContext);
    

3.将 LocalTime 的数据存储为 UTC

虽然有些查询拦截器看起来可以在这里提供帮助,但它们被多次调用并产生了意想不到的结果。我想出的最佳解决方案是覆盖 SaveChanges

    public override int SaveChanges()
    
        UpdateCommonProperties();
        UpdateDatesToUtc();
        bool saveFailed;
        do
        
            saveFailed = false;
            try
            
                var result = base.SaveChanges();
                return result;
            
            catch (DbUpdateConcurrencyException ex)
            
                saveFailed = ConcurrencyExceptionHandler(ex);
            

         while (saveFailed);
        return 0;
    

    private void UpdateDatesToUtc()
    
        if (!ChangeTracker.HasChanges()) return;

        var modifiedEntries = ChangeTracker.Entries().Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified));

        foreach (var entry in modifiedEntries)
        
            entry.ModifyTypes<DateTime>(ConvertToUtc);
            entry.ModifyTypes<DateTime?>(ConvertToUtc);
        
    

    private static DateTime ConvertToUtc(DateTime dt)
    
        if (dt.Kind == DateTimeKind.Utc) return dt;
        return dt.ToUniversalTime();
    

    private static DateTime? ConvertToUtc(DateTime? dt)
    
        if (dt?.Kind == DateTimeKind.Utc) return dt;
        return dt?.ToUniversalTime();
    

扩展名是(基于 Talon https://***.com/a/39974362/618660 的回复

public static class TypeReflectionExtension

    static Dictionary<Type, PropertyInfo[]> PropertyInfoCache = new Dictionary<Type, PropertyInfo[]>();

    static void TypeReflectionHelper()
    
        PropertyInfoCache = new Dictionary<Type, PropertyInfo[]>();
    

    public static PropertyInfo[] GetTypeProperties(this Type type)
    
        if (!PropertyInfoCache.ContainsKey(type))
        
            PropertyInfoCache[type] = type.GetProperties();
        
        return PropertyInfoCache[type];
    

    public static void ModifyTypes<T>(this DbEntityEntry dbEntityEntry, Func<T, T> method)
    
        foreach (var propertyInfo in dbEntityEntry.Entity.GetType().GetTypeProperties().Where(p => p.PropertyType == typeof(T) && p.CanWrite))
        
            propertyInfo.SetValue(dbEntityEntry.Entity, method(dbEntityEntry.CurrentValues.GetValue<T>(propertyInfo.Name)));
        
    

【讨论】:

【参考方案17】:

扩展了适用于我的 EF Dotnet Core 的先前解决方案放置代码。

 public partial class XxxxxxDataContext 
    partial void CustomizeMapping(ref ModelBuilder modelBuilder) 
      var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
        v => v.ToUniversalTime(),
        (v) => v.Kind == DateTimeKind.Utc?DateTime.SpecifyKind(v, DateTimeKind.Utc):TimeZoneInfo.ConvertTimeToUtc(v, TimeZoneInfo.Local));

      var nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
        v => v.HasValue ? v.Value.ToUniversalTime() : v,
        v => v.HasValue ? v.Value.Kind == DateTimeKind.Utc ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : TimeZoneInfo.ConvertTimeToUtc(v.Value, TimeZoneInfo.Local) : v);

      foreach (var entityType in modelBuilder.Model.GetEntityTypes()) 
        if (entityType.IsKeyless) 
          continue;
        

        foreach (var property in entityType.GetProperties()) 
          if (property.ClrType == typeof(DateTime)) 
            property.SetValueConverter(dateTimeConverter);
           else if (property.ClrType == typeof(DateTime?)) 
            property.SetValueConverter(nullableDateTimeConverter);
          
        
      
    
  

【讨论】:

以上是关于实体框架日期时间和 UTC的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JPA 和 Hibernate 在 UTC 时区中存储日期/时间和时间戳

如何在条件下使用实体框架组日期而不是时间日期和总和

将 UTC 设置为默认日期时区

按日期部分比较实体框架中的SQL日期时间

在实体框架代码中,日期时间总是更改为时间戳

实体框架中的日期时间文化问题