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
    { нормальный код }

но почему так ведет себя данный кусок кода (точнее в него переходит) - пока не понятно.