实体框架DateTime和UTC


96

是否可以让实体框架(我目前正在使用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");

Answers:


144

您可以考虑以下一种方法:

首先,定义以下属性:

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

这样,只要Entity Framework从数据库加载实体,它就会设置DateTimeKind您指定的实体,例如UTC。

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


7
如果无法正常工作,则可能缺少以下一种用法:使用系统;使用System.Collections.Generic; 使用System.ComponentModel.DataAnnotations.Schema; 使用System.Linq; 使用System.Reflection;
Saustrup 2014年

7
@Saustrup-您会发现关于SO的大多数示例为简洁起见将省略用法,除非它们与问题直接相关。但是,谢谢。
Matt Johnson-Pint 2014年

4
@MattJohnson,如果没有@Saustrup的using语句,则会遇到一些无用的编译错误,例如'System.Array' does not contain a definition for 'Where'
Jacob Eggers 2014年

7
正如@SilverSideDown所说,这仅适用于.NET 4.5。我在gist.github.com/munr/3544bd7fab6615290561上创建了一些扩展使其与.NET 4.0兼容。要注意的另一件事是,这不适用于投影,仅适用于完全加载的实体。

5
关于将其与预测一起使用有什么建议吗?
2015年

32

我真的很喜欢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));
            }
        }
    }
}

1
这是对已接受答案的非常有用的扩展!
学习者

也许我错过了一些东西,但是相对于DateTimeKind.Unspecified,它如何默认为DateTimeKind.Utc?
罗纳吉'17

1
@Rhonage对不起。默认设置在ApplicationDbContext构造函数中。我更新了答案以包含该内容。
Bob.at.Indigo.Health

1
@ Bob.at.AIPsychLab感谢队友,现在更加清晰。试图找出是否有一些反射在进行-但是,不,死了很简单!
Rhonage

如果模型的DateTIme属性没有(公共)setter方法,则此操作将失败。建议编辑。另请参阅stackoverflow.com/a/3762475/2279059
弗洛里安(Florian Winter)

13

该答案适用于Entity Framework 6

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

为此,我们需要使用DbCommandInterceptorEntityFramework提供的对象。

创建拦截器:

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事件中发生的表映射,因此单元测试将因模拟失败。这个得到我的投票!
参议员

1
你为什么阴影DisposeGetData
user247702

2
该代码可能应该归功于@IvanStoev:stackoverflow.com/a/40349051/90287
Rami A.

不幸的是,如果您正在映射空间数据,则此操作将失败
克里斯(Chris

@ user247702是的阴影Dispose是错误的,重写Dispose(bool)
user2397863

9

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

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

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

并且,当它被传递回我的服务器时,它也自动按照预期再次转换为UTC。


7
与通常的感觉相反,DateTimeOffset不存储时区。它存储该值表示的与UTC的偏移量。无法将偏移量反向映射,以确定从其创建偏移量的实际时区,从而使数据类型几乎无用。
Suncat2000

2
否,但是可以用来正确存储DateTime:medium.com/@ojb500/in-praise-of-datetimeoffset-e0711f991cba
Carl

1
仅UTC不需要位置,因为它到处都是相同的。如果您使用的不是UTC,则还需要位置,否则,使用datetimeoffset时,时间信息将无用。
Horitsu

@ Suncat2000到目前为止,这是存储时间点的最明智的方法。所有其他日期/时间类型也不提供时区。
约翰

1
DATETIMEOFFSET将执行原始海报所需的操作:将日期时间存储为UTC,而无需执行任何(显式)转换。@Car DATETIME,DATETIME2和DATETIMEOFFSET都正确存储了日期时间值。除了额外存储UTC的偏移量之外,DATETIMEOFFSET几乎没有任何优势。您在数据库中使用的就是您的电话。我只是想开车回家,因为它没有很多人错误地认为的时区
Suncat2000

5

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


5

我正在对此进行研究,但大多数答案并不十分理想。据我所知,没有办法告诉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之外的任何人标记的本地化日期。


6
正如我在解决该问题数年后所发现的那样,如果要在其他结构(例如数据传输对象)中分配或选择DateTime字段,则EF会同时忽略getter和setter方法。在这些情况下,DateTimeKind.Utc生成结果后,您仍然必须将Kind更改为。示例:from o in myContext.Records select new DTO() { BrokenTimestamp = o.BbTimestamp };将所有Kind设置为DateTimeKind.Unspecified
Suncat2000

1
我在实体框架上使用DateTimeOffset已有一段时间了,如果您使用数据类型DateTimeOffset指定EF实体,那么所有EF查询都将返回带有UTC偏移量的日期,就像保存在数据库中一样。因此,如果将数据类型更改为DateTimeOffset而不是DateTime,则不需要上述解决方法。
Moutono

很高兴知道!感谢@Moutono

按照@ Suncat2000的评论,这根本行不通,应该删除
Ben Morris

5

对于EF Core,在GitHub上对此主题进行了精彩的讨论:https : //github.com/dotnet/efcore/issues/4711

一种解决方案(对Christopher Haws的积分)将所有日期存储为UTC或从数据库中检索出来的所有日期都视为UTC,这OnModelCreating是在DbContext类的方法中添加以下内容:

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.IsQueryType)
    {
        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 ,请检查此链接


绝对是我的最佳解决方案!谢谢
本莫里斯

这与DateTimeOffset一起使用吗?
Mark Redman

1
@MarkRedman我认为这没有任何意义,因为如果您有DateTimeOffset的合法用例,则还希望保留有关时区的信息。有关何时选择DateTime和DateTimeOffset的信息,请参阅docs.microsoft.com/zh-cn/dotnet/standard/datetime/…stackoverflow.com/a/14268167/3979621
Honza Kalfus

IsQueryType似乎已被取代IsKeyLessgithub.com/dotnet/efcore/commit/...
马克Tielemans


3

一年,另一个解决方案!这是针对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,则会抛出描述性异常。


1
这是一个很好的解决方案,尤其是在大多数新开发将使用Core或.NET 5的情况下,应该更高一些。UTC实施政策的额外假想点-如果更多的人将UTC的日期一直保留到实际的用户显示中,我们几乎没有任何日期/时间错误。
oflahero

1

对于那些需要像我这样使用.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);
        }  

1

马特·约翰逊·品特(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));
                }
            }
        }
    }
}

0

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


0

就我而言,我只有一张带有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;
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.