Obtenir DateTime comme UTC avec Dapper
j'utilise Dapper pour mapper mes entities vers SQL Server CE. Si j'enregistre un DateTime
Kind=Utc
lorsque je l'ai lu en retour je reçois un DateTime
Kind=Unspecified
, ce qui conduit à toutes sortes de problèmes.
Exemple:
var f = new Foo { Id = 42, ModificationDate = DateTime.UtcNow };
Console.WriteLine("{0} ({1})", f.ModificationDate, f.ModificationDate.Kind);
connection.Execute("insert into Foo(Id, ModificationDate) values(@Id, @ModificationDate)", f);
var f2 = connection.Query<Foo>("select * from Foo where Id = @Id", f).Single();
Console.WriteLine("{0} ({1})", f2.ModificationDate, f2.ModificationDate.Kind);
ce code donne la sortie suivante:
20/09/2012 10:04:16 (Utc)
20/09/2012 10:04:16 (Unspecified)
je sais que je devrais être à l'aide d'un DateTimeOffset
, mais malheureusement SQL CE n'a pas de support pour ce type.
Est-il une solution? Puis - je dire à Dapper de supposer que toutes les dates ont <!--8? Et plus généralement, quelles sont mes options pour personnaliser le mappage?
EDIT: ma solution actuelle est de corriger les dates après que Dapper ait matérialisé le résultat, mais ça sent un peu mauvais...
var results = _connection.Query<Foo>(sql, param).Select(PatchDate);
...
static Foo PatchDate(Foo f)
{
if (f.ModificationDate.Kind == DateTimeKind.Unspecified)
f.ModificationDate = DateTime.SpecifyKind(f.ModificationDate, DateTimeKind.Utc);
return f;
}
4 réponses
ajouter cette réponse pour toute autre personne qui vient à la recherche d'une solution simple. C'est maintenant possible avec l'ajout de SqlMapper.TypeHandler in Dapper.
ajouter cette classe pour convertir la valeur de db en datetime avec le type spécifié comme UTC.
public class DateTimeHandler : SqlMapper.TypeHandler<DateTime>
{
public override void SetValue(IDbDataParameter parameter, DateTime value)
{
parameter.Value = value;
}
public override DateTime Parse(object value)
{
return DateTime.SpecifyKind((DateTime)value, DateTimeKind.Utc);
}
}
puis dans mon Global.fichier asax de mon API Web j'ajoute le gestionnaire de type à dapper.
SqlMapper.AddTypeHandler(new DateTimeHandler());
si vous avez besoin de vous assurer que vous insérez toujours des dates comme UTC, alors sur la méthode SetValue vous pouvez utilisation:
parameter.Value = DateTime.SpecifyKind(value, DateTimeKind.Utc);
J'ai regardé dans le code Dapper. Sauf si le mien était périmé, pour les types de valeur comme datetime (qui est mappé à DbType.DateTime), dapper ne fait qu'un simple moulage à partir de L'objet IDataReader.
Pseudo : taux de retour (DateTime)IDataReader.GetValue (0);
C'est le cas spécifique pour Datetime hors d'un tas de code générique et lambdas.
AFAIK, SQL datetime ne stocke jamais l'offset / timezone de sorte que le type dira toujours "Unspecified" sur n'importe quel datetime vous de stocker et de récupérer.
Donc, pour le faire proprement, on pouvait le toucher dapper internes:
ce qui est une douleur car il faudrait toucher une grosse méthode de génération IL (le désérialiseur DataRow) et mettre dans un si case pour DateTime.
OR
il suffit de mettre un setter sur les accessoires DateTime où UTC est un problème (ce qui est un peu contre POCO mais est relativement sain):
class Foo
{
private DateTime _modificationDate;
public DateTime ModificationDate
{
get { return _modificationDate; }
set { _modificationDate = DateTime.SpecifyKind(value, DateTimeKind.Utc); }
}
//Ifs optional? since it's always going to be a UTC date, and any DB call will return unspecified anyways
}
je voulais juste mettre ma solution complète ici pour une intégration transparente DateTimeOffset
/DateTimeOffset?
Champs/propriétés avec une base de données MySQL 5.7 (qui ne supporte pas DbType.DateTimeOffset
) - basé sur la réponse de @matt-jenkins CI-DESSUS:
public static class DapperExtensions
{
class DateTimeOffsetTypeHandler : SqlMapper.TypeHandler<DateTimeOffset>
{
public override void SetValue(IDbDataParameter parameter, DateTimeOffset value)
{
switch (parameter.DbType)
{
case DbType.DateTime:
case DbType.DateTime2:
case DbType.AnsiString: // Seems to be some MySQL type mapping here
parameter.Value = value.UtcDateTime;
break;
case DbType.DateTimeOffset:
parameter.Value = value;
break;
default:
throw new InvalidOperationException("DateTimeOffset must be assigned to a DbType.DateTime SQL field.");
}
}
public override DateTimeOffset Parse(object value)
{
switch (value)
{
case DateTime time:
return new DateTimeOffset(DateTime.SpecifyKind(time, DateTimeKind.Utc), TimeSpan.Zero);
case DateTimeOffset dto:
return dto;
default:
throw new InvalidOperationException("Must be DateTime or DateTimeOffset object to be mapped.");
}
}
}
private static int DateTimeOffsetMapperInstalled = 0;
public static void InstallDateTimeOffsetMapper()
{
// Assumes SqlMapper.ResetTypeHandlers() is never called.
if (Interlocked.CompareExchange(ref DateTimeOffsetMapperInstalled, 1, 0) == 0)
{
// First remove the default type map between typeof(DateTimeOffset) => DbType.DateTimeOffset (not valid for MySQL)
SqlMapper.RemoveTypeMap(typeof(DateTimeOffset));
SqlMapper.RemoveTypeMap(typeof(DateTimeOffset?));
// This handles nullable value types automatically e.g. DateTimeOffset?
SqlMapper.AddTypeHandler(typeof(DateTimeOffset), new DateTimeOffsetTypeHandler());
}
}
}
si vous utilisez Dapper from source (pas nuget), vous pouvez modifier le code pour toujours forcer DateTimeKind de UTC. Une option plus configurable pourrait être de créer un nouvel attribut pour les valeurs de propriété DateTime qui vous permettent de spécifier le type date time comme un indice à dapper. Dapper peut rechercher des propriétés DateTime avec cet attribut et, lorsqu'il est trouvé, l'utiliser pour spécifier le type DateTime lors de la cartographie ORM. Ce pourrait être une belle fonctionnalité pour core dapper car vous n'êtes pas le seul avec ce problème :)