将 .net DateTime 的 Dapper 映射更改为使用 DbType DateTime2 并再次恢复它

Posted

技术标签:

【中文标题】将 .net DateTime 的 Dapper 映射更改为使用 DbType DateTime2 并再次恢复它【英文标题】:Changing the Dapper mapping for .net DateTime to use DbType DateTime2 and reinstating it back again 【发布时间】:2019-01-23 14:05:01 【问题描述】:

类似问题How can I get Dapper to map .net DateTime to DateTime2;但我希望之后能够重新设置它。

目前对该问题的公认答案涉及更改 Dapper 源文件;但我正在使用 NuGet 包,所以这对我不起作用。正如对已接受答案的第一条评论所指出的那样,这是不可逆的 - “如果一些是 DateTime 而另一些是 DateTime2 怎么办?” - 这是我的场景:不同的查询需要不同的映射(每个查询都会只需要一个或另一个)。

我正在使用higher-voted answer 来回答同样的问题。然而,这种方法似乎是不可逆的。似乎在第一次查询完成时设置的任何值都保留下来,之后就不可更改了。

以下代码是一个 MCVE。如果你运行它,你会看到类型 always 显示为“日期时间”,并且值的精度永远不会超过毫秒(正如你对日期时间所期望的那样);尽管尝试更改映射。然后您必须注释掉对“PerformDapperQuery()”的第一次调用,然后再次运行它:您现在将看到类型 always 返回为“datetime2”,并且值具有完整的 7-几分之一秒的数字精度(正如您对 datetime2 所期望的那样)。

public static void Main()

    // I know this is marked as obsolete, and I am open to suggestions for alternatives.
    // see https://github.com/StackExchange/Dapper/issues/798
#pragma warning disable 618
    var oldValue = SqlMapper.LookupDbType(typeof(DateTime), null, false, out var handler);
#pragma warning restore 618

    PerformDapperQuery();
    SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2);
    PerformDapperQuery();
    SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime);
    PerformDapperQuery();
    SqlMapper.AddTypeMap(typeof(DateTime), oldValue);
    PerformDapperQuery();
 

private static void PerformDapperQuery()

    using (var connection = new SqlConnection("server=localhost;Database=master;Integrated Security=SSPI;"))
    
        var parameters = new  Param = DateTime.Now ;
        using (var reader = connection.ExecuteReader(
            "SELECT sql_variant_property(@Param, 'BaseType'), CAST(@PARAM AS datetime2(7))", parameters))
        
            Assert.That(reader.Read(), Is.True);
            string type = reader.GetString(0);
            DateTime value = reader.GetDateTime(1);
            Console.WriteLine($"Output: type,value:o");
        
    

所以问题的第一部分是:如何多次更改 Dapper 的 DateTime 映射?问题的第二部分是我想恢复以前的映射;但如您所见,LookupDbType 被标记为已过时,所以如果有任何替代方法,我会很感兴趣。

在 Damien_The_Unbeliever 给出缓存解释后编辑

我将上面的查询更改为 $"SELECT sql_variant_property(@Param, 'BaseType'), CAST(@PARAM AS datetime2(7)) -- DateTime.Now:o",这样会有所不同每次,果然,这确实改变了行为。

我遇到这个问题的原因是我想添加一些东西来包装特定的 Dapper 查询,使它们使用 DateTime2 而不是 DateTime,所以我编写了这个类:

internal sealed class DapperDateTime2MapperScope : IDisposable

    private readonly DbType? _predecessor;
    private bool _isDisposed;

    public DapperDateTime2MapperScope()
    
        _predecessor = SqlMapperGetDbType();
        SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2);
    

    public void Dispose()
    
        Dispose(true);
        GC.SuppressFinalize(this);
    

    private void Dispose(bool disposing)
    
        if (!_isDisposed)
        
            if (disposing)
            
                if (_predecessor.HasValue)
                
                    SqlMapper.AddTypeMap(typeof(DateTime), _predecessor.Value);
                
            

            _isDisposed = true;
        
    

    private DbType SqlMapperGetDbType()
    
#pragma warning disable 618
        return SqlMapper.LookupDbType(typeof(DateTime), null, false, out var handler);
#pragma warning restore 618
    

然后可用于将 Dapper 查询包装在 using 块中,以使该查询使用 DateTime2 映射:

using (new DapperDateTime2MapperScope())

    -- Perform Dapper query here

然后我编写了该类的单元测试 - 一个没有using 的测试和一个带有using 的测试;而且我发现单元测试交互彼此:它们可以单独工作,但是当所有测试都运行时,一个或另一个测试会失败。原因(感谢 Damien 的解释)是 Dapper 的查询缓存。好消息是我认为这很好 - 单元测试遇到了问题,因为它们使用相同的查询;但是在我真正的代码库中,如果我在这个using 中包装一个特定的查询,那么我总是希望那个查询使用那个映射。所以基本上这只是我的单元测试的一个问题,而不是真正使用该类。

【问题讨论】:

【参考方案1】:

您的代码确实正确地更改了类型映射 - 但 dapper 会积极缓存查询。

如果您的实际查询在 datetimedatetime2 用例之间有所不同(我希望他们会这样),那应该没问题。否则,您可以自己清除查询缓存(但显然这可能会产生其他连锁反应、不良后果):

        public static void Main()
        
            // I know this is marked as obsolete, and I am open to suggestions for alternatives.
            // see https://github.com/StackExchange/Dapper/issues/798
#pragma warning disable 618
            var oldValue = SqlMapper.LookupDbType(typeof(DateTime), null, false, out var handler);
#pragma warning restore 618

            PerformDapperQuery();
            SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2);
            SqlMapper.PurgeQueryCache();
            PerformDapperQuery();
            SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime);
            SqlMapper.PurgeQueryCache();
            PerformDapperQuery();
            SqlMapper.AddTypeMap(typeof(DateTime), oldValue);
            SqlMapper.PurgeQueryCache();
            PerformDapperQuery();
        

对于您使用LookupDbType,我认为您可以改用GetDbType

【讨论】:

GetDbType 也已过时;但肯定缓存是问题,所以我将其标记为答案并编辑问题以解释这是如何在我为检查行为而编写的单元测试中发生的,以及(现在我已经考虑过)为什么它实际上会赢'在实际代码中不是问题。谢谢。

以上是关于将 .net DateTime 的 Dapper 映射更改为使用 DbType DateTime2 并再次恢复它的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Dapper 中获取正确的 DateTime

使用 DateTime 类型参数的 Dapper 查询速度慢?

如何使用 Dapper.NET 将 C# 列表插入数据库

使用 TableValuedParameter 和 Dapper 将字符串转换为 smalldatetime 失败

dapper.net,如何刷新 ConcurrentDictionary?

.NET轻量级ORM框架Dapper入门精通