共享主键的两个表上的一对一关系

时间:2013-10-31 14:15:44

标签: php symfony doctrine-orm

我想要两个表{​​{1}}和Person来分享他们的主键:

Address

我正在尝试模拟这样的想法:当您创建CREATE TABLE Person (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY); CREATE TABLE Address (personId INT UNSIGNED NOT NULL PRIMARY KEY); 时,您还要为其创建Person

我提出了以下映射:

Address

当我尝试保留class Person { /** * @Id * @Column(type="integer") * @GeneratedValue */ private $id; /** * @OneToOne(targetEntity="Address", cascade={"all"}) * @JoinColumn(name="id", referencedColumnName="personId") */ private $address; public function __construct() { $this->address = new Address($this); } } class Address { /** * @Id * @OneToOne(targetEntity="Person") * @JoinColumn(name="personId", referencedColumnName="id") */ private $person; public function __construct(Person $person) { $this->person = $person; } } 时,我希望它会级联到Person,但我得到以下异常:

学说\ ORM \ ORMException

  

类型Address的实体通过外部实体Address具有身份,   但是这个实体本身没有身份。你必须打电话   对相关实体Person并确保一个   在尝试保留EntityManager#persist()之前生成了标识符。   如果是后插入ID生成(例如MySQL Address   或PostgreSQL Auto-Increment)这意味着您必须致电SERIAL   在两个持续的操作之间。

我无法明确调用EntityManager#flush(),因为我没有明确flush() persist(),而是期望Address级联持久Person }。

据我了解,在适当的时刻Address获得flush()应该是Doctrine的工作。

当对idauto_increment使用不同的Person ID时,这种一对一的关系会很好(但我必须在{Address添加addressId 1}})。

我的目标是让他们分享Person

这可能吗?

3 个答案:

答案 0 :(得分:3)

如何在学说2.1中使用Identity through foreign Entities

在您的方案中,您可以使用@ManyToOne代替@OneToOne关联。

修改

当您坚持拥有方时,该关联会持续存在。在您的映射中,即Address。更改映射,以便所有者拥有外键,一切都应该正常工作。

/**
 * @OneToOne(targetEntity="Address", cascade={"all"}, inversedBy="person")
 * @JoinColumn(name="id", referencedColumnName="personId")
 */
private $address;

换句话说,如果Address应该是Person的所有者,那么您必须先将Address保留在第一位,以便保持关联。 如果您希望将关联与Person保持一致,则应将Person标记为拥有方,将Address标记为反向方:< / p>

/**
 * @Id
 * @OneToOne(targetEntity="Person", inversedBy="address")
 * @JoinColumn(name="personId", referencedColumnName="id")
 */
private $person;

答案 1 :(得分:2)

您可以使用symfony listener并从地址实体中移除自动增量策略:

首先创建一个Listner并创建您的列表器类,如AdressGenerator.php

<?php

namespace Zodiac\HelperBundle\Listener;


use Doctrine\ORM\Event\LifecycleEventArgs;
use AAA\XXXBundle\Entity\Person;
use AAA\XXXBundle\Entity\Adress;

class AdressGenerator
{
    public function prePersist(LifecycleEventArgs $args)
    {
        // This function is called just beforethe event Persist 
        // The AdressGeneratorlistner is defined in the config.yml file
        // Get the entity manager and the entity from le LifecycleEventArgs
        $em = $args->getEntityManager();
        $entity = $args->getEntity();


        if ($entity instanceof Person) {
            //Create the Adress entity
            $adress = new Adress();
            $adress->setId($entity->getId()); 
            $em->persist($adress);
            $entity->setAdress($adress);
            $em->persist($entity);
            $em->flush();

        }

    }
}

然后在config.yml中添加您的侦听器服务:

services:
AdressGenerator.listener:
    class: AAA\xxxBundle\Listener\AdressGenerator
    tags:
        - { name: doctrine.event_listener, event: prePersist }

答案 2 :(得分:0)

我找到了一个解决方案,但我不知道这是否是最好的(对我来说看起来很重)。

这不是映射问题,而是我们坚持的方式:

而不是:

$em->persist($person);
$em->flush();

我们应该这样做:

$em->transactional(function($em) use ($person)
{
    $address = $person->getAddress();
    $person->setAddress(null);
    $em->persist($person);
    $em->flush();
    $address->setPerson($person);
    $em->persist($address);
    $em->flush();
});

如果某人有更清洁的解决方案,他将获得赏金: - )。