Что такое каскадное удаление

Каскадное удаление данных

в мире реляционных баз данных позволяет удалять связанные данные из зависимой таблицы, при удалении данных из основной таблицы. В случае модели, которую мы использовали в предыдущих примерах (две связанные таблицы Customer и Order), при использовании каскадного удаления, удаление данных покупателя будет вести к удалению всех связанных с ним заказов. В SQL Server и T-SQL каскадное удаление реализовано в виде опций ON DELETE CASCADE и ON UPDATE CASCADE, которые указываются при объявлении внешнего ключа таблицы.

По умолчанию Code-First включает каскадное удаление для внешних ключей, не поддерживающих значение NULL, используя соответствующий SQL-код при создании таблицы. В предыдущей статье мы описали, как указать Code-First на то, что внешний ключ должен обязательно использоваться (т.е. поддерживать ограничение NOT NULL). Давайте вспомним, как это сделать:

Можно явно указать свойство внешнего ключа, тогда Code-First по умолчанию использует для него значение NOT NULL в базе данных. В примерах мы использовали внешние ключи CustomerId и UserId.

Если внешний ключ не указан в классе модели, тогда Code-First автоматически генерирует его, разрешая использовать NULL. Чтобы это изменить, можно использовать атрибут Required к навигационному свойству модели.

Давайте рассмотрим пример использования каскадного удаления на примере нашего тестового проекта ASP.NET. Для этого добавим новую веб-форму CascadeDelete.aspx и добавим следующий код:

В этой форме используются две кнопки для удаления и сохранения данных. В коде обработчика Save_Click происходит создание произвольного объекта Customer с тремя связанными объектами Order, после чего эти данные вставляются в базу. В коде обработчика Delete_Click мы сначала извлекаем данные нужного заказчика из базы данных, а затем удаляем его. Обратите внимание, что здесь используется «жадная загрузка» (eager loading), т.к. мы вызываем метод Include(). Это означает, что помимо данных покупателя, будут извлечены все данные связанных с ним заказов. Фактически каскадное удаление в данном случае не нужно, т.к. мы уже извлекли все связанные заказы.

Модель данных на текущий момент выглядит следующим образом:

Запустите наш пример и откройте в браузере веб-форму CascadeDelete.aspx и щелкните по кнопке “Сохранить”. Entity Framework воссоздаст базу данных (если модель изменилась) и добавит новые данные в таблицы Customers и Orders. Чтобы в этом убедиться, используйте средства Visual Studio или SQL Server Management Studio для просмотра данных:

Что такое каскадное удаление. Смотреть фото Что такое каскадное удаление. Смотреть картинку Что такое каскадное удаление. Картинка про Что такое каскадное удаление. Фото Что такое каскадное удаление

Нажмите на кнопку “Удалить”, чтобы убедиться, что данные покупателя и связанные с ним заказы удаляются корректно. При этом Entity Framework отправит четыре запроса DELETE базе данных (три для каждого заказа и один для покупателя). Давайте теперь отключим использование жадной загрузки и явно используем каскадное удаление. Ниже показан измененный код обработчика Delete_Click:

Здесь мы удалили вызов метода Include() и теперь Code-First не известно о связанных с покупателем заказов. В отличие от предыдущего примера, здесь Entity Framework отправит один запрос DELETE для удаления покупателя. При выполнении этого запроса сработает средство каскадного удаления и SQL Server найдет связанные заказы, удалит сначала их, а уже потом удалит покупателя.

Отключение каскадного удаления данных

Возможно вам понадобиться отключить использование каскадного удаления в базе данных. Как описывалось выше, чтобы сделать это, можно удалить явное определение первичного ключа из класса модели и положиться на автоматическую генерацию первичного ключа с помощью Code-First (при этом Code-First указывает поддержку NULL для этого ключа). Также можно воспользоваться средствами Fluent API для явного отключения каскадного удаления, если, например, требуется сохранить объявление первичного ключа в классе модели.

Важно помнить, что при отключении каскадного удаления в вашем приложении могут возникать ошибки, если вы не позаботитесь об извлечении связанных данных перед удалением, как мы это делали в первом примере с использованием “жадной загрузки”.

Отключить или включить каскадное удаление в Fluent API позволяет метод WillCascadeOnDelete(), которому передается логический параметр. Использование этого метода показано в примере ниже:

Если вы запустите приложение и попробуете удалить данные, используя второй пример обработчика Delete_Click, то возникнет исключение, показанное на рисунке ниже:

