在SQLite上使用Dapper正确转换DateTime属性

时间:2014-11-20 10:05:31

标签: datetime sqlite dapper

我使用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

3 个答案:

答案 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 docsthis 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;
    }
}