• Программинг

Нужны источники бесперебойного питания?

Источники бесперебойного питания от дизельстор

C# Реализация UPDATE для SQLite. Часть 4

Вот и заканчиваем писать шаблон класса bdFacade. Подошла очередь и для операции UPDATE. Кто не читал предыдущие статьи из этой серии, рекомендую, для начала, прочитать первые три части – часть 1, часть 2 и часть 3. Когда я только начинал писать шаблон для операции обновления данных в различных своих программах, у меня было много идей, как требуется реализовать метод, который будет универсальным. Хотя, то что вы увидите сегодня, не является универсальным на 100%. После этой статьи у нас будет готовый класс, который позволит легко работать с базой данных в наших программах.
Реализация

Для изменения данных будет только один метод, но навороченный. Назовем его нетривиально Update. Можно, конечно писать кучу перегружаемых братьев, но я не вижу в этом смысла. Если запрос к базе данных будет сложным, то для него требуется писать отдельный метод.

Перед тем как писать основной метод, предлагаю рассмотреть как в C# реализовуются исключения, определенный пользователем. Так вы научитесь еще и писать свои обработчики исключений.

1 class ExceptionWarning : Exception
2 {
3     private string _messageText;
4  
5     public string MessageText
6     {
7         get { return _messageText; }
8     }
9  
10     public ExceptionWarning(string messagetext)
11         : base()
12     {
13         _messageText = messagetext;
14     }
15 }

Как видите, это обычный класс, который наследуется от системного класса Exception. Мы задали переменную _messageText, в которой будет храниться текст сообщения. Аксессор MessageText задает свойство и позволяет использовать переменную только для чтения. Далее конструктор задает значение нашей переменной. Класс написан. Можете добавить еще переменных и перегруженных конструкторов.

Займемся методом UPDATE. Смотрим код:

1 public void Update(string tablename, ParametersCollection collection, object[] whereparams, string whereseparator)
2 {
3     ConnectionState previousConnectionState = ConnectionState.Closed;
4     using (SQLiteConnection connect = new SQLiteConnection(ConnectionString))
5     {
6         try
7         {
8             //проверяем переданные аргументы
9             if (whereparams.Length == 0) throw (new ExceptionWarning("Ошибка! Не указано ни одно условие"));
10             if (whereparams.Length > 0 && whereseparator.Trim().Length == 0) throw (new ExceptionWarning("При использовании нескольких условий, требуется указать разделитель OR или AND"));
11  
12             previousConnectionState = connect.State;
13             if (connect.State == ConnectionState.Closed)
14             {
15                 connect.Open();
16             }
17  
18             int i = 0;
19             //готовим переменную для сбора полей и их значений
20             string sql_params = string.Empty;
21             bool ifFirst = true;
22             SQLiteCommand command = new SQLiteCommand(connect);
23             //в цикле создаем строку запроса
24             foreach (Parameter param in collection)
25             {
26                 if (ifFirst)
27                 {
28                     sql_params = param.ColumnName + " = @param" + i;
29                     ifFirst = false;
30                 }
31                 else
32                 {
33                     sql_params += "," + param.ColumnName + " = @param" + i;
34                 }
35                 //и добавляем параметры с таким же названием
36                 command.Parameters.Add("@param" + i, param.DbType).Value = param.Value;
37                 i++;
38             }
39  
40             //условия для запроса
41             string sql_where = string.Empty;
42             ifFirst = true;
43             //собираем строку с условиями
44             foreach (object item in whereparams)
45             {
46                 if (ifFirst)
47                 {
48                     sql_where = item.ToString();
49                     ifFirst = false;
50                 }
51                 else
52                 {
53                     sql_where += " " + whereseparator + " " + item;
54                 }
55             }
56             sql_where = "WHERE " + sql_where;
57             //собираем запрос воедино
58             command.CommandText = string.Format("UPDATE {0} SET {1} {2}", tablename, sql_params, sql_where);
59             //выполняем запрос
60             command.ExecuteNonQuery();
61         }
62         catch (ExceptionWarning message)
63         {
64             System.Windows.Forms.MessageBox.Show(message.MessageText, "Ошибка при обновлении данных в таблице " + tablename, MessageBoxButtons.OK, MessageBoxIcon.Warning);
65         }
66         catch (Exception error)
67         {
68             System.Windows.Forms.MessageBox.Show(error.Message, "Ошибка при обновлении данных в таблице " + tablename, MessageBoxButtons.OK, MessageBoxIcon.Error);
69         }
70         finally
71         {
72             if (previousConnectionState == ConnectionState.Closed)
73             {
74                 connect.Close();
75             }
76         }
77     }
78 }

Метод принимает 4 аргумента: 1) имя таблицы, с которой будем работать; 2) коллекцию параметров, с которой мы уже работали, когда писали метод INSERT; 3) массив условий; 4) разделитель между условиями, если их (условий) больше одного.

Строки

1 if (whereparams.Length == 0) throw (new ExceptionWarning("Ошибка! Не указано ни одно условие"));
2 if (whereparams.Length > 0 && whereseparator.Trim().Length == 0) throw (new ExceptionWarning("При использовании нескольких условий, требуется указать разделитель OR или AND"));
3 ...
4 catch (ExceptionWarning message)
5 {
6     System.Windows.Forms.MessageBox.Show(message.MessageText, "Ошибка при обновлении данных в таблице " + tablename, MessageBoxButtons.OK, MessageBoxIcon.Warning);
7 }

проверяют переданные аргументы и вызывают наш класс исключений, передавая в него строку с каким-то текстом.

Далее наша стандартная проверка состояния подключения к базе данных. Следующий цикл foreach готовит строку для замены данных в таблице. Сначала создаем текстовую переменную sql_params и там же в объект SQLiteCommand добавляем данные из коллекции параметров. Также мы делали и в методе вставки данных. Второй foreach генерирует строку для условий. Т.к. теоретически условий для запроса может быть несколько и они будут разделяться операторами AND или OR. Поэтому, если вы передаете несколько условий – задавайте переменную whereseparator. Иначе, будет вызвано исключение.

В итоге строка

1 command.CommandText = string.Format("UPDATE {0} SET {1} {2}", tablename, sql_params, sql_where);

соберет все переменные в одну строку запроса.

Можно было бы возвращать данные об успешности выполнения нашей операции, как это мы делали, когда писали DELETE, но я не хочу усложнять код. Этот метод можно условно разбить на три части для более легкого понимания. Часть в начале и в самом конце отвечают за подключение к базе данных. Часть, которая проверяет переданные аргументы и циклы для сбора и подготовки строки запроса. Ну и пару строк, которые собственно отправляют запрос.

Заключение

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

Хочу еще отметить тот факт, что если поле проиндексировано, его нельзя использовать в запросе, где условия разделены оператором OR. Чтобы вам было понятно приведу пример.

В базе данных есть проиндексированное поле testdate. Код при создании базы следующий:

1 CREATE UNIQUE INDEX indx ON Test (testdate)

Если выполнить запрос

1    UPDATE text SET title = 'простой текст' WHERE id = 1 OR id = 2

будет вызвано исключение с ошибкой Abort due constraint violation column testdate is not unique. Если вместо OR записать AND, все отработает чисто. Правда таких записей не будет найдено, если id это поле с autoincrement.