Что такое каскадное удаление. Смотреть фото Что такое каскадное удаление. Смотреть картинку Что такое каскадное удаление. Картинка про Что такое каскадное удаление. Фото Что такое каскадное удаление

Как уже описывалось ранее, при удалении данных из родительской таблицы, необходимо позаботиться об удалении данных из производной таблицы. Мы забыли извлечь данные связанных заказов из таблицы Orders и поэтому SQL Server вернул ошибку при попытке удаления данных только покупателя. Если вы теперь включите “жадную загрузку” с помощью метода Include() в обработчике Delete_Click, то эта ошибка исчезнет, но возникнет новая – как описывалось выше, в этом случае Code-First отправит четыре запроса на удаление и при удалении первого заказа Code-First установит для свойства Order.Customer значение NULL, а т.к. наша модель содержит внешний ключ, который не может иметь значение NULL возникнет ошибка.

Из этого описания можно сделать вывод, что для данного примера отключение каскадного удаления нельзя применить, но тогда возникает вопрос, зачем вообще отменять каскадное удаление? По своему опыту скажу, что отключение каскадного удаления используется в основном при получении циклической ссылки между таблицами в сложных базах данных. Такая ссылка может возникнуть, если между несколькими таблицами используется отношение “родительская-дочерняя” и последняя зависимая таблица неожиданно ссылается на одну из родительских таблиц. Проблема циклических ссылок проявляется не только при удалении данных, а также при их обновлении (операция UPDATE в T-SQL).

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

Источник

Руководство по проектированию реляционных баз данных. Каскадное удаление данных

Дополнение к циклу переведенных статей.
Статьи: 1-3, 4-6, 7-9, 10-13, 14-15

Информация в статье относится к 5-й части руководства.

В комментариях один из пользователей небеспричинно упрекнул в отсутствии информации о каскадном удалении данных. Восполняю пробел. У автора статей нет информации на эту тему, поэтому я написал небольшую статью об этом. Она достаточно логично впишется в указанный цикл.
Для начала, чтобы не было путаницы, стоит сказать, что речь не столько и не только о каскадном удалении данных, а о теме ссылочной целостности и внешних ключах, частью которой и является каскадное удаление данных.

Введение.

Ближе к сути.

О внешних ключах было рассказано в переводах, останавливаться не буду на этом. Расскажу о “спутнике”.

В случае, если не знать теории, следить за связями данных придется самостоятельно. Альтернативным вариантом является возложение этой задачи на базу данных. Что же за, так названное, слежение за связями данных? Чтобы понять, нужен пример.

У нас есть какие-то вещи. Они разбросаны, их много. Мы хотим навести порядок. Порядок – это, зачастую, классификация (категоризация) и опись. Мы хотим порядка, при этом, мы умеем работать с базами данных и не хотим ничего писать на бумаге. Мы записываем все вещи “в столбик”. Далее мы просматриваем список и определяем категории к которым относятся вещи.

Пусть это часть наших вещей, остальные не рассматриваем:

Книга 1, книга 2, книга 3 – это книги, как ни странно.
Компьютерная мышка, клавиатура – это компьютерная периферия.
Ручка, степлер – это канцелярские принадлежности.

Мы создаем две таблицы в базе данных: categories (категории) и stuff(вещи).

1 | книги
2 | компьютерная периферия
3 | канцелярские принадлежности

stuff_id | category_id | name

1 | 1 | книга 1
2 | 1 | книга 2
3 | 1 | книга 3
4 | 2 | компьютерная мышка
5 | 2 | клавиатура
6 | 3 | ручка
7 | 3 | степлер

P.S. Изображения с habrastorage.org не отображаются.

Итого: у нас есть книги, компьютерная периферия, канцелярские принадлежности.

Мы захотели выкинуть или подарить все наши книги, не хотим видеть эти вещи, как категорию, у себя дома, нам нравятся электронные книги. Мы удаляем из таблицы категорий категорию “книги”. При этом, у нас остаются вещи из этой категории в другой таблице, мы ссылаемся на эти категории в таблице вещей. Это и называется нарушением ссылочной целостности. Казалось бы, нет у нас категории, а значит и нет книг, но записи в таблице вещей остались и вещей-то у нас много и в будущем положение дел может повториться и повторится и тогда у нас будет бардак, много лишней информации и все вытекающие последствия как в удобстве работы с нашей информацией, так и в технической части при работе с базой (напр., поиск информации). И тут приходит понимание, что нам нужно работать с двумя таблицами, следить в каких случаях связи могут быть нарушены, сломаны и совершать какие-то телодвижения и тут есть два варианта: самостоятельно делаем это или, вот тут знание – сила, мы может переложить эту головную боль на базу данных.

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

