C#, MS SQL Server и неверное время
Jan 10, 23Январские каникулы
Новый год наступил и в первую “выходную” неделю начались уже проблемы. Странный “баг” просачивался один раз на другой системе, но это было всего один раз. В определенный момент в базу пишется неверная дата, точнее идет перестановка дня и месяца местами. То есть было 04-01-2023, а стало 01-04-2023. Благо сообщений в базу “насыпало” немного, поэтому починили в базе всё руками. Но баг остался же - что делать?
Поиск бага
Программное обеспечение, которое собирала информацию с контроллера в БД, было написано на подрядчиком C# в году так 2016-2017. Я выяснил, что за работу в базе данных отвечает DLL-ка, в которой и находится неверно работающий код. Опять использую dnSpy для просмотра внутренностей, благо обфуксации кода нет.
Функция WriteOperMess:
// Token: 0x060000A7 RID: 167 RVA: 0x0000544C File Offset: 0x0000364C
public void WriteOperMess(int MessKod, string Message)
{
try
{
int num = 0;
int num2 = 1;
SqlDb sqlDb = new SqlDb();
sqlDb.Open();
sqlDb.GetFieldType("PLCMESSAGE", "DTIME");
sqlDb.CreateDbCommand("SELECT TOP(1) ID, isAck, Priority FROM Message Where Sysid = 250 AND Mess = " + MessKod + " ORDER BY ID DESC");
sqlDb.ExecuteReader();
while (sqlDb.Reader.Read())
{
num = Convert.ToInt32(sqlDb.Reader["isAck"]);
num2 = Convert.ToInt32(sqlDb.Reader["Priority"]);
}
sqlDb.Reader.Close();
sqlDb.CreateDbCommand("INSERT INTO PLCMessage (IDmess, Place, DTime, dTimeAck, SysID, SysNum, Mess, Message, IsAck, Priority, Value) VALUES (@IDmess, @Place, @DTime, @dTimeAck, @SysID, @SysNum, @Mess, @Message, @IsAck, @Priority, @Value)");
sqlDb.Command.Parameters.Add("@IDmess", SqlDbType.Int).Value = -1;
sqlDb.Command.Parameters.Add("@Place", SqlDbType.VarChar).Value = Environment.MachineName;
if (sqlDb.UseDateTimeInPlcMessage)
{
DateTime dateTime = DateTime.Now;
dateTime = dateTime.AddMilliseconds((double)(-(double)dateTime.Millisecond));
sqlDb.Command.Parameters.Add("@DTime", SqlDbType.DateTime2).Value = dateTime;
}
else
{
string text;
if (DateTime.Now.Second <= 9)
{
text = "0" + Convert.ToString(DateTime.Now.Second);
}
else
{
text = Convert.ToString(DateTime.Now.Second);
}
string text2;
if (DateTime.Now.Minute <= 9)
{
text2 = "0" + Convert.ToString(DateTime.Now.Minute);
}
else
{
text2 = Convert.ToString(DateTime.Now.Minute);
}
string text3;
if (DateTime.Now.Hour <= 9)
{
text3 = "0" + Convert.ToString(DateTime.Now.Hour);
}
else
{
text3 = Convert.ToString(DateTime.Now.Hour);
}
string text4 = Convert.ToString(DateTime.Now.Date);
string value = string.Concat(new string[]
{
text4.Substring(0, 10),
" ",
text3,
":",
text2,
":",
text
});
sqlDb.Command.Parameters.Add("@DTime", SqlDbType.VarChar).Value = value;
}
sqlDb.Command.Parameters.Add("@dTimeAck", SqlDbType.VarChar).Value = ((num == 1) ? "Квитировать" : "");
sqlDb.Command.Parameters.Add("@SysID", SqlDbType.Int).Value = 250;
sqlDb.Command.Parameters.Add("@SysNum", SqlDbType.Int).Value = 1;
sqlDb.Command.Parameters.Add("@Mess", SqlDbType.Int).Value = MessKod;
sqlDb.Command.Parameters.Add("@Message", SqlDbType.VarChar).Value = Message;
sqlDb.Command.Parameters.Add("@IsAck", SqlDbType.Int).Value = num;
sqlDb.Command.Parameters.Add("@Priority", SqlDbType.Int).Value = num2;
sqlDb.Command.Parameters.Add("@Value", SqlDbType.VarChar).Value = "";
sqlDb.Command.ExecuteNonQuery();
sqlDb.Close();
}
catch (Exception exception)
{
Log.WriteLogToFile(exception);
}
}
Получается, если флаг UseDateTimeInPlcMessage не в True, тогда выполняется невполне корректный код, который из-за языковых настроек системы дает разные результаты. Поэтому возник вопрос в формировании флага UseDateTimeInPlcMessage - как он возникает.
За это отвечает функция SqlDb.FieldType GetFieldType:
public SqlDb.FieldType GetFieldType(string table = "PLCMESSAGE", string field = "DTIME")
{
SqlDb.FieldType fieldType = SqlDb.FieldType.Nvarchar;
if (this.SqlConnected())
{
try
{
this.CreateDbCommand(string.Concat(new string[]
{
"SELECT C.NAME 'COLUMN_NAME', C.IS_NULLABLE, C.USER_TYPE_ID, T.NAME 'TYPE_NAME' FROM SYS.COLUMNS C LEFT JOIN SYS.TYPES T ON T.USER_TYPE_ID = C.USER_TYPE_ID WHERE C.OBJECT_ID = OBJECT_ID('",
table,
"') AND C.NAME = '",
field,
"'"
}));
this.ExecuteReader();
if (this.Reader.HasRows && this.Reader.Read())
{
fieldType = (SqlDb.FieldType)Convert.ToInt32(this.Reader["USER_TYPE_ID"].ToString());
}
this.Reader.Close();
}
catch (Exception)
{
}
}
this.UseDateTimeInPlcMessage = (fieldType == SqlDb.FieldType.Datetime || fieldType == SqlDb.FieldType.Datetime2);
return fieldType;
}
Судя по коду, может быть условие когда нет коннекта с базой, но тогда бы и сообщение бы не записалось тоже (а оно в базу записывается).
Понятно, что для решения задачи надо будет поменять после
if (sqlDb.UseDateTimeInPlcMessage)
{ }
else
{ нормальный код }
но почему так ведет себя данный кусок кода (точнее в него переходит) - пока не понятно.