Comments 8
При сохранении записи на курс со ссылкой на студента, которого больше нет в БД, мы получим ошибку ограничения внешнего ключа. По хорошему на сервере нужно было заранее об этом подумать и сделать каскадное удаление данных, чтобы вообще не допускать этой проблемы.
Вот как-то совсем не понял, как вообще может возникнуть эта ошибка... если студента в БД нет, откуда взялась запись, которую вы пытаетесь сохранить? как она вообще могла появиться?
Со стороны интерфейса, как я понимаю, оно выглядит так: ткнули в студента, ткнули в курс, нажали кнопку "записать выбранного студента на выбранный курс". И описанная проблема может возникнуть только в случае, если с момента загрузки списков, пока расставляются галки, кто-то другой этого выбранного студента удалил.
Ну так это не проблема внешнего ключа, это проблема непродуманного алгоритма многопользовательской работы. Достаточно изменить запрос с простого, но ничего не проверяющего
INSERT INTO Registrations (StudentID, CourseID) VALUES (@StudentID, @CourseID);
на надёжное
INSERT INTO Registrations (StudentID, CourseID)
SELECT Students.StudentID, Cources.CourseID
FROM Students
CROSS JOIN Cources
WHERE Students.StudentID = @StudentID
AND Cources.CourseID = @CourseID;
И, собственно, всё. Ошибка умерла, даже не родившись. А, посмотрев на affected rows, можно сказать, успешно выполнена запись, или кто-то успел студента удалить.
Да, также вообще не понял, каким тут боком каскадное удаление. Так, для красного словца? Мы же вроде вставляем новую запись...
Мы сталкиваемся с этой проблемой в мобильных приложениях, когда синхронизируем данные c сервера и сохраняем их в базу данных SQLite.
Пример вымышленный, однако практика показывает, что такое может случиться. Я представляю себе ситуацию так: в административной панели по какой-то причине был удален студент, однако запись о его участии в курсе осталась (почему это произошло — вопрос к бэкенду). Каскадное удаление решило бы проблему появления "висячих" записей, так как при удалении родительской записи "студент" удалилась бы и "запись на курс" с этим студентом. (Возможно есть и другие ситуации, которые приводят к появлению таких записей, в том числе и из-за неправильно логики на бэкенде или мобилке).
При сохранении в мобильном приложении можно было бы добавить проверку на наличие студента в записи на курс, которую мы пытаемся сохранить, но тогда мы не выявим корень проблемы и могут возникнуть другие ошибки связанные уже с логикой в мобильном приложении (часто лучше быстро получить ошибку и быстро ее поправить, чем вообще не подозревать о ее наличии). Плюс из-за дополнительных проверок на наличие родительских записей, скорость синхронизации в МП просядет, что вызовет гнев пользователей.
Из-за появления ошибок такого рода, мы решили улучшить ее описание при возникновении в мобильном приложениях, чтобы можно было быстро понять в чем проблема и пофиксить ее (все таки мы люди и нам свойственно ошибаться).
Ааа... так это просто пример неудачный! Вот если бы базово рассматривалась именно заливка данных извне (грубо - контрагент прислал список записанных им на выезде потенциальных обучающихся, к примеру), где по определению может быть не всё ладно, тогда было бы сразу понятно, почему возможные проблемы проще именно допустить, а их причину проанализировать - потому что на каждую возможную граблю никаких запросов не хватит.
А ещё лучше, если бы рассматривалась именно проблема "кривого" запроса - да хоть с опечаткой. Это я к тому, что возможно в наборе добавляемых данных наличие просто кучи приводящих к ошибке ляпов, но отловится только один из них, а остальные - лишь после исправления, на следующем запуске. Почему и ратую за то, чтобы хотя бы самые очевидные из таких ляпов не допускались самОй логикой кода.
На практике, каскадное удаление на бэкенде почти нигде не используется, потому что может приводить к страшным багам в ORM, при неосторожном использовании или ошибках в самом ORM, вплоть до удаления всей базы в ходе путешествия по каскадам. Гораздо эффективней иметь поле "запись удалена" и устанавливать туда правильный флаг. За одним, такой подход позволяет реализовать отмену операций удаления или просмотр истории всех операций и прочие кейсы путешествий в прошлое.
На практике, каскадное удаление на бэкенде почти нигде не используется, потому что может приводить к страшным багам в ORM, при неосторожном использовании или ошибках в самом ORM, вплоть до удаления всей базы в ходе путешествия по каскадам.
Ну так это либо бага в ORM (что вряд ли), либо кривая логика бэка, либо кривая реализация этой логики. В любом случае - это проблема вовсе не сервера БД.
Гораздо эффективней иметь поле "запись удалена" и устанавливать туда правильный флаг.
Мягкое удаление - это, конечно, вариант. Но он утяжеляет БД, поскольку в ней будет мотаться куча записей, которые теперь надо в абсолютно каждом запросе фильтровать. Уж лучше думать об архивной таблице и сливать туда всё удаляемое триггером BEFORE / INSTEAD OF DELETE. Решение в общем тоже не сахарное, база по-прежнему будет пухнуть. Две БД и перемещение из одной в другую - это уже операция через клиента, и тут проблем вагон и маленькая тележка. И всё это - руками, ибо версионку и temporal tables я как-то в SQLite не припоминаю.
или просмотр истории всех операций и прочие кейсы путешествий в прошлое.
Не всех. Только удаление - апдейты таким способом не поймать. И глубина истории будет всего в одно действие. Или придётся делать полноценную систему журналирования всех действий с данными.
.
Борьба с ветряными мельницами. Такую ошибку с бд надо отслеживать на сервере или даже заменить удаление записи с первичным ключем просто на перенос в неактивные или архив, если возможно.
Так в статье и говорится о том, что эту ошибку нужно исправлять на сервере, вот только если все таки допустили ошибку и в приложение пришли не консистентные данные, то благодаря методам, которые описанны в статье, можно будет быстро вычислить ошибку и поправить. Иначе просто в связи плохого описания ошибки, пришлось бы дергать мобильных разработчиков, чтобы они нашли проблему, а их время не дешево стоит. К тому же эту ошибку можно допустить и в мобильном приложении, и эти методы также позволят быстро разобраться в проблеме.
Кот в мешке: мастерство обработки ошибок внешних ключей SQLite