Сломаться связи могут (если говорить “правильным” языком – ссылочная целостность может нарушиться) в следующих случаях:

Средства поддержания ссылочной целостности SQL (скажу сразу, наперед, когда будет нужно – поймете; если говорить про РСУБД MySQL, то использование этих средств вместе с внешними ключами возможно только для таблиц InnoDB; внешние ключи можно искользовать в MyISAM, создавая определенную структуру даных, но тогда вся головная боль по слежению за связями ложится на пользователя) позволяют обрабатывать указанные случаи.

И вот как решаются эти проблемы (в порядке перечисления):

Теперь два последних. Тут положение дел более интересное.

Где необязательные конструкции ON DELETE и ON UPDATE позволяют задать те самые варианты решения проблемы, которые рассмотрены выше. А эти ключевые слова именуют их:

CASCADE – при удалении или обновлении записи в таблице-предке, которая содержит первичный ключ, автоматически удаляются или обновляются записи со ссылками на это значение в таблице-потомке. В нашем случае, если мы удалим категорию, то удалятся и все вещи, относящиеся к этой категории в таблице вещей. Если мы обновим идентификатор у категории, то у вещей, которые ссылались на эту категорию, идентификатор также изменится на новый.
То самое, каскадное, но, как видите, не только удаление.

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

NO ACTION — при удалении или обновлении записи в таблице-предке, которая содержит первичный ключ, в таблице-потомке никаких действий предприниматься не будет.
В нашем случае, если мы удалим или обновим идентификатор категории в таблице категорий, то это никак не повлияет на таблицу вещей.

RESTRICT – если в таблице-потомке есть записи, которые ссылаются на существующий первичный ключ в таблице-потомке, то при удалении или обновлении записи с первичным ключом в таблице-предке возвратится ошибка.
В нашем случае, если мы попробуем обновить или изменить идентификатор категории при том, что есть вещи, относящиеся к этой категории, то мы получим ошибку.

SET DEFAULT – тут понятно из названия, что при удалении или обновлении записи в таблице-предке, которая содержит первичный ключ, в таблице-потомке соответствующим записям будет выставлено значение по умолчанию. Есть одно “НО”. В РСУБД MySQL это ключевое слово не используется.

А теперь вновь – к каскадному удалению данных. Почему именно оно на слуху? Почему спросили про него в первую очередь, не смотря на то, что оно лишь одно из. Наверное, потому, что каскадное удаление данных наиболее частое решение проблемы.

Источник

Каскадное удаление

В Entity Framework Core (EF Core) связи представлены с помощью внешних ключей. Сущность с внешним ключом является дочерней или зависимой в связи. Значение внешнего ключа этой сущности должно соответствовать значению первичного ключа (или альтернативному значению ключа) связанной основной или родительской сущности.

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

Избежать этого нарушения можно двумя способами.

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

Второй вариант допустим для любого типа связи и называется каскадным удалением.

В этом документе описывается каскадное удаление (и удаление потерянных объектов) в контексте обновления базы данных. При этом активно используются концепции, представленные в разделах Отслеживание изменений в EF Core и Изменение внешних ключей и свойств навигации. Прежде чем приступать к изучению этого документа, обязательно ознакомьтесь с ними.

Вы можете запустить и отладить весь код, используемый в этой документации, скачав пример кода из GitHub.

Когда происходит каскадная реакция на события

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

Удаление основной или родительской сущности

По соглашению эта связь настроена как обязательная, поскольку свойство внешнего ключа Post.BlogId не допускает значения NULL. Для обязательных связей по умолчанию используется каскадное удаление. Дополнительные сведения о моделировании связей см. в разделе Связи.

При удалении блога происходит каскадное удаление всех его записей. Пример:

Метод SaveChanges создает следующий код SQL (на примере SQL Server).

Разрыв связи

Вместо удаления блога можно разорвать связь между ним и каждой его записью. Это можно сделать, установив значение NULL для свойства навигации по ссылке Post.Blog для каждой записи:

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

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

Концепции каскадного удаления и удаления потерянных объектов тесно взаимосвязаны. В обоих случаях при разрыве связи с основной или родительской сущностью ее зависимые и дочерние сущности удаляются. При каскадном удалении это происходит вследствие удаления самой основной или родительской сущности. При удалении потерянных объектов основная или родительская сущность по-прежнему существует, но при этом больше не связана с зависимыми и дочерними сущностями.

