Безопасное удаление записей из справочников
Распространённая ситуация: надо очистить справочник от лишних записей. Когда это записи вроде «test», никаких проблем возникнуть не должно. Но если название не такое «кричащее», появляются сомнения: а нет ли привязки к ID этой записи где-то в скрипте? Добро, если все ID аккуратно сложены в scr_Consts (где им и полагается быть), можно посмотреть туда и выяснить. Но бывает, что они валяются где попало. Даже заглянуть в scr_Consts – дело не быстрое, особенно, когда записей много. А уж искать по всем скриптам – огромная трата времени. К тому же, записи из справочников может удалить и пользователь, а он, порой, и не догадывается, что к ID удаляемой записи привязана какая-то логика. Как же быть? Была мысль сделать служебное поле «запрет удаления записи», но это только оттянет решение проблемы: рано или поздно встанет вопрос, ставить туда галочку, или нет. Да и добавлять такое поле в каждую таблицу каждого справочника, да логику обработки запретов, да приучиться пользоваться новым полем – всё это сложно и громоздко.
Предлагаю другой путь: при удалении записи из таблицы справочника действительно автоматически проверять, не указан ли её ID где-то в скриптах. И если указан – сообщать об этом, запрещая удаление. Положительные стороны такого подхода: быстрая реализация и надёжность. Отрицательная сторона – медленное удаление из справочника. Всё-таки, проверить ВСЕ скрипты на вхождение строки ID не быстро. В среднем выходит около трёх секунд. Однако, практика показала, что это не слишком высокая высокая плата за надёжность.
Вот, что следует сделать:
1. Эту функцию вставить в scr_Utils:
//@АБ(all)
/*
Функция для поиска вхождения строки аргумента в тексте всех скриптов
Возвращает название скрипта, если строка найдена, и '', если такой строки нет
*/
var ServiceCount = Services.InformationsCount;
for (var i = 0; i ServiceCount; i++) {
var Info = Services.Informations(i);
var ServiceTypeCode = Info.ServiceTypeCode;
var USI = new String(Info.USI);
var CaptionUSI = ExtractUSICodeEx(Info.USI);
if (ServiceTypeCode == 'Script'){
try {
if (USI.indexOf('Call Centre') >= 0){
//для call-центра нет лицензии
continue;
}
var Service = Services.GetSingleItemByUSI(CaptionUSI);
var ServiceText = new String(Service.Text);
if (ServiceText.indexOf(FindText) >= 0){
//Если в тексте скрипта найдена строка поиска
//Log.Write(1, 'Строка '+FindText+' найдена в скрипте '+CaptionUSI);
return CaptionUSI;
}
}
catch (e){
//Вдруг ошибки лицензий
}
}
}
return '';
}
2. В scr_WindowUtils вставить в функцию DeleteDataGridRecords(DataGrid)
после строк:
var Message = FormatStr(KeyDataFieldNotAssignedInDataset, Dataset.USI);
throw (Message);
}
вставить:
//(здесь определяется, что это - справочник.
//Критерии: либо сервис в папке 'Dictionaries',
//либо относится к группе таблиц 'TG_DICTIONARY')
var DatasetUSI = new String(Dataset.USI);
var IsDictionaryDataset = ((GetParentTableGroup(Dataset) == 'TG_DICTIONARY')||
(DatasetUSI.indexOf('Dictionaries') > 0));
//
и перед
вместо
вставить:
if (IsDictionaryDataset){
//Если удаляем из справочника
System.BeginProcessing(); //Начало показа
var FindScript = FindTextInService(ID);
System.EndProcessing(); //Конец показа
if (FindScript != ''){
//Если в скриптах используется такой ID, запрещаем удаление
ShowWarningDialog('Удаление невозможно:\nЗапись c ID = \''+
ID+'\'\n используется в скрипте '+FindScript);
DeleteNoPossibility = DeleteNoPossibility + 1;
continue;
} else {
//Запись не найдена - удаляем
Dataset.Delete();
}
} else {
Dataset.Delete();
}
Предложенная проверка не работает для идентификаторов, "зашитых" в параметры SelectQuery. Если есть опасность удалить такую запись, то стоит написать функцию, аналогичную описанной выше FindTextInService(FindText), но для поиска в параметрах всех запросов. Принцип тот же.
"Будак Анатолий Васильевич" написал:catch (e){
//Вдруг ошибки лицензий
}
Предлагаю все же не делать проверку на ошибки лицензий через перехват исключений. Это может привести к неприятным последствиям в случае возникновения других типов ошибок. Лучше воспользоваться System.GetHasLicense.
Согласен насчет актуальности данной проблемы. Но по моему опыту часто возникает необходимость в такой проверке тогда, когда производится очистка базы. Мое предложение - хранить все константы в таблице констант. Один раз при запуске системы объявлять эти константы непосредственно в скрипте scr_Consts так, например:
function InitVars() { var ConstsDataset = Services.GetNewItemByUSI('ds_Consts'); ConstsDataset.FetchRecordsCount = -1; ConstsDataset.Open(); var Code, ID; while (!ConstsDataset.IsEOF) { ID = ConstsDataset.Values('ID'); Code = ConstsDataset.Values('Code'); eval(FormatStr('%1 = \'%2\';', Code, ID)); ConstsDataset.GotoNext(); }; ConstsDataset.Close(); };
А в самих таблицах создавать триггерок с маленьким запросом EXISTS из этой самой таблицы.
Преимущества такого подхода:
1. Целостность на уровне СУБД;
2. Часто в хранимых ф-ях и процедурах, триггерах, вьюхах так же используются константы. Можно будет эти значения не задавать, а делать запрос из этой таблицы;
3. Удобный просмотр (с сортировками, группировками), а также добавление новых констант (без возможности продублировать айдишник, как это часто бывает в проектах);
4. Скорость удаления записей практически не пострадает.
Артем сделал неплохое предложение, но я бы хотел немного дополнить его.
Для хранения такой информации я бы посоветовал использовать механизм системеых настроек. Он именно для даких целей и предназначен. Просто необходимо заводить все необходимые данные (в том числе константы) именно в него, а при удалении записей делать анализ именно этого набора данных.