Способы каскадного удаления объектов, их плюсы и минусы

Задача каскадного удаления

Пусть дана следующая диаграмма:

Если в базе данных есть объекты типа Клиент, ссылающиеся на Адрес, то без дополнительных настроек при попытке удаления объекта типа Адрес произойдёт ошибка. База данных не даст удалить такой объект.

Варианты решения проблемы

Вариантов может быть очень много, в данной статье будут приведено только несколько. Технология предоставляет механизмы для решения проблемы (в основном они опираются на использование бизнес-серверов), варианты ограничиваются лишь фантазией разработчика.

Специальные интерфейсы

Для реализации каскадного удаления можно воспользоваться специально разработанными интерфейсами IReferencesCascadeDelete и IReferencesNullDelete.

Рекурсивное удаление

Это самый простой вариант, но и самый недружелюбный к пользователю: удаление 1 объекта может привести к удалению важной информации информации, связанной с данным объектом.

Алгоритм:

  • В бизнес-сервере мастера (в примере - Адрес) вычитать все объекты, ссылающиеся на удаляемый.
  • Проставить всем объектам статус ObjectStatus.Deleted.
  • Отправить на удаление все объекты.
  • Повторить рекурсивно для всех объектов.

Фиктивный объект

Такой вариант позволяет сохранить все данные, кроме того объекта, который необходимо удалить. Однако в базе останется множество объектов, ссылающихся на несуществующий.

Стоит также отметить, что данный способ требует дополнительной обработки данных при выводе пользователю. Объекты, ссылающиеся на фиктивные, необходимо фильтровать или обрабатывать особым образом.

Вариантов решения проблемы несколько:

  • создавать фиктивный объект при каждом удалении
  • создать по 1 фиктивному объекту для каждого класса и “вешать” все ссылки на него.

Алгоритм для второго варианта:

  • (один раз) Создать объект и записать его в базу. Запоминить его PrimaryKey, например, в файле конфигурации или в файле с константами.
  • В бизнес-сервере мастера (в примере - Адрес) вычитать все объекты, ссылающиеся на удаляемый.
  • Проставить всем объектам ссылку на фиктивный объект.
  • Отправить на обновление все объекты.

Фиктивное удаление

При фиктивном удалении данные на самом деле не удаляются из базы, а всего лишь помечаются как удаленные. Во все объекты добавляется какое-нибудь поле типа bool. При удалении объекта в бизнес-сервере перехватывается объект, у него меняется статус с Deleted на Altered и изменяется поле Актуально = false;.

После этого объект уходит на обновление в базу и остается в ней, но считается удаленным. Разумеется, необходимо реализовывать логику, которая будет “считать” такие объекты удаленными: при выводе информации пользователю необходимо накладывать ограничения на выводимые данные.

Пример

Необходимо доработать диаграмму классов таким образом, чтобы она поддерживала фиктивное удаление: добавить поле Актуально:bool.

Добавить логику в бизнес-сервера объектов (на примере Адреса):

if (UpdatedObject.GetStatus() == ObjectStatus.Deleted)
{
	// Не дадим объекту удалиться, но выставим флаг Актуальности.
	UpdatedObject.SetStatus(ObjectStatus.Altered);
	UpdatedObject.Актуально = false;

	// Найдем все объекты, ссылающиеся на "удаляемый" и удалим их.
	var ds = (SQLDataService)DataServiceProvider.DataService;
	var klients =
		ds.Query<Клиент>(Клиент.Views.КлиентE)
		  .Where(k => k.Прописка.__PrimaryKey == UpdatedObject.__PrimaryKey);
	foreach (var k in klients)
	{
		k.SetStatus(ObjectStatus.Deleted);
	}

	return klients.ToArray();
}

Далее, чтобы пользователю не выводились “удаленные” данные при просмотре списка объектов, требуется на соответствующий контрол наложить ограничение вида:

var ds = (MSSQLDataService)DataServiceProvider.DataService;
IQueryable<Клиент> limit1 = ds.Query<Адрес>(Адрес.Views.АдресL).Where(Address => Address.Актуально);
Function onlyActual = LinqToLcs.GetLcs(limit1.Expression, Адрес.Views.АдресL).LimitFunction;