How to specify that DateTime objects retrieved fro

2019-02-07 18:17发布

问题:

This question already has an answer here:

  • Entity Framework DateTime and UTC 10 answers

I have C# program where all DateTime objects are DateTimeKind.UTC. When saving the objects to the database it stores UTC as expected. However, when retreiving them, they are DateTimeKind.Unspecified. Is there a way to tell Entity Framework (Code First) when creating DateTime objects in C# to always use DateTimeKind.UTC?

回答1:

No, there's not. And it's actually DateTimeKind.Unspecified.

However, if you are concerned about supporting multiple timezones, you should consider using DateTimeOffset. It's like a regular DateTime, except that it does not represent a "perspective" of time, it represents an absolute view, in which 3PM (UTC - 3) equals 4PM (UTC - 2). DateTimeOffset contains both the DateTime and the time zone and it's supported by both EntityFramework and SQL Server.



回答2:

You can have your datacontext fix up all the relevant values as it goes. The following does so with a cache of properties for entity types, so as to avoid having to examine the type each time:

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 */
}

This can also be combined with attributes so as to allow one to set the DateTimeKind each property should have, by storing a set of rules about each property, rather than just the PropertyInfo, and looking for the attribute in GetDateProperties.



回答3:

My solution, using code first: Declare the DateTime properties in this way:

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

Also can create the property as:

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

ToKindUtc() is a extension to change DateTimeKind.Unspecified to DateTimeKind.Utc or call ToUniversalTime() if kind is DateTimeKind.Local Here the code for the extensions:

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

Remember to include in the model's file.

using TheNameSpaceWhereClassIsDeclared;

The set method of property is called when reading from datatabase with EF, or when assigned in a MVC controller's edit method.

Warning, if in web forms, if you edit dates in local timezone, you MUST convert the date to UTC before send to server.



回答4:

Have a look on the michael.aird answer here: https://stackoverflow.com/a/9386364/279590 It stamp the date UTC kind during loading, with an event on ObjectMaterialized.