#c# #sql-server #.net-core #azure-sql-database
#c# #sql-сервер #.net-core #azure-sql-database
Вопрос:
У меня есть фрагмент кода, который вставляет образец данных ([id datetime2] …) в базу данных SQLAzure.
Позже для каждого вызова я делаю select, чтобы узнать, есть ли PrimaryKey уже в DB [id datetime2], поэтому я обновляю его, в противном случае я делаю insert. Проблема в том, что select ничего не возвращает, но при вставке будет получена ошибка с дублированным ключом. (!?)
Я создал образец кода, воспроизводящий мою проблему, не каждый раз, но большую ее часть.
Если я заменю
command.Parameters.AddWithValue("@Date", date);
Автор:
command.Parameters.Add("@Date", SqlDbType.DateTime2).Value = date;
Это будет работать, но я хотел бы понять, почему команды select и insert не совпадают.
//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 для выбора…
Комментарии:
1. ваши данные в столбце Id вставляют -1 в БД, и его право выбора не работает! правильно ли это указано в идентификаторе столбца в хранилище БД -1?
2. команда. Параметры. AddWithValue(«@Date», значение. Дата); //{19:33:22.7727095} в этой строке что такое ценность?
3. Не используйте
AddWithValue
. Создайте параметр с определенным типом, точностью и задайте его значение.4. @hassan.ef мой плохой, его ‘1’ в БД, проблема, если локализована на дату 😉
5. @AmitYadav опять моя ошибка, ошибка между реальным кодом и образцом кода, значение равно DateTime.Now
Ответ №1:
AddWithValue
выводит SqlDbType
из предоставленного типа .NET как DateTime
, а не DateTime2
. Затем доли секунды усекаются с точностью до 3 и округляются до 1/300 секунды, чтобы соответствовать менее точному типу данных параметра. Вы увидите это менее точное значение, если вы запрашиваете базу данных без WHERE
предложения.
Усечение / округление не происходит с explict SqlDbType.DateTime2
, поскольку .NET DateTime
и SqlDbType.DateTime2
оба поддерживают дробные секунды с точностью до 7.
Это еще одна причина, которой следует избегать AddWithValue
.
Смешивание типов datetime / datetime2 также может привести к неожиданному поведению, как показано в вашем SELECT
запросе. datetime2
имеет более высокий приоритет типа данных, чем datetime, поэтому значение доли секунды сравнивается с использованием 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
Хотя это изменение, нарушающее поведение сравнения, можно контролировать, запустив на уровне совместимости с базой данных 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()
Комментарии:
1. Конечно, как я уже сказал, если я использую command. Parameters.Add(«@Date», SqlDbType.DateTime2).Value = date; Работает нормально, но я не понимаю, как использование одного и того же Sql.Parameter (добавленного с помощью AddParamerWithValue ) может дать другой результат, используемый в команде выбора и вставки…
2. @CarlosBarroso, я добавлю к своему ответу, чтобы объяснить, почему неожиданные результаты с тем же значением параметра.
3. Я прекрасно понимаю ваш пример, но на самом деле это не мой вариант использования. Чтобы возобновить
command.Parameters.AddWithValue("@Date", Datetime.Now); command.CommandText = "INSERT INTO Answer (Date) VALUES (@Date)"; command.ExecuteNonQuery(); command.CommandText = "SELECT TOP 1 Id FROM Answer where Date = @Date;"; command.ExecuteScalar(); //return null
ту же базу данных, ту же таблицу, тот же тип, тот же параметр, разное поведение между выбором и вставкой.4. @CarlosBarroso, ваш случай точно такой же, как в примере с T-SQL, но с
WHERE
предложением вместоIF
оператора. Я добавил пример .NET, чтобы проиллюстрировать, что поведение сравнения такое же. Вы упомянули тот же тип, но тип отличается (SqlDbType.DateTime вместо SqlDbType.DateTime2).5. В вашем TSQL вы сравниваете SqlDateTime с sqlDatetime2, поэтому ожидается сбой, ничего общего с моим поведением. Но ваш пример .net отлично воспроизводит поведение, я все еще не понимаю, почему при вставке будет использоваться SqlDateTime2, а в Where используется округленное SqlDateTime, это не имеет смысла 🙂