使用Dapper获取DateTime作为UTC

Tho*_*que 55 .net c# orm utc dapper

我正在使用Dapper将我的实体映射到SQL Server CE.如果我保存DateTimeKind=Utc,当我读回我得到一个DateTimeKind=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);
Run Code Online (Sandbox Code Playgroud)

此代码提供以下输出:

20/09/2012 10:04:16 (Utc)
20/09/2012 10:04:16 (Unspecified)
Run Code Online (Sandbox Code Playgroud)

我知道我应该使用a 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;
}
Run Code Online (Sandbox Code Playgroud)

小智 70

为寻求简单修复的其他人添加此答案.现在可以在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在我的Web API的Global.asax文件中,我将类型处理程序添加到dapper.

SqlMapper.AddTypeHandler(new DateTimeHandler());
Run Code Online (Sandbox Code Playgroud)

如果您需要确保始终以UTC格式插入日期,则可以使用SetValue方法:

parameter.Value = DateTime.SpecifyKind(value, DateTimeKind.Utc);
Run Code Online (Sandbox Code Playgroud)

  • 这绝对是正确的答案.我要做的一个区别是将`SqlMapper.AddTypeHandler`初始化代码放在特定于SQL的东西中,例如连接工厂的静态构造函数.这样,无论您从何处进行连接,都可以初始化此处理程序,而不是仅通过记住初始化它的Web服务. (6认同)
  • 这有效吗?我认为 Dapper 不允许您覆盖内置类型的映射。 (3认同)
  • 要实现此目的:首先使用 SqlMapper.RemoveTypeMap(typeof(DateTime)); 删除默认的 DateTime 处理程序;然后使用 SqlMapper.AddTypeHandler(new DateTimeHandler()); 注册您的自定义处理程序; (2认同)

Viv*_*vek 27

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

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

这是Datetime中一堆通用代码和lambdas的具体情况.

AFAIK,SQL datetime永远不会存储偏移量/时区,因此在您存储和获取的任何日期时间,该类型总是会显示"未指定".

所以,要干净利落地做,你可以触摸小巧玲珑的内部:

这是一个痛苦,因为您必须触摸一个大的IL生成方法(DataRow Deserializer)并为DateTime添加if情况.

要么

只是在DateTime道具上放置一个setter,其中UTC是一个问题(这是对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
}
Run Code Online (Sandbox Code Playgroud)


jam*_*nor 5

只是想将我的完整解决方案放在这里,以便将DateTimeOffset/ DateTimeOffset?fields/properties 与 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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)