如何指定从 EntityFramework 检索到的 DateTime 对象应该是 DateTimeKind.UTC

How to specify that DateTime objects retrieved from EntityFramework should be DateTimeKind.UTC

我有一个 C# 程序,其中所有 DateTime 个对象都是 DateTimeKind.UTC。将对象保存到数据库时,它会按预期存储 UTC。然而,当检索它们时,它们是DateTimeKind.Unspecified。在 C# 中创建 DateTime 对象时,有没有办法告诉 Entity Framework(代码优先)始终使用 DateTimeKind.UTC

不,没有。实际上是 DateTimeKind.Unspecified.

但是,如果您担心支持多个时区,则应考虑使用 DateTimeOffset。它就像一个常规的 DateTime,只是它不代表 "perspective" 时间,它代表一个绝对视图,其中 3PM (UTC - 3) 等于 4PM (UTC - 2)。 DateTimeOffset 包含日期时间和时区,EntityFramework 和 SQL 服务器都支持它。

您可以让您的数据上下文在运行时修复所有相关值。以下内容使用实体类型的属性缓存来执行此操作,以避免每次都必须检查类型:

public class YourContext : DbContext
{
  private static readonly List<PropertyInfo> EmptyPropsList = new List<PropertyInfo>();
  private static readonly Hashtable PropsCache = new Hashtable(); // Spec promises safe for single-reader, multiple writer.
                                                                  // Spec for Dictionary makes no such promise, and while
                                                                  // it should be okay in this case, play it safe.
  private static List<PropertyInfo> GetDateProperties(Type type)
  {
    List<PropertyInfo> list = new List<PropertyInfo>();
    foreach(PropertyInfo prop in type.GetProperties())
    {
      Type valType = prop.PropertyType;
      if(valType == typeof(DateTime) || valType == typeof(DateTime?))
        list.Add(prop);
    }
    if(list.Count == 0)
      return EmptyPropsList; // Don't waste memory on lots of empty lists.
    list.TrimExcess();
    return list;
  }
  private static void FixDates(object sender, ObjectMaterializedEventArgs evArg)
  {
    object entity = evArg.Entity;
    if(entity != null)
    {
      Type eType = entity.GetType();
      List<PropertyInfo> rules = (List<PropertyInfo>)PropsCache[eType];
      if(rules == null)
        lock(PropsCache)
          PropsCache[eType] = rules = GetPropertyRules(eType); // Don't bother double-checking. Over-write is safe.
      foreach(var rule in rules)
      {
        var info = rule.PropertyInfo;
        object curVal = info.GetValue(entity);
        if(curVal != null)
          info.SetValue(entity, DateTime.SpecifyKind((DateTime)curVal, rule.Kind));
      }
    }
  }
  public YourContext()
  {
    ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += FixDates;
    /* rest of constructor logic here */
  }
  /* rest of context class here */
}

这也可以与属性结合,以便允许一个人设置 DateTimeKind 每个 属性 应该有的,通过存储一组关于每个 属性 的规则,而不是只是 PropertyInfo,并在 GetDateProperties.

中寻找属性

在此处查看 michael.aird 答案: 它在加载期间标记日期 UTC 种类,并在对象物化上有一个事件。

我的解决方案,首先使用代码: 以这种方式声明 DateTime 属性:

private DateTime _DateTimeProperty;
public DateTime DateTimeProperty
{
    get
    {
        return _DateTimeProperty;
    }
    set
    {
        _DateTimeProperty = value.ToKindUtc();
    }
}

也可以将 属性 创建为:

private DateTime? _DateTimeProperty;
public DateTime? DateTimeProperty
{
    get
    {
        return _DateTimeProperty;
    }
    set
    {
        _DateTimeProperty = value.ToKindUtc();
    }
}

ToKindUtc() 是将 DateTimeKind.Unspecified 更改为 DateTimeKind.Utc 的扩展,或者如果 kind 是 DateTimeKind.Local 则调用 ToUniversalTime() 这里是扩展代码:

public static class DateTimeExtensions
{
    public static DateTime ToKindUtc(this DateTime value)
    {
        return KindUtc(value);
    }
    public static DateTime? ToKindUtc(this DateTime? value)
    {
        return KindUtc(value);
    }
    public static DateTime ToKindLocal(this DateTime value)
    {
        return KindLocal(value);
    }
    public static DateTime? ToKindLocal(this DateTime? value)
    {
        return KindLocal(value);
    }
    public static DateTime SpecifyKind(this DateTime value, DateTimeKind kind)
    {
        if (value.Kind != kind)
        {
            return DateTime.SpecifyKind(value, kind);
        }
        return value;
    }
    public static DateTime? SpecifyKind(this DateTime? value, DateTimeKind kind)
    {
        if (value.HasValue)
        {
            return DateTime.SpecifyKind(value.Value, kind);
        }
        return value;
    }
    public static DateTime KindUtc(DateTime value)
    {
        if (value.Kind == DateTimeKind.Unspecified)
        {
            return DateTime.SpecifyKind(value, DateTimeKind.Utc);
        }
        else if (value.Kind == DateTimeKind.Local)
        {
            return value.ToUniversalTime();
        }
        return value;
    }
    public static DateTime? KindUtc(DateTime? value)
    {
        if (value.HasValue)
        {
            return KindUtc(value.Value);
        }
        return value;
    }
    public static DateTime KindLocal(DateTime value)
    {
        if (value.Kind == DateTimeKind.Unspecified)
        {
            return DateTime.SpecifyKind(value, DateTimeKind.Local);
        }
        else if (value.Kind == DateTimeKind.Utc)
        {
            return value.ToLocalTime();
        }
        return value;
    }
    public static DateTime? KindLocal(DateTime? value)
    {
        if (value.HasValue)
        {
            return KindLocal(value.Value);
        }
        return value;
    }
}

记得包含在模型的文件中。

using TheNameSpaceWhereClassIsDeclared;

属性 的设置方法在使用 EF 从数据库读取时调用,或者在 MVC 控制器的编辑方法中分配时调用。

警告,如果在 Web 表单中,如果您在本地时区编辑日期,则必须在发送到服务器之前将日期转换为 UTC。