关于使用doctrine2删除级联


227

我试图做一个简单的例子,以学习如何从父表中删除一行并使用Doctrine2自动删除子表中的匹配行。

这是我正在使用的两个实体:

Child.php:

<?php

namespace Acme\CascadeBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="child")
 */
class Child {

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @ORM\ManyToOne(targetEntity="Father", cascade={"remove"})
     *
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="father_id", referencedColumnName="id")
     * })
     *
     * @var father
     */
    private $father;
}

父亲.php

<?php
namespace Acme\CascadeBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="father")
 */
class Father
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
}

这些表已在数据库上正确创建,但未创建“删除级联”选项。我究竟做错了什么?


您是否测试了级联是否正常执行?也许Doctrine用代码而不是数据库来处理它们。
有问题的

Answers:


408

在教义中有两种级联:

1)ORM级别- cascade={"remove"}在关联中使用-这是在UnitOfWork中完成的计算,不影响数据库结构。删除对象时,UnitOfWork将遍历关联中的所有对象并将其删除。

2)数据库级别- onDelete="CASCADE"在关联的joinColumn上使用-这会将On Delete Cascade添加到数据库的外键列中:

@ORM\JoinColumn(name="father_id", referencedColumnName="id", onDelete="CASCADE")

我还要指出的是,您现在具有层叠= {“ remove”}的方式,如果删除Child对象,则此层叠将删除Parent对象。显然不是您想要的。


3
我通常使用onDelete =“ CASCADE”,因为这意味着ORM要做的工作更少,并且应该具有更好的性能。
Michael Ridgway

57
我也这样做,但这要看情况。假设您有一个包含图片的图片库。当您删除图库时,您也希望从磁盘上删除图像。如果在图像对象的delete()方法中实现该功能,则使用ORM进行级联删除将确保所有图像的delte()函数都被调用,从而节省了执行cronjobs来检查孤立图像文件的工作。
2012年

4
- @迈克尔李奇微有时两个语句应适用onDelete,以及cascade = {"remove"}当你有fosUser相关的一些对象实例。两个对象都不应该单独存在
Luke Adamczewski 2012年

17
请注意,您可以编写@ORM\JoinColumn(onDelete="CASCADE")并仍然让教义自动处理列名。
mcfedr 2014年

5
@dVaffection这是一个好问题。我认为这onDelete="CASCADE"不会有任何影响,因为Doctrine cascade={"remove"}在删除根实体之前必须先删除相关实体(必须这样做)。因此,当删除根实体时,将没有任何onDelete="CASCADE"可删除的外部关系。但是可以肯定的是,我建议您仅创建一个小的测试用例,并查看正在执行的查询及其执行顺序。
2015年

50

这是简单的例子。联系人具有一对多关联的电话号码。删除联系人后,我希望所有与之关联的电话号码也被删除,因此我使用了ON DELETE CASCADE。一对多/多对一关系是通过phone_numbers中的外键实现的。

CREATE TABLE contacts
 (contact_id BIGINT AUTO_INCREMENT NOT NULL,
 name VARCHAR(75) NOT NULL,
 PRIMARY KEY(contact_id)) ENGINE = InnoDB;

CREATE TABLE phone_numbers
 (phone_id BIGINT AUTO_INCREMENT NOT NULL,
  phone_number CHAR(10) NOT NULL,
 contact_id BIGINT NOT NULL,
 PRIMARY KEY(phone_id),
 UNIQUE(phone_number)) ENGINE = InnoDB;

ALTER TABLE phone_numbers ADD FOREIGN KEY (contact_id) REFERENCES \
contacts(contact_id) ) ON DELETE CASCADE;

通过将“ ON DELETE CASCADE”添加到外键约束中,当删除与他们关联的联系人时,phone_numbers将自动被删除。

