62

我正在使用 Dapper 将我的实体映射到 SQL Server CE。如果我保存一个DateTimewith Kind=Utc,当我读回它时,我会得到一个DateTimewith 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吗?更一般地说,我有哪些自定义映射的选项?


编辑:我目前的解决方法是在 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;
}
4

4 回答 4

102

为其他寻求简单修复的人添加此答案。现在可以通过在 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);
于 2016-09-27T16:52:06.047 回答
28

查看了 Dapper 代码。除非我的已经过时,否则对于像 datetime(映射到 DbType.DateTime)这样的值类型,dapper 只是从 IDataReader 对象进行简单的转换。

伪:yield return (DateTime)IDataReader.GetValue(0);

这是一堆通用代码和 lambdas 中 Datetime 的特定情况。

AFAIK,SQL 日期时间从不存储偏移量/时区,因此在您存储和获取的任何日期时间中,这种类型总是会说“未指定”。

所以,为了干净利落,你可以触摸精致的内部结构:

这很痛苦,因为您必须接触一个大的 IL 生成方法(DataRow Deserializer)并为 DateTime 放入 if 案例。

或者

只需在 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
}
于 2013-06-13T17:08:48.523 回答
4

只是想将我的完整解决方案放在这里,以便将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());
        }
    }
}
于 2018-04-11T16:53:22.363 回答
0

如果您使用源代码(不是 nuget)的 Dapper,您可以调整代码以始终强制使用 UTC 的 DateTimeKind。一个更可配置的选项可能是为 DateTime 属性值创建一个新属性,允许您指定日期时间类型作为 dapper 的提示。Dapper 可以使用此属性查找 DateTime 属性,并在找到时使用它在 ORM 映射期间指定 DateTime 类型。这对于核心 dapper 来说可能是一个不错的功能,因为您不是唯一遇到此问题的人 :)

于 2014-01-24T21:42:03.147 回答