我使用Dapper在SQLite中插入和获取对象:一个对象具有DateTime(和DateTimeOffset)类型的属性,我必须以毫秒精度保持和检索。我无法找到正确检索值的方法,因为Dapper失败了:
System.FormatException : String was not recognized as a valid DateTime.
in System.DateTimeParse.ParseExactMultiple(String s, String[] formats, DateTimeFormatInfo dtfi, DateTimeStyles style)
in System.DateTime.ParseExact(String s, String[] formats, IFormatProvider provider, DateTimeStyles style)
in System.Data.SQLite.SQLiteConvert.ToDateTime(String dateText, SQLiteDateFormats format, DateTimeKind kind, String formatString)
in System.Data.SQLite.SQLite3.GetDateTime(SQLiteStatement stmt, Int32 index)
in System.Data.SQLite.SQLite3.GetValue(SQLiteStatement stmt, SQLiteConnectionFlags flags, Int32 index, SQLiteType typ)
in System.Data.SQLite.SQLiteDataReader.GetValue(Int32 i)
in System.Data.SQLite.SQLiteDataReader.GetValues(Object[] values)
in Dapper.SqlMapper.<>c__DisplayClass5d.<GetDapperRowDeserializer>b__5c(IDataReader r) in SqlMapper.cs: line 2587
in Dapper.SqlMapper.<QueryImpl>d__11`1.MoveNext() in SqlMapper.cs: line 1572
in System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
in System.Linq.Enumerable.ToList(IEnumerable`1 source)
in Dapper.SqlMapper.Query(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in SqlMapper.cs: line 1443
in Dapper.SqlMapper.Query(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in SqlMapper.cs: line 1382
我该怎么办?列的类型为DATETIME。
我是否必须创建自定义TypeHandler并将DateTime转换为SQLite字符串,格式为&#34; o&#34;?
Dapper version 1.38
答案 0 :(得分:1)
我知道它很旧,但是我找到了解决方案。 经过大量的分析和分析Dapper代码后,我想到了这一点(注意这是2019年):
首先,您必须创建日期时间处理程序:
public class DateTimeHandler : SqlMapper.TypeHandler<DateTimeOffset>
{
private readonly TimeZoneInfo databaseTimeZone = TimeZoneInfo.Local;
public static readonly DateTimeHandler Default = new DateTimeHandler();
public DateTimeHandler()
{
}
public override DateTimeOffset Parse(object value)
{
DateTime storedDateTime;
if (value == null)
storedDateTime = DateTime.MinValue;
else
storedDateTime = (DateTime)value;
if (storedDateTime.ToUniversalTime() <= DateTimeOffset.MinValue.UtcDateTime)
return DateTimeOffset.MinValue;
else
return new DateTimeOffset(storedDateTime, databaseTimeZone.BaseUtcOffset);
}
public override void SetValue(IDbDataParameter parameter, DateTimeOffset value)
{
DateTime paramVal = value.ToOffset(this.databaseTimeZone.BaseUtcOffset).DateTime;
parameter.Value = paramVal;
}
}
现在,请注意,Dapper将.Net的类型DateTimeOffset转换为dbType-DateTimeOffset。您需要删除此映射并添加自己的映射:
SqlMapper.RemoveTypeMap(typeof(DateTimeOffset));
SqlMapper.AddTypeHandler(DateTimeHandler.Default);
仅此而已。现在,每次Dapper都会在模型中看到DateTimeOffset属性,它将运行DateTimeHandler进行管理。
答案 1 :(得分:0)
我发现由于在查找TypeHandler之前选择了默认的typeMap,因此无法使用基本类型的自定义TypeHandler。
我已经打开了一个问题dapper-dot-net但是在同一时间我已经解决了通过反射替换默认的typeMap,其中一个新的类似于前一个减去四个键DateTime,DateTime?,DateTimeOffset,DateTimeOffset?
答案 2 :(得分:0)
我已经对Adam Jachocki的解决方案做了些微修改,因为它对我不起作用。我在Sqlite中将日期存储为TEXT,而Dapper为我提供了一个字符串而不是DateTime作为要解析的对象值。显然,Sqlite使用三种不同的数据类型存储日期时间值:INTEGER(unix epoch),TEXT(ISO 8601 YYYY-MM-DD HH:MM:SS.SSS)和REAL(“自11月24日中午以来在格林威治的天数,公元前4741年”)。最后一个确实存在,因此下面的代码不支持它。 有关更多信息,请参见sqlite docs和this page。
下面是我对DateTimeOffset TypeHandler的实现。亚当的其余解决方案保持不变。
internal class DateTimeOffsetHandler : SqlMapper.TypeHandler<DateTimeOffset>
{
private static readonly TimeZoneInfo databaseTimeZone = TimeZoneInfo.Local;
private static readonly DateTime unixOrigin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
public static DateTimeOffsetHandler Default { get; } = new DateTimeOffsetHandler();
public DateTimeOffsetHandler() {}
public override DateTimeOffset Parse(object value)
{
if (!TryGetDateTime(value, out DateTime storedDateValue))
{
throw new InvalidOperationException($"Unable to parse value {value} as DateTimeOffset");
}
if (storedDateValue.ToUniversalTime() <= DateTimeOffset.MinValue.UtcDateTime)
{
return DateTimeOffset.MinValue;
}
else
{
return new DateTimeOffset(storedDateValue, databaseTimeZone.BaseUtcOffset);
}
}
public override void SetValue(IDbDataParameter parameter, DateTimeOffset value)
{
DateTime paramVal = value.ToOffset(databaseTimeZone.BaseUtcOffset).DateTime;
parameter.Value = paramVal;
}
private bool TryGetDateTime(object value, out DateTime dateTimeValue)
{
dateTimeValue = default;
if (value is DateTime d)
{
dateTimeValue = d;
return true;
}
if (value is string v)
{
dateTimeValue = DateTime.Parse(v);
return true;
}
if (long.TryParse(value?.ToString() ?? string.Empty, out long l))
{
dateTimeValue = unixOrigin.AddSeconds(l);
return true;
}
if (float.TryParse(value?.ToString() ?? string.Empty, out float f))
{
throw new InvalidOperationException("Unsupported Sqlite datetime type, REAL.");
}
return false;
}
}