更改 .net DateTime 的 Dapper 映射以使用 DbType DateTime2 并再次恢复它
Changing the Dapper mapping for .net DateTime to use DbType DateTime2 and reinstating it back again
类似于问题How can I get Dapper to map .net DateTime to DateTime2;但我希望之后能够重新设置它。
该问题目前接受的答案涉及更改 Dapper 源文件;但我使用的是 NuGet 包,所以这对我不起作用。正如对已接受答案的第一条评论所指出的那样,这是不可逆的 - "What if some are DateTime and others are DateTime2?" - 这就是我的场景:不同的查询需要不同的映射(每个查询只需要一个或另一个)。
我正在使用 higher-voted answer 来回答同一个问题。然而,这种做法也似乎是不可逆的。似乎第一个查询完成时设置的任何值都保留了下来,之后就不能更改了。
以下代码是一个MCVE。如果你 运行 它,你会看到类型是 always 显示为 "datetime",并且值的精度永远不会超过毫秒(正如你所期望的那样对于日期时间);尽管尝试更改映射。然后,您必须注释掉对 "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
,所以我写了这个 class:
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
}
然后我编写了那个 class 的单元测试——一个没有 using
的测试,一个有 using
的测试;我发现单元测试 相互影响 :它们可以单独工作,但是当所有测试都是 运行 时,一个或另一个测试将失败。原因(感谢 Damien 的解释)是 Dapper 的查询缓存。好消息是我认为这很好——单元测试遇到了问题,因为它们使用了相同的查询;但在我的真实代码库中,如果我将特定查询包装在 using
中,那么我总是希望该查询使用该映射。所以基本上这只是我的单元测试的问题,而不是真正使用 class.
您的代码确实正确地更改了类型映射 - 但 dapper 积极缓存查询。
如果您的实际查询在 datetime
和 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);
SqlMapper.PurgeQueryCache();
PerformDapperQuery();
SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime);
SqlMapper.PurgeQueryCache();
PerformDapperQuery();
SqlMapper.AddTypeMap(typeof(DateTime), oldValue);
SqlMapper.PurgeQueryCache();
PerformDapperQuery();
}
对于 LookupDbType
的使用,我认为您可以改用 GetDbType
。
类似于问题How can I get Dapper to map .net DateTime to DateTime2;但我希望之后能够重新设置它。
该问题目前接受的答案涉及更改 Dapper 源文件;但我使用的是 NuGet 包,所以这对我不起作用。正如对已接受答案的第一条评论所指出的那样,这是不可逆的 - "What if some are DateTime and others are DateTime2?" - 这就是我的场景:不同的查询需要不同的映射(每个查询只需要一个或另一个)。
我正在使用 higher-voted answer 来回答同一个问题。然而,这种做法也似乎是不可逆的。似乎第一个查询完成时设置的任何值都保留了下来,之后就不能更改了。
以下代码是一个MCVE。如果你 运行 它,你会看到类型是 always 显示为 "datetime",并且值的精度永远不会超过毫秒(正如你所期望的那样对于日期时间);尽管尝试更改映射。然后,您必须注释掉对 "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
,所以我写了这个 class:
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
}
然后我编写了那个 class 的单元测试——一个没有 using
的测试,一个有 using
的测试;我发现单元测试 相互影响 :它们可以单独工作,但是当所有测试都是 运行 时,一个或另一个测试将失败。原因(感谢 Damien 的解释)是 Dapper 的查询缓存。好消息是我认为这很好——单元测试遇到了问题,因为它们使用了相同的查询;但在我的真实代码库中,如果我将特定查询包装在 using
中,那么我总是希望该查询使用该映射。所以基本上这只是我的单元测试的问题,而不是真正使用 class.
您的代码确实正确地更改了类型映射 - 但 dapper 积极缓存查询。
如果您的实际查询在 datetime
和 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);
SqlMapper.PurgeQueryCache();
PerformDapperQuery();
SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime);
SqlMapper.PurgeQueryCache();
PerformDapperQuery();
SqlMapper.AddTypeMap(typeof(DateTime), oldValue);
SqlMapper.PurgeQueryCache();
PerformDapperQuery();
}
对于 LookupDbType
的使用,我认为您可以改用 GetDbType
。