Принудительное удаление записи с отвязыванием от записей в связанных таблицах
Всем привет!
Все мы знаем, что, если при удалении записи на нее есть ссылки из других таблиц, то система выдает список связей для того, чтобы пользователь попробовал разобраться с этим, где нужно удалил эти связи или вообще отказался от затеи удалять ту самую запись. Но, на мой взгляд, в 90% случаев пользователь уверен, что эту запись точно нужно удалить и разбираться со связями он 100% не хочет. Так почему бы не дать ему возможность просто удалить эту запись, предварительно отвязав ее от остальных объектов автоматически?
Для решения этой задачи был взят за основу скрипт отсюда.
1. Создаем хранимую процедуру под sa или под пользователем с правом sysadmin, которая будет делать отвязку записи и ее удаление:
begin
declare @ColumnName nvarchar(max)
declare @TableName nvarchar(max)
declare @TempSQL nvarchar(max)
declare @RecordValue nvarchar(max)
declare local_cursor cursor LOCAL FOR
SELECT DISTINCT
CONSTRAINT_COLUMN_USAGE.COLUMN_NAME AS COLUMN_NAME,
CONSTRAINT_COLUMN_USAGE.TABLE_NAME AS TABLE_NAME
FROM ((INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS CONSTRAINT_COLUMN_USAGE
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TABLE_CONSTRAINTS
ON CONSTRAINT_COLUMN_USAGE.CONSTRAINT_NAME = TABLE_CONSTRAINTS.CONSTRAINT_NAME)
INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS REFERENTIAL_CONSTRAINTS
ON CONSTRAINT_COLUMN_USAGE.CONSTRAINT_NAME = REFERENTIAL_CONSTRAINTS.CONSTRAINT_NAME)
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS PARENT_TABLES
ON REFERENTIAL_CONSTRAINTS.UNIQUE_CONSTRAINT_NAME = PARENT_TABLES.CONSTRAINT_NAME
WHERE TABLE_CONSTRAINTS.CONSTRAINT_TYPE = 'FOREIGN KEY' AND
PARENT_TABLES.TABLE_NAME = @ParentTableName
IF (isnull(cast(@RecordID AS nvarchar(max)), '') = '')
RETURN
open local_cursor
fetch next FROM local_cursor INTO @ColumnName, @TableName
while @@fetch_Status = 0
begin
SET @TempSQL = 'UPDATE [' + @TableName + '] SET [' + @ColumnName + '] = null' + ' WHERE ['+ @ColumnName + '] = ''' + @RecordID + ''''
exec sp_executesql @TempSQL
fetch next FROM local_cursor INTO @ColumnName, @TableName
end
close local_cursor
deallocate local_cursor
SET @TempSQL = 'delete from [' + @ParentTableName + '] WHERE [ID] = ''' + @RecordID + ''''
exec sp_executesql @TempSQL
end
--grant execute on tsp_UnbindAndDeleteRecord to public
Как вы может быть заметили, процедура создана с "with exec as owner". Добавил это я на всякий случай, если вдруг вы не дадите гранты на использование этой процедуры пользователям.
2. Ищем сервис окна wnd_DeleteRecordsWizard, которое появляется, когда выводится список связанных объектов при удалении записей. Добавляем во фрейм с кнопками свою кнопку btnDeleteForce и даем ей "Заголовок" = "Удалить принудительно".
3. На OnClick этой кнопки "вешаем" наш код, в котором запускаем нашу процедуру из пункта 1, передавая в нее в качестве параметров ID удаляемых записей (у нас их ведь может быть сколько угодно) и имя таблицы, из которой производится удаление.
var Dataset = dlData.Dataset;
if (Dataset.IsEmptyPage) {
return;
}
if (ShowConfirmationDialog("Вы уверены?") != wmrYes) {
return;
}
var TableName = Self.Attributes('TableName');
if (IsEmptyValue(TableName)) {
return;
}
var RecordIDs = Self.Attributes('RecordIDs');
if (!RecordIDs) {
return;
}
var Count = RecordIDs.length;
var RecordID;
try {
System.BeginProcessing();
for (var i = 0; i Count; i++) {
System.ProcessMessages();
RecordID = RecordIDs[i];
var sql = "exec tsp_UnbindAndDeleteRecord '" +
RecordID + "', '" + TableName + "'";
Connector.DBEngine.ExecuteCustomSQL(sql, System.EmptyValue);
}
} finally {
System.EndProcessing();
}
Self.Close();
}
4. Все-таки позволять всем пользователям принудительно удалять запись - дело рискованное. Поэтому предлагаю позволять это делать ответственному человеку, а именно пользователю с правами администратора системы. Для этого в OnPrepare окна wnd_DeleteRecordsWizard добавляем код показа нашей кнопки:
5. Сохраняем сервисы и проверяем.
Если же вы, все-таки, решите позволять такое удаление любому пользователю, то, как понимаете, пункт 4 делать не нужно.
Примечание: Проверялся этот функционал на MS SQL 2008 (для Oracle он вообще не подходит, как Вы можете судить по синтаксису хранимой процедуры), но, думаю, он будет работать и на MS SQL 2005.
Спасибо S.Kalishenko. Полезный функционал. Только я так понял доработка для версии террасофт начиная с 3.3.2 так как в версии 3.3.1 еще нет окна wnd_DeleteRecordsWizard. Я думаю как вариант сделать в окне wnd_BaseGridArea в amDelete еще один пунк "Удалить принудительно" и поправить код следующим образом
var Dataset = dlData.Dataset; if (Dataset.IsEmptyPage) { return; } if (ShowConfirmationDialog("Вы уверены?") != wmrYes) { return; } var TableName = GetTableFromDataset(Dataset); if (IsEmptyValue(TableName)) { return; } var RecordIDs = GetGridSelectedIDsArray(); if (!RecordIDs) { return; } var Count = RecordIDs.length; var RecordID; try { System.BeginProcessing(); for (var i = 0; i < Count; i++) { System.ProcessMessages(); RecordID = RecordIDs[i]; var sql = "exec tsp_UnbindAndDeleteRecord '" + RecordID + "', '" + TableName.SQLName + "'"; Connector.DBEngine.ExecuteCustomSQL(sql, System.EmptyValue); } } finally { System.EndProcessing(); }
а и еще вопрос а возможно сделать деталь в которой бы отображалось в каких таблицах данная запись имеет связанные записи?
"Мещеринов Иван Александрович" написал:Только я так понял доработка для версии террасофт начиная с 3.3.2 так как в версии 3.3.1 еще нет окна wnd_DeleteRecordsWizard.
Да, в 3.3.1 можно реализовать функционал как Вы описали.
"Мещеринов Иван Александрович" написал:а и еще вопрос а возможно сделать деталь в которой бы отображалось в каких таблицах данная запись имеет связанные записи?
Можно, только код я не готов Вам предоставить. Для такой детали можно воспользоваться реализацией получения датасета в wnd_DeleteRecordsWizard из 3.3.2.
Здравствуйте, сообщество.
Сегодня сделал аналогичный функционал для БД Oracle.
Изменяем метод btnDeleteForceOnClick(Control) до следующего вида:
function btnDeleteForceOnClick(Control) { ForceDeleteRecord(dlData.Dataset, Self.Attributes('TableName'), Self.Attributes('RecordIDs')); Self.Close(); }
Добавляем метод ForceDeleteRecord(Dataset, TableName, RecordIDs) в тот же модуль:
function ForceDeleteRecord(Dataset, TableName, RecordIDs) { if (!Dataset || IsEmptyValue(TableName)) { return; } if (Dataset.IsEmptyPage) { return; } if (ShowConfirmationDialog("Вы уверены?") != wmrYes) { return; } if (!RecordIDs) { return; } var Count = RecordIDs.length; var RecordID; try { System.BeginProcessing(); for (var i = 0; i < Count; i++) { System.ProcessMessages(); RecordID = RecordIDs[i]; var sql = ''; switch (Connector.DBExecutor.DBExecutorTypeCode) { case 'MSSQL': var sql = "exec tsp_UnbindAndDeleteRecord '" + RecordID + "', '" + TableName + "'"; break; case 'Oracle': var sql = "begin \"tsp_UnbindAndDeleteRecord\""('"" +