类似于问题How can I get Dapper to map .net DateTime to DateTime2;但我希望以后可以重新设置。
该问题的当前公认答案涉及更改Dapper源文件;但我使用的是NuGet软件包,因此对我不起作用。正如对已接受答案的第一条评论所指出的那样,这是不可逆的-“如果某些是DateTime而其他是DateTime2,该怎么办?” -这是我的情况:不同的查询需要不同的映射(每个查询只需一个即可。
我对同一问题使用higher-voted answer。但是,这种方法也似乎是不可逆的。似乎保留第一个查询时设置的任何值,并且此后不可更改。
以下代码是MCVE。如果运行它,您会看到类型始终是 ,显示为“ datetime”,并且值的精确度永远不会超过毫秒(就像您期望的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}");
}
}
}
所以问题的第一部分是:如何多次更改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
}
}
然后可以将其包装到using块中的Dapper查询,以使该查询使用DateTime2映射:
using (new DapperDateTime2MapperScope())
{
-- Perform Dapper query here
}
然后我写了该类的单元测试-一个没有using
的测试和一个带有using
的测试;我发现单元测试彼此交互:它们可以单独工作,但是当所有测试运行时,一个或另一个测试将失败。原因(由于Damien的解释)是Dapper缓存查询。好消息是,我认为这很好-单元测试由于使用相同的查询而遭受了问题;但是在我的真实代码库中,如果我在此using
中包装一个特定的查询,那么我将始终希望该查询使用该映射。因此,基本上这只是我的单元测试的问题,而不是真正使用该类的问题。
答案 0 :(得分:1)
您的代码确实可以正确更改类型映射-但是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
。
答案 1 :(得分:-1)
您必须使用交易
Func<DbCommand, string, object, DbParameter> CreateParameter = (cmd, parameterName, parameterValue) =>
{
var parameter = cmd.CreateParameter();
parameter.ParameterName = parameterName;
parameter.DbType = DbType.String;
parameter.Direction = ParameterDirection.Input;
parameter.Value = parameterValue;
return parameter;
};
string sql = $"CREATE_RECORD @Application, @UpdatedTime; Select SCOPE_IDENTITY()";
using (DbCommand command = CommandFactory.CreateCommand())
{
try
{
DbParameter[] parameters = new DbParameter[]
{
CreateParameter(command, "@Application", escalation.Application),
CreateParameter(command, "@UpdatedTime", escalation.UpdatedTime)
};
command.CommandType = CommandType.Text;
command.Connection = yourOpenConnection;
command.Transaction = command.Connection.BeginTransaction(IsolationLevel.Serializable);
command.Parameters.AddRange(parameters);
int recID = (int)command.ExecuteScalar();
command.Transaction.Commit();
return recID;
}
catch (Exception ex)
{
command.Transaction.Rollback();
}
}
}
重要的是
command.Transaction = command.Connection.BeginTransaction(IsolationLevel.Serializable);
//...
command.Transaction.Commit();
//...
command.Transaction.Rollback();
如果不提交,则所有查询都将被阻止以选择包含。如果您不想阻止此查询所涉及的数据库对象的读取,则可以使用 IsolationLevel.ReadCommited 。
IsolationLevel.Serializable 阻止与查询相关的任何访问,您的应用程序可能会创建难以解决的死锁。快照是一种很好的隔离模式,没有阻塞,但是需要更改访问selects中的元素的方式,并在每个查询上使用timestamp列,以了解查询是否脏了。
最后,函数CreateParameter只是在查询中创建参数的一种简单方法(这样可以避免 SQL注入)。