Получить DateTime как UTC с Dapper
Я использую Dapper для сопоставления своих объектов с SQL Server CE. Если я сохраняю DateTime
с Kind=Utc
, когда я его прочитаю, я получаю DateTime
с Kind=Unspecified
, что приводит к возникновению всех проблем.
Пример:
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);
Этот код дает следующий результат:
20/09/2012 10:04:16 (Utc)
20/09/2012 10:04:16 (Unspecified)
Я знаю, что должен использовать DateTimeOffset
, но, к сожалению, SQL CE не поддерживает этот тип.
Есть ли обходной путь? Могу ли я сказать Dapper, что все даты имеют DateTimeKind.Utc
? И в общем, каковы мои настройки для настройки отображения?
EDIT: Моим текущим обходным путем является исправление дат после того, как Dapper материализовал результат, но он немного пахнет...
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;
}
Ответы
Ответ 1
Посмотрел код Dapper. Если мой учет не был устаревшим, для типов значений, таких как datetime (который отображается в DbType.DateTime), dapper просто выполняет простое преобразование из объекта IDataReader.
Pseudo: return return (DateTime) IDataReader.GetValue(0);
Это конкретный случай для Datetime из кучи общего кода и lambdas.
AFAIK, SQL datetime никогда не хранит смещение/часовой пояс, поэтому вид всегда будет указывать "Unspecified" в любом хранилище и выборке времени.
Итак, чтобы сделать это чисто, вы можете коснуться внутренних функций dapper:
который является болью, так как вам придется прикоснуться к большому методу генерации IL (Deserializer DataRow) и поставить случай if для DateTime.
ИЛИ
просто установите сеттер в репозитории DateTime, где UTC является проблемой (что не похоже на POCO, но относительно разумно):
class Foo
{
private DateTime _modificationDate;
public DateTime ModificationDate
{
get { return _modificationDate; }
set { _modificationDate = DateTime.SpecifyKind(value, DateTimeKind.Utc); }
}
//Ifs optional? since it always going to be a UTC date, and any DB call will return unspecified anyways
}
Ответ 2
Добавление этого ответа для всех, кто приходит в поисках простого исправления. Это возможно теперь с добавлением SqlMapper.TypeHandler в Dapper.
Добавьте этот класс, чтобы преобразовать значение из db в datetime с типом, указанным как 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);
}
}
Затем в моем файле Global.asax моего веб-API я добавлю обработчик типа dapper.
SqlMapper.AddTypeHandler(new DateTimeHandler());
Если вам нужно убедиться, что вы всегда вставляете даты в формате UTC, то в методе SetValue вы можете использовать:
parameter.Value = DateTime.SpecifyKind(value, DateTimeKind.Utc);
Ответ 3
Если вы используете Dapper из источника (не nuget), вы можете настроить код, чтобы всегда принудительно устанавливать DateTimeKind из UTC. Более настраиваемый параметр может заключаться в создании нового атрибута для значений свойства DateTime, которые позволяют указать тип времени даты как подсказку для dapper. Dapper может искать свойства DateTime с этим атрибутом, и когда найденный может использовать его для указания типа DateTime во время сопоставления ORM. Это может быть приятной особенностью для core dapper, поскольку вы не единственная проблема с этой проблемой:)