INSERT INTO table contacts(name) VALUES('Robert Smith');
INSERT INTO table phone_numbers(phone_number, contact_id) VALUES('8963333333', 1);
INSERT INTO table phone_numbers(phone_number, contact_id) VALUES('8964444444', 1);

现在,当删除联系人表中的一行时,其所有关联的phone_numbers行将被自动删除。

DELETE TABLE contacts as c WHERE c.id=1; /* delete cascades to phone_numbers */

要在Doctrine中实现相同的目的,要获得相同的数据库级“ ON DELETE CASCADE”行为,请使用onDelete =“ CASCADE”选项配置@JoinColumn 。

<?php
namespace Entities;

use Doctrine\Common\Collections\ArrayCollection;

/**
 * @Entity
 * @Table(name="contacts")
 */
class Contact 
{

    /**
     *  @Id
     *  @Column(type="integer", name="contact_id") 
     *  @GeneratedValue
     */
    protected $id;  

    /** 
     * @Column(type="string", length="75", unique="true") 
     */ 
    protected $name; 

    /** 
     * @OneToMany(targetEntity="Phonenumber", mappedBy="contact")
     */ 
    protected $phonenumbers; 

    public function __construct($name=null)
    {
        $this->phonenumbers = new ArrayCollection();

        if (!is_null($name)) {

            $this->name = $name;
        }
    }

    public function getId()
    {
        return $this->id;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function addPhonenumber(Phonenumber $p)
    {
        if (!$this->phonenumbers->contains($p)) {

            $this->phonenumbers[] = $p;
            $p->setContact($this);
        }
    }

    public function removePhonenumber(Phonenumber $p)
    {
        $this->phonenumbers->remove($p);
    }
}

<?php
namespace Entities;

/**
 * @Entity
 * @Table(name="phonenumbers")
 */
class Phonenumber 
{

    /**
    * @Id
    * @Column(type="integer", name="phone_id") 
    * @GeneratedValue
    */
    protected $id; 

    /**
     * @Column(type="string", length="10", unique="true") 
     */  
    protected $number;

    /** 
     * @ManyToOne(targetEntity="Contact", inversedBy="phonenumbers")
     * @JoinColumn(name="contact_id", referencedColumnName="contact_id", onDelete="CASCADE")
     */ 
    protected $contact; 

    public function __construct($number=null)
    {
        if (!is_null($number)) {

            $this->number = $number;
        }
    }

    public function setPhonenumber($number)
    {
        $this->number = $number;
    }

    public function setContact(Contact $c)
    {
        $this->contact = $c;
    }
} 
?>

<?php

$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);

$contact = new Contact("John Doe"); 

$phone1 = new Phonenumber("8173333333");
$phone2 = new Phonenumber("8174444444");
$em->persist($phone1);
$em->persist($phone2);
$contact->addPhonenumber($phone1); 
$contact->addPhonenumber($phone2); 

$em->persist($contact);
try {

    $em->flush();
} catch(Exception $e) {

    $m = $e->getMessage();
    echo $m . "<br />\n";
}

如果你现在这样做

# doctrine orm:schema-tool:create --dump-sql

您将看到将生成与第一个原始SQL示例相同的SQL


4
它是正确的位置吗?删除电话号码不应删除联系人。删除应触发级联的联系人。为什么然后将级联放置在儿童/电话上?
przemo_li 2015年

1
@przemo_li这是正确的位置。联系人不知道电话号码存在,因为电话号码引用了该联系人,而联系人没有引用该电话号码。因此,如果某个联系人被删除,则电话号码将引用一个不存在的联系人。在这种情况下,我们希望发生一些事情:触发ON DELETE操作。我们决定级联删除操作,因此也要删除电话号码。
marijnz0r

3
@przemi_li onDelete="cascade"正确地放置在实体上(子代上),因为这是SQL层叠,放置在子代上。只有学说级联(cascade=["remove"],这是不是在这里使用)放在父。
莫里斯(Maurice)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.