我有一段代码在sqlAzure数据库中插入一个数据样本([id + datetime2] + ...)。
以后每次调用时,我都会进行一次选择,以了解primaryKey是否已在数据库[id + datetime2]中,因此我对其进行了更新,否则进行插入。 问题是,选择不返回任何内容,但插入将得到重复的键错误。 (!?)
我创建了一个代码示例,该代码可以重现我的问题,而并非每次都可以,但是大部分都是如此。
如果我替换
command.Parameters.AddWithValue("@Date", date);
作者
command.Parameters.Add("@Date", SqlDbType.DateTime2).Value = date;
它可以工作,但是我想了解为什么选择和插入命令不匹配。
//CODE - BEGIN
//.NetCore 2.2
//SqlDatabase Azure
var date = DateTime.Now;
command.Parameters.AddWithValue("@Id", 1);
command.Parameters.AddWithValue("@Date", Date); //{19:33:22.7727095}
command.CommandText = "INSERT INTO Answer (Id , Date) VALUES (@Id, @Date)";
command.ExecuteNonQuery();
/*DB
**IdDevice Date
**1 2019-04-18 19:33:22.7733333
*/
//Retry
command.CommandText = "SELECT TOP 1 Id FROM Answer where Id = @Id AND Date = @Date;";
var exist = command.ExecuteScalar();
if (exist == null)
{
throw;
}
//CODE - END
这只是一个复制行为的快速示例,我对insert + select使用了完全相同的参数,但是select将不会返回任何内容。 也许将.net Datetime转换为SqlDateTime进行插入,并在SqlDateTime2中进行选择...
答案 0 :(得分:3)
AddWithValue
从提供的.NET类型中将SqlDbType
推断为DateTime
而不是DateTime2
。然后,小数秒将被截断为3的精度,并舍入为1/300秒以匹配不太精确的参数数据类型。如果查询不带WHERE
子句的数据库,您将看到这个不太精确的值。
显式SqlDbType.DateTime2
不会发生截断/舍入,因为.NET DateTime
和SqlDbType.DateTime2
都支持小数秒,精度最高为7。
这是avoid AddWithValue
的另一个原因。
混合datetime / datetime2类型也会导致意外行为,如SELECT
查询所示。 datetime2
的数据类型优先级比日期时间高,因此使用扩展到更高精度的datetime
实际1/300秒值(而不是四舍五入后的值)来比较小数秒值。考虑以下T-SQL示例:
--these values compare not equal because the datetime value of 1/300 is actually .003333333333...
DECLARE @datetime datetime = '2019-04-19T00:00:00.003';
DECLARE @datetime2 datetime2 = '2019-04-19T00:00:00.003';
IF @datetime = @datetime2 PRINT 'EQUAL' ELSE PRINT 'NOT EQUAL';
GO
--these values compare not equal because the datetime value is actually .006666666666...
DECLARE @datetime datetime = '2019-04-19T00:00:00.007';
DECLARE @datetime2 datetime2 = '2019-04-19T00:00:00.007';
IF @datetime = @datetime2 PRINT 'EQUAL' ELSE PRINT 'NOT EQUAL';
GO
--these values comare equal because the datetime value is .010000000000...
DECLARE @datetime datetime = '2019-04-19T00:00:00.010';
DECLARE @datetime2 datetime2 = '2019-04-19T00:00:00.010';
IF @datetime = @datetime2 PRINT 'EQUAL' ELSE PRINT 'NOT EQUAL';
GO
尽管可以通过在breaking change 120或更低版本中运行来控制此比较行为database compatibility level,但最好仅匹配SQL类型。这将为您的代码提供最佳性能并为将来提供证明。
编辑:
使用不匹配类型的.NET参数可以证明相同的行为。下面是一个PowerShell示例。
$connectionString = "Data Source=.;Initial Catalog=tempdb;Integrated Security=SSPI"
$connection = New-Object System.Data.SqlClient.SqlConnection($connectionString)
$connection.Open()
$command = New-Object System.Data.SqlClient.SqlCommand("CREATE TABLE dbo.Answer (Id int NOT NULL, Date datetime2 NOT NULL);", $connection)
[void]$command.ExecuteNonQuery()
$command.CommandText = "INSERT INTO dbo.Answer (Id, Date) VALUES (@Id, @Date);"
[void]$command.Parameters.AddWithValue("@Id", 1)
[void]$command.Parameters.AddWithValue("@Date", [DateTime]::Parse("2019-04-19T00:00:00.003"))
[void]$command.ExecuteNonQuery()
$command.CommandText = "SELECT TOP 1 Id FROM Answer where Id = @Id AND Date = @Date;"
$exists = $command.ExecuteScalar()
# not exists
if($exists -ne $null) { Write-Host "exists" } else { Write-Host "not exists" }
$command.CommandText = "INSERT INTO dbo.Answer (Id, Date) VALUES (@Id, @Date);"
$command.Parameters["@Id"].Value = 2
$command.Parameters["@Date"].Value = [DateTime]::Parse("2019-04-19T00:00:00.007")
[void]$command.ExecuteNonQuery()
$command.CommandText = "SELECT TOP 1 Id FROM Answer where Id = @Id AND Date = @Date;"
$exists = $command.ExecuteScalar()
# not exists
if($exists -ne $null) { Write-Host "exists" } else { Write-Host "not exists" }
$command.CommandText = "INSERT INTO dbo.Answer (Id, Date) VALUES (@Id, @Date);"
$command.Parameters["@Id"].Value = 3
$command.Parameters["@Date"].Value = [DateTime]::Parse("2019-04-19T00:00:00.010")
[void]$command.ExecuteNonQuery()
$command.CommandText = "SELECT TOP 1 Id FROM Answer where Id = @Id AND Date = @Date;"
$exists = $command.ExecuteScalar()
# exists
if($exists -ne $null) { Write-Host "exists" } else { Write-Host "not exists" }
$connection.Close()