เมื่อลบน้ำตกด้วยหลักคำสอน 2


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;
}

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;
}

ตารางนั้นสร้างขึ้นอย่างถูกต้องบนฐานข้อมูล แต่ตัวเลือก On Delete Cascade จะไม่ถูกสร้างขึ้น ผมทำอะไรผิดหรือเปล่า?


คุณได้ทำการทดสอบว่าน้ำตกนั้นทำงานอย่างถูกต้องหรือไม่? บางทีหลักคำสอนจัดการกับพวกเขาในรหัสแทนในฐานข้อมูล
ปัญหา

คำตอบ:


408

การลดหลั่นมีสองประเภทในลัทธิ

1) ระดับ ORM - ใช้cascade={"remove"}ในการเชื่อมโยง - นี่เป็นการคำนวณที่กระทำใน UnitOfWork และไม่มีผลต่อโครงสร้างฐานข้อมูล เมื่อคุณลบวัตถุออก UnitOfWork จะวนซ้ำวัตถุทั้งหมดในการเชื่อมโยงและลบออก

2) ระดับฐานข้อมูล - ใช้onDelete="CASCADE"ใน joinColumn ของสมาคม - สิ่งนี้จะเพิ่ม On Delete Cascade ให้กับคอลัมน์ foreign key ในฐานข้อมูล:

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

ฉันยังต้องการชี้ให้เห็นว่าวิธีที่คุณมี cascade = {"remove"} ในตอนนี้ถ้าคุณลบวัตถุลูก, น้ำตกนี้จะลบวัตถุหลัก ชัดเจนไม่ใช่สิ่งที่คุณต้องการ


3
ฉันมักใช้ onDelete = "เรียงซ้อน" เพราะมันหมายถึง ORM ต้องทำงานน้อยลงและควรมีประสิทธิภาพที่ดีขึ้นเล็กน้อย
Michael Ridgway

57
ฉันก็ทำเช่นกัน แต่ก็ขึ้นอยู่กับ พูดเช่นคุณมีแกลเลอรี่ภาพพร้อมภาพ เมื่อคุณลบแกลเลอรี่คุณต้องการลบภาพจากดิสก์ด้วย หากคุณใช้สิ่งนั้นในเมธอด delete () ของอิมเมจออบเจกต์ของคุณการลบแบบเรียงซ้อนโดยใช้ ORM จะทำให้แน่ใจว่ามีการเรียกใช้ฟังก์ชัน delte () ของอิมเมจทั้งหมดของคุณ
ไข้หวัดใหญ่

4
@Michael Ridgway บางครั้งควรใช้ทั้งสองข้อความonDeleteเช่นเดียวกับcascade = {"remove"}เมื่อคุณมีวัตถุบางอย่างที่เกี่ยวข้องกับ fosUser วัตถุทั้งสองไม่ควรอยู่คนเดียว
ลุค Adamczewski

17
โปรดทราบว่าคุณสามารถเขียน@ORM\JoinColumn(onDelete="CASCADE")และให้หลักคำสอนจัดการชื่อคอลัมน์โดยอัตโนมัติ
mcfedr

5
@dVaffection เป็นคำถามที่ดี ฉันคิดว่าสิ่งonDelete="CASCADE"นี้จะไม่ส่งผลกระทบใด ๆ เนื่องจาก Doctrine cascade={"remove"}จะลบเอนทิตีที่เกี่ยวข้องก่อนที่จะลบเอนทิตีราก (ต้อง) ดังนั้นเมื่อลบเอนทิตีรูทแล้วจะไม่มีความสัมพันธ์ต่างประเทศเหลือไว้onDelete="CASCADE"ให้ลบ แต่เพื่อให้แน่ใจว่าฉันขอแนะนำให้คุณสร้างกรณีทดสอบขนาดเล็กและดูคำถามที่กำลังดำเนินการและลำดับการดำเนินการ
ไข้หวัด

50

นี่คือตัวอย่างง่ายๆ ผู้ติดต่อมีหมายเลขโทรศัพท์ที่เกี่ยวข้องหนึ่งถึงหลายหมายเลข เมื่อผู้ติดต่อถูกลบฉันต้องการลบหมายเลขโทรศัพท์ที่เกี่ยวข้องทั้งหมดดังนั้นฉันจึงใช้ ON DELETE CASCADE ความสัมพันธ์แบบหนึ่งต่อหลาย / หลายต่อกลุ่มถูกนำไปใช้กับโดย foreign key ใน 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 หากต้องการรับ behavoir DB ON ระดับ "ON DELETE CASCADE" คุณกำหนดค่า @JoinColumn ด้วยตัวเลือกonDelete = "CASCADE"

<?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

1
@przemo_li เป็นตำแหน่งที่ถูกต้อง ผู้ติดต่อไม่ทราบว่ามีหมายเลขโทรศัพท์อยู่เนื่องจากหมายเลขโทรศัพท์มีการอ้างอิงถึงผู้ติดต่อและผู้ติดต่อไม่มีการอ้างอิงถึงหมายเลขโทรศัพท์ ดังนั้นหากผู้ติดต่อถูกลบหมายเลขโทรศัพท์จะมีการอ้างอิงไปยังผู้ติดต่อที่ไม่มีอยู่ ในกรณีนี้เราต้องการสิ่งที่เกิดขึ้น: เรียกการดำเนินการ ON DELETE เราตัดสินใจลดการลบดังนั้นเพื่อลบหมายเลขโทรศัพท์ด้วย
marijnz0r

3
@przemi_li onDelete="cascade"ถูกวางไว้อย่างถูกต้องในเอนทิตี (บนลูก) เพราะนั่นคือSQL cascadingซึ่งวางอยู่บนลูก เฉพาะหลักคำสอนแบบเรียงซ้อน ( cascade=["remove"]ซึ่งไม่ได้ใช้ที่นี่) ถูกวางไว้บนพาเรนต์
Maurice
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.