[英]How to disable cascade deletion for many-to-many relations?
對於多對多關系(例如組和用戶),一旦刪除任何一個實體,聯合表中的行就會自動刪除,就像為關系設置了級聯刪除屬性一樣。 所以基本上我想刪除它IF ONLY IT IS EMPTY 。 所以解決方案必須保證沒有關系被丟棄(就像 FK 約束保證它一樣)。
是否可以默認不這樣做並在違反外鍵約束時拋出異常?
PS:刪除前檢查不是解決方案,因為它很容易出現競爭條件。
PPS:映射定義是微不足道的,為了完整起見,我將它們發布在這里(即使它們沒有帶來任何有用的東西)
PPPS: onDelete: cascade
也不是解決方案:它在數據庫級別創建相應的ON DELETE CASCADE
。
PPPPS:不能使用ON DELETE RESTRICT
,因為 doctrine 將從聯合表中刪除所有引用。
在角色中:
manyToMany:
users:
targetEntity: UserAccount
mappedBy: roles
在用戶中:
manyToMany:
roles:
targetEntity: Role
joinTable:
name: user_role
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
role_id:
referencedColumnName: id
此答案可以視為一種解決方法 。 3個參與課程之間的many-to-many
關聯可以替換one-to-many/many-to-one
關聯,因為默認情況下,Doctrine沒有使用one-to-many
的級聯刪除功能。
幾個小時前,我遇到了同樣的問題: User
和Group
實體之間有一個簡單的ManyToMany
關聯。
如果有一些相關的users
,我不希望Doctrine刪除一個Group
實體,而即使在某個Group中,我也不希望Doctrine刪除一個User
。
為此,在Group
實體中,我具有以下代碼行:
/**
* Group
*
* @ORM\Entity()
* @ORM\HasLifecycleCallbacks()
*
*/
class Group
{
...
/**
* @var \Doctrine\Common\Collections\ArrayCollection
*
* @ORM\ManyToMany(targetEntity="User", mappedBy="groups")
*
*/
private $users;
...
/**
* @ORM\PreRemove()
*/
public function preRemove() {
if ($this->users->count()>0) {
throw new \AppBundle\Utils\Exception\FKConstraintViolationException();
}
}
}
這是FKConstraintViolationException類
namespace AppBundle\Utils\Exception;
class FKConstraintViolationException extends \Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException {
public function __construct() {
$message='Self generated exception';
$driverException=new \Doctrine\DBAL\Driver\OCI8\OCI8Exception('Self generated exception.'); //or whatever driver you use if you have another db
parent::__construct($message, $driverException);
}
}
然后在控制器中,將remove()
放在try/catch
塊中
try {
$em->remove($group);
$em->flush();
$this->addFlash('success', 'deleted_successfully');
} catch (\Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException $e) {
$this->addFlash('error', 'cannotdelete_related_entities');
}
也許最好在preFlush
或onFlush
事件上創建偵聽器,因為您實際上可以在不刷新的情況下在對象上調用remove()
並獲得異常。
在遇到同樣的問題並沒有找到任何解決方案后,我花了幾個小時試圖找出發生這種情況的原因。 我在這里為遇到此問題的其他人發布我的發現。
Role
是關系的擁有方onDelete: 'restrict'
添加到 JoinColumns。 (例如,如果您使用的是 symfony 制造商,這將導致使用正確的 ON DELETE RESTRICT 進行遷移)onDelete: 'cascade'
添加到 InverseJoinColumns。 (這將防止數據被刪除)以下是使用屬性的示例 class 實現:
#[ORM\Entity]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\ManyToMany(targetEntity: Role::class, mappedBy: 'users')]
private $roles;
}
#[ORM\Entity]
class Role
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\ManyToMany(targetEntity: User::class, inversedBy: 'roles')]
#[ORM\JoinColumn(onDelete: 'restrict')]
#[ORM\InverseJoinColumn(onDelete: 'cascade')]
private $users;
}
現在我不確定它是否應該這樣工作,但以下發現使我找到了這個解決方案:
出錯的地方在於方法Doctrine\ORM\Persisters\Entity\BasicEntityPersister::deleteJoinTableRecords()
具體情況
if (isset($mapping['isOnDeleteCascade'])) {
continue;
}
基本上,除非設置了此映射,否則 doctrine 將自動添加一個查詢以刪除關系表中的所有現有條目。
現在,實際設置此映射鍵的位置在方法Doctrine\ORM\Mapping\ClassMetadataInfo::_validateAndCompleteManyToManyMapping()
這是我發現的唯一一個將isOnDeleteCascade
添加到映射的地方。 此方法同時檢查 JoinColumn 和 InverseJoinColumn,如果其中任何一個具有onDelete='cascade'
,則該鍵設置為 true。 具體來說:
if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) === 'cascade') {
$mapping['isOnDeleteCascade'] = true;
}
不幸的是,我在文檔中找不到有關該問題的太多信息,或者這是否是處理此問題的預期方法。 從我所見,不可能像目前在 Doctrine 中實現的方式那樣對兩個外鍵都有 RESTRICT 條件。 但是當只有一方需要限制另一方時,它會起作用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.