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

Ric*_*imo 5 c# sql-server datetime datetime2 dapper

类似于问题如何让 Dapper 将 .net DateTime 映射到 DateTime2;但我希望事后能够将其重新设置回来。

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

我正在使用同一问题的得票较高的答案。然而,这种做法似乎也是不可逆转的。似乎第一个查询完成时设置的任何值都会保留,并且此后不可更改。

以下代码是 MCVE。如果运行它,您会看到类型始终显示为“datetime”,并且值的精度永远不会超过毫秒(正如您对日期时间的期望);尽管尝试改变映射。然后,您必须注释掉对“PerformDapperQuery()”的第一次调用,然后再次运行它:您现在将看到该类型始终返回为“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}");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Damien_The_Un believer 给出缓存解释后进行编辑

我将上面的查询更改为 $"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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

using (new DapperDateTime2MapperScope())
{
    -- Perform Dapper query here
}
Run Code Online (Sandbox Code Playgroud)

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

Dam*_*ver 3

您的代码确实正确地更改了类型映射 - 但 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();
        }
Run Code Online (Sandbox Code Playgroud)

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