Где происходит каскадная реакция на события

Каскадная реакция на события может применяться к следующим сущностям.

Каскадное удаление отслеживаемых сущностей

EF Core всегда применяет к отслеживаемым сущностям каскадную реакцию на события. Это значит, что, если приложение загружает все соответствующие зависимые или дочерние сущности в DbContext (как показано в приведенных выше примерах), каскадная реакция на события будет корректно применяться, независимо конфигурации базы данных.

Точное время, когда происходят каскадные реакции для отслеживаемых сущностей, можно контролировать с помощью ChangeTracker.CascadeDeleteTiming и ChangeTracker.DeleteOrphansTiming. Дополнительные сведения см. в разделе Изменение внешних ключей и свойств навигации.

Каскадное удаление в базе данных

Многие системы баз данных также реализуют каскадную реакцию на события, которая активируется при удалении сущности в базе данных. EF Core определяет конфигурацию такой реакции на основе настроек каскадного удаления в модели EF Core при создании базы данных с использованием метода EnsureCreated или миграций EF Core. Например, в представленной выше модели при использовании SQL Server для записей создается следующая таблица.

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

Как правило, базы данных не поддерживают автоматическое удаление потерянных объектов. Это связано с тем, что в базах данных используются только внешние ключи и нет свойств навигации, тогда как в EF Core связи представляются с использованием и внешних ключей, и свойств навигации. Это значит, что в большинстве случаев для разрыва связи обе ее стороны должны быть загружены в DbContext.

Выполняющаяся в памяти база данных EF Core в настоящее время не поддерживает каскадное удаление в базе данных.

Не следует настраивать каскадное удаление в базе данных при обратимом удалении сущностей. Это может привести к случайному полному (вместо обратимого) удалению сущностей.

Ограничения для каскадной реакции на события в базе данных

Некоторые базы данных, особенно SQL Server, устанавливают ограничения для каскадных операций, которые приводят к возникновению циклов. Рассмотрим следующую модель.

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

Это вполне разумный, хотя и достаточно строгий подход к управлению блогом. Тем не менее при попытке создать базу данных SQL Server с этими настройками каскадной реакции на событий возникнет следующее исключение:

Microsoft.Data.SqlClient.SqlException (0x80131904): Введение ограничения внешнего ключа (FOREIGN KEY) FK_Posts_Person_AuthorId для таблицы Posts может привести к появлению циклов или множественных каскадных путей. Укажите ON DELETE NO ACTION или ON UPDATE NO ACTION либо измените другие ограничения внешнего ключа (FOREIGN KEY).

Эту проблему можно решить двумя способами.

Применение первого подхода в нашем примере позволяет сделать связь между блогом и его владельцем необязательной, предоставив свойство внешнего ключа, допускающее значение NULL.

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

Если использовать второй подход, связь между блогом и владельцем можно оставить обязательной и настроить для каскадного удаления, но при этом задать применение этой конфигурации только к отслеживаемым сущностям, а не к базе данных.

Что произойдет, если загрузить владельца блога и сам блог, а затем удалить владельца?

EF Core выполнит каскадное удаление владельца и принадлежащего ему блога.

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

После этого возникает исключение из-за нарушения ограничения внешнего ключа в базе данных.

Microsoft.Data.SqlClient.SqlException: Конфликт инструкции DELETE с ограничением REFERENCE FK_Blogs_People_OwnerId. Конфликт произошел в базе данных Scratch, таблица dbo.Blogs, столбец OwnerId. Выполнение данной инструкции было прервано.

Каскадное распространение значений NULL

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

Еще раз возьмем примеры из раздела Когда происходит каскадная реакция на события. На этот раз рассмотрим необязательную связь, которая представлена допускающим значение NULL свойством внешнего ключа :

Этому свойству внешнего ключа будет присваиваться значение NULL для каждой записи при удалении связанного с ней блога. Например, следующий код аналогичен приведенному ранее.

Тем не менее теперь при вызове метода SaveChanges будут выполнены следующие обновления базы данных.

Аналогичным образом, при разрыве связи в любом из приведенных выше примеров:

При вызове метода SaveChanges записи будут обновлены с присвоением внешнему ключу значения NULL.

Дополнительные сведения о том, как EF Core управляет внешними ключами и свойствами навигации при изменении их значений, см. в разделе Изменение внешних ключей и свойств навигации.

