.Net / SQL Вставить и Выбрать на datetime2 не совпадает - PullRequest
1 голос
/ 19 апреля 2019

У меня есть фрагмент кода, который вставляет образец данных ([id + datetime2] + ...) в базу данных sqlAzure.

Позже для каждого вызова я делаю выбор, чтобы узнать, находится ли primaryKey в БД [id + datetime2], поэтому я обновляю его, в противном случае я делаю вставку. Проблема в том, что select ничего не возвращает, но вставка получит ошибку дублированного ключа. (!?)

Я создал образец кода, воспроизводящего мою проблему, не каждый раз, а большую ее часть.

Если я заменю

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

Это просто быстрый пример для воспроизведения поведения, я использую точно такой же параметр для вставки + выбора, но выбор ничего не возвращает. Возможно .net Datetime преобразуется в SqlDateTime для вставки и в SqlDateTime2 для выбора ...

1 Ответ

3 голосов
/ 19 апреля 2019

AddWithValue выводит SqlDbType из поставляемого типа .NET как DateTime, а не DateTime2.Дробные секунды затем усекаются с точностью до 3 и округляются до 1/300 секунды, чтобы соответствовать менее точному типу данных параметра.Это менее точное значение вы увидите, если запросите базу данных без предложения WHERE.

С экспликацией SqlDbType.DateTime2 не происходит усечения / округления, поскольку .NET DateTime и SqlDbType.DateTime2 оба поддерживают доли секундыс точностью до 7.

Это еще одна причина, чтобы избегать AddWithValue.

Смешивание типов datetime / datetime2 также может привести к неожиданному поведению, как показаноВаш SELECT запрос.datetime2 имеет более высокий приоритет типа данных, чем datetime, поэтому значение доли секунды сравнивается с использованием фактического значения 1/300 секунды datetime, расширенного с большей точностью, а не округленного / усеченного.Рассмотрим эти примеры 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

Хотя этим поведением сравнения критическое изменение можно управлять, запустив уровень совместимости базы данных 120 или ниже, это будетЛучше всего просто соответствовать типам 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()
...