Вот и заканчиваем писать шаблон класса bdFacade. Подошла очередь и для операции UPDATE. Кто не читал предыдущие статьи из этой серии, рекомендую, для начала, прочитать первые три части – часть 1, часть 2 и часть 3. Когда я только начинал писать шаблон для операции обновления данных в различных своих программах, у меня было много идей, как требуется реализовать метод, который будет универсальным. Хотя, то что вы увидите сегодня, не является универсальным на 100%. После этой статьи у нас будет готовый класс, который позволит легко работать с базой данных в наших программах.
Реализация
Для изменения данных будет только один метод, но навороченный. Назовем его нетривиально Update. Можно, конечно писать кучу перегружаемых братьев, но я не вижу в этом смысла. Если запрос к базе данных будет сложным, то для него требуется писать отдельный метод.
Перед тем как писать основной метод, предлагаю рассмотреть как в C# реализовуются исключения, определенный пользователем. Так вы научитесь еще и писать свои обработчики исключений.
1 |
class ExceptionWarning : Exception |
3 |
private string _messageText; |
5 |
public string MessageText |
7 |
get { return _messageText; } |
10 |
public ExceptionWarning(string messagetext) |
13 |
_messageText = messagetext; |
Как видите, это обычный класс, который наследуется от системного класса Exception. Мы задали переменную _messageText, в которой будет храниться текст сообщения. Аксессор MessageText задает свойство и позволяет использовать переменную только для чтения. Далее конструктор задает значение нашей переменной. Класс написан. Можете добавить еще переменных и перегруженных конструкторов.
Займемся методом UPDATE. Смотрим код:
1 |
public void Update(string tablename, ParametersCollection collection, object[] whereparams, string whereseparator) |
3 |
ConnectionState previousConnectionState = ConnectionState.Closed; |
4 |
using (SQLiteConnection connect = new SQLiteConnection(ConnectionString)) |
9 |
if (whereparams.Length == 0) throw (new ExceptionWarning("Ошибка! Не указано ни одно условие")); |
10 |
if (whereparams.Length > 0 && whereseparator.Trim().Length == 0) throw (new ExceptionWarning("При использовании нескольких условий, требуется указать разделитель OR или AND")); |
12 |
previousConnectionState = connect.State; |
13 |
if (connect.State == ConnectionState.Closed) |
20 |
string sql_params = string.Empty; |
22 |
SQLiteCommand command = new SQLiteCommand(connect); |
24 |
foreach (Parameter param in collection) |
28 |
sql_params = param.ColumnName + " = @param" + i; |
33 |
sql_params += "," + param.ColumnName + " = @param" + i; |
36 |
command.Parameters.Add("@param" + i, param.DbType).Value = param.Value; |
41 |
string sql_where = string.Empty; |
44 |
foreach (object item in whereparams) |
48 |
sql_where = item.ToString(); |
53 |
sql_where += " " + whereseparator + " " + item; |
56 |
sql_where = "WHERE " + sql_where; |
58 |
command.CommandText = string.Format("UPDATE {0} SET {1} {2}", tablename, sql_params, sql_where); |
60 |
command.ExecuteNonQuery(); |
62 |
catch (ExceptionWarning message) |
64 |
System.Windows.Forms.MessageBox.Show(message.MessageText, "Ошибка при обновлении данных в таблице " + tablename, MessageBoxButtons.OK, MessageBoxIcon.Warning); |
66 |
catch (Exception error) |
68 |
System.Windows.Forms.MessageBox.Show(error.Message, "Ошибка при обновлении данных в таблице " + tablename, MessageBoxButtons.OK, MessageBoxIcon.Error); |
72 |
if (previousConnectionState == ConnectionState.Closed) |
Метод принимает 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")); |
4 |
catch (ExceptionWarning message) |
6 |
System.Windows.Forms.MessageBox.Show(message.MessageText, "Ошибка при обновлении данных в таблице " + tablename, MessageBoxButtons.OK, MessageBoxIcon.Warning); |
проверяют переданные аргументы и вызывают наш класс исключений, передавая в него строку с каким-то текстом.
Далее наша стандартная проверка состояния подключения к базе данных. Следующий цикл 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.