По умолчанию, начиная с первой версии Entity Framework 2008 года, выполнялась адресная привязка таких связей. До появления EF Core у такого подхода не было своего названия и его нельзя было изменить. Сейчас он называется ClientSetNull и описывается в следующем разделе.

Базы данных также можно настроить для подобного каскадного распространения значений NULL при удалении основной или родительской сущности в необязательной связи. Тем не менее этот подход применяется гораздо реже операций каскадного удаления в базе данных. Одновременное выполнение каскадного удаления и каскадного распространения значений NULL в базе данных SQL Server почти всегда будет приводить к возникновению циклов связей. Дополнительные сведения о настройке каскадного распространения значений NULL см. в следующем разделе.

Настройка каскадной реакции на события

Прежде чем переходить к этому разделу, ознакомьтесь с приведенными выше. Для понимания параметров конфигурации потребуется знание представленных ранее материалов.

Настройка каскадной реакции на события для отдельных связей осуществляется с помощью метода OnDelete в OnModelCreating. Пример:

Дополнительные сведения о настройке связей между типами сущностей см. в разделе Связи.

Влияние на схему базы данных

Значение перечисления DeleteBehaviorВлияние на схему базы данных
CascadeON DELETE CASCADE
ОграничениеON DELETE RESTRICT
NoActiondatabase default
SetNullON DELETE SET NULL
ClientSetNulldatabase default
ClientCascadedatabase default
ClientNoActiondatabase default

Влияние на поведение метода SaveChanges

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

Обязательная связь с загруженными зависимыми или дочерними сущностями

Значение перечисления DeleteBehaviorПоведение при удалении основной или родительской сущностиПоведение при разрыве связи с основной или родительской сущностью
CascadeEF Core удаляет зависимые сущностиEF Core удаляет зависимые сущности
ОграничениеInvalidOperationExceptionInvalidOperationException
NoActionInvalidOperationExceptionInvalidOperationException
SetNullПри создании базы данных возникает исключение SqlExceptionПри создании базы данных возникает исключение SqlException
ClientSetNullInvalidOperationExceptionInvalidOperationException
ClientCascadeEF Core удаляет зависимые сущностиEF Core удаляет зависимые сущности
ClientNoActionDbUpdateExceptionInvalidOperationException

Обязательная связь, для которой не загружены зависимые или дочерние сущности

Значение перечисления DeleteBehaviorПоведение при удалении основной или родительской сущностиПоведение при разрыве связи с основной или родительской сущностью
CascadeБаза данных удаляет зависимые сущностиН/Д
ОграничениеDbUpdateExceptionН/Д
NoActionDbUpdateExceptionН/Д
SetNullПри создании базы данных возникает исключение SqlExceptionН/Д
ClientSetNullDbUpdateExceptionН/Д
ClientCascadeDbUpdateExceptionН/Д
ClientNoActionDbUpdateExceptionН/Д

Необязательная связь с загруженными зависимыми или дочерними сущностями

Значение перечисления DeleteBehaviorПоведение при удалении основной или родительской сущностиПоведение при разрыве связи с основной или родительской сущностью
CascadeEF Core удаляет зависимые сущностиEF Core удаляет зависимые сущности
ОграничениеEF Core присваивает значения NULL внешним ключам зависимых сущностейEF Core присваивает значения NULL внешним ключам зависимых сущностей
NoActionEF Core присваивает значения NULL внешним ключам зависимых сущностейEF Core присваивает значения NULL внешним ключам зависимых сущностей
SetNullEF Core присваивает значения NULL внешним ключам зависимых сущностейEF Core присваивает значения NULL внешним ключам зависимых сущностей
ClientSetNullEF Core присваивает значения NULL внешним ключам зависимых сущностейEF Core присваивает значения NULL внешним ключам зависимых сущностей
ClientCascadeEF Core удаляет зависимые сущностиEF Core удаляет зависимые сущности
ClientNoActionDbUpdateExceptionEF Core присваивает значения NULL внешним ключам зависимых сущностей

Необязательная связь, для которой не загружены зависимые или дочерние сущности

Значение перечисления DeleteBehaviorПоведение при удалении основной или родительской сущностиПоведение при разрыве связи с основной или родительской сущностью
CascadeБаза данных удаляет зависимые сущностиН/Д
ОграничениеDbUpdateExceptionН/Д
NoActionDbUpdateExceptionН/Д
SetNullБаза данных присваивает значения NULL внешним ключам зависимых сущностейН/Д
ClientSetNullDbUpdateExceptionН/Д
ClientCascadeDbUpdateExceptionН/Д
ClientNoActionDbUpdateExceptionН/Д

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *