Dapperを使用して、エンティティをSQL Server CEにマップしています。 DateTime
をKind=Utc
で保存すると、読み返すときにKind=Unspecified
でDateTime
を取得し、あらゆる種類の問題につながります。
例:
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はこのタイプをサポートしていません。
回避策はありますか?すべての日付にDateTimeKind.Utc
があると仮定するようにDapperに指示できますか?より一般的には、マッピングをカスタマイズするためのオプションは何ですか?
編集:私の現在の回避策は、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;
}
簡単な修正を探している人にこの答えを追加します。これは、DapperにSqlMapper.TypeHandlerを追加することで可能になりました。
このクラスを追加して、dbの値を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);
}
}
次に、Web APIのGlobal.asaxファイルで、タイプハンドラーをdapperに追加します。
SqlMapper.AddTypeHandler(new DateTimeHandler());
常にUTCとして日付を挿入していることを確認する必要がある場合は、SetValueメソッドで次を使用できます。
parameter.Value = DateTime.SpecifyKind(value, DateTimeKind.Utc);
Dapperコードを調べました。日付(DbType.DateTimeにマップされる)のような値タイプの場合、私のものが古くなっていない限り、dapperはIDataReaderオブジェクトから単純なキャストを行うだけです。
疑似:yield return(DateTime)IDataReader.GetValue(0);
これは、多数の汎用コードとラムダのうち、Datetimeに特有のケースです。
私の知る限り、SQLの日時はオフセット/タイムゾーンを保存しないため、保存および取得する日時で種類は常に「指定なし」と表示されます。
だから、それをする きれいに、dapperの内部に触れることができます:
大きなIL生成メソッド(DataRow Deserializer)に触れ、DateTimeのifケースを入力する必要があるため、これは苦痛です。
OR
uTCが問題となるDateTimeの小道具にセッターを置くだけです(POCOに対してちょっとですが、比較的健全です)。
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
}
DateTimeOffset
/DateTimeOffset?
フィールド/プロパティをMySQL 5.7データベース(DbType.DateTimeOffset
をサポートしていない)とシームレスに統合するための完全なソリューションをここに配置したかった-@ matt-jenkinsに基づく上記の答え:
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());
}
}
}
(nugetではなく)ソースからDapperを使用している場合、コードを微調整して、常にUTCのDateTimeKindを強制することができます。より構成可能なオプションは、DateTimeプロパティ値の新しい属性を作成して、dapperへのヒントとして日時の種類を指定できるようにすることです。 Dapperは、この属性を持つDateTimeプロパティを検索し、見つかった場合はORMマッピング中にDateTimeの種類を指定するためにそれを使用できます。この問題を抱えているのはあなただけではないので、これはコアdapperの素晴らしい機能です。