J'ai un programme C # où tous les objets DateTime
sont DateTimeKind.UTC
. Lors de l'enregistrement des objets dans la base de données, il stocke UTC comme prévu. Cependant, lors de leur récupération, ils sont DateTimeKind.Unspecified
. Existe-t-il un moyen de dire à Entity Framework (Code First) lors de la création d'objets DateTime
en C # de toujours utiliser DateTimeKind.UTC
?
Non, il n'y en a pas. Et c'est en fait DateTimeKind.Unspecified
.
Cependant, si vous êtes préoccupé par la prise en charge de plusieurs fuseaux horaires, vous devez envisager d'utiliser DateTimeOffset . C'est comme un DateTime normal, sauf qu'il ne représente pas une "perspective" du temps, il représente une vue absolue, dans laquelle 3PM (UTC - 3) est égal à 4PM (UTC - 2). DateTimeOffset contient à la fois le DateTime et le fuseau horaire et il est pris en charge par EntityFramework et SQL Server.
Vous pouvez demander à votre datacontext de corriger toutes les valeurs pertinentes au fur et à mesure. Ce qui suit le fait avec un cache de propriétés pour les types d'entités, afin d'éviter d'avoir à examiner le type à chaque fois:
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 */
}
Cela peut également être combiné avec des attributs de manière à permettre de définir le DateTimeKind
que chaque propriété devrait avoir, en stockant un ensemble de règles sur chaque propriété, plutôt que simplement le PropertyInfo
, et en recherchant l'attribut dans GetDateProperties
.
Ma solution, en utilisant d'abord le code: Déclarez les propriétés DateTime de cette façon:
private DateTime _DateTimeProperty;
public DateTime DateTimeProperty
{
get
{
return _DateTimeProperty;
}
set
{
_DateTimeProperty = value.ToKindUtc();
}
}
Peut également créer la propriété en tant que:
private DateTime? _DateTimeProperty;
public DateTime? DateTimeProperty
{
get
{
return _DateTimeProperty;
}
set
{
_DateTimeProperty = value.ToKindUtc();
}
}
ToKindUtc()
est une extension pour changer DateTimeKind.Unspecified
en DateTimeKind.Utc
ou appeler ToUniversalTime()
si kind est DateTimeKind.Local
Voici le code des 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;
}
}
N'oubliez pas d'inclure dans le fichier du modèle.
using TheNameSpaceWhereClassIsDeclared;
La méthode set de propriété est appelée lors de la lecture à partir de la base de données avec EF, ou lorsqu'elle est affectée dans la méthode d'édition d'un contrôleur MVC.
Attention, si dans les formulaires Web, si vous modifiez les dates dans le fuseau horaire local, vous DEVEZ convertir la date en UTC avant de l'envoyer au serveur.
Jetez un œil à la réponse michael.aird ici: https://stackoverflow.com/a/9386364/27959 Il tamponne le type de date UTC lors du chargement, avec un événement sur ObjectMaterialized.