OneToMany 或 OneToOne,我是在正确还是错误的道路上?

Posted

技术标签:

【中文标题】OneToMany 或 OneToOne,我是在正确还是错误的道路上?【英文标题】:OneToMany or OneToOne, I'm in the right or wrong path? 【发布时间】:2014-09-07 17:44:56 【问题描述】:

我有这个数据库模型:

然后我创建了这个实体(我只留下关系部分,因为另一个与主题无关):

Orders.php

class Orders 
    /**
     * @ORM\ManyToOne(targetEntity="Person", inversedBy="orders")
     * @ORM\JoinColumn(name="person_id", referencedColumnName="id")
     * */
    protected $person;

    public function setPerson(Person $person)
    
        $this->person = $person;
        return $this;
    

    public function getPerson()
    
        return $this->person;
    


Person.php

class Person 
    /**
     * @ORM\OneToMany(targetEntity="NaturalPerson", mappedBy="person")
     * */
    private $naturals;

    /**
     * @ORM\OneToMany(targetEntity="LegalPerson", mappedBy="person")
     * */
    private $legals;

    /**
     * @ORM\OneToMany(targetEntity="Orders", mappedBy="person")
     * */
    private $orders;

    public function __construct()
    
        $this->naturals = new ArrayCollection();
        $this->legals = new ArrayCollection();
        $this->orders = new ArrayCollection();
    

    public function getNaturals()
    
        return $this->naturals;
    

    public function getLegals()
    
        return $this->legals;
    

    public function getOrders()
    
        return $this->orders;
    


NaturalPerson.php

class NaturalPerson 

    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Person", inversedBy="naturals")
     * @ORM\JoinColumn(name="person_id", referencedColumnName="id")
     */
    protected $person;

    /**
     * @ORM\Column(name="identification_type", type="ci_type", nullable=false)
     * @DoctrineAssert\Enum(entity="Tanane\FrontendBundle\DBAL\Types\CIType")
     */
    protected $identification_type;

    /**
     * @ORM\Column(name="ci", type="integer", nullable=false)
     */
    protected $ci;

    public function setPerson(Person $person)
    
        $this->person = $person;
        return $this;
    

    public function getPerson()
    
        return $this->person;
    

    public function setIdentificationType($identification_type)
    
        $this->identification_type = $identification_type;
        return $this;
    

    public function getIdentificationType()
    
        return $this->identification_type;
    

    public function setCI($ci)
    
        $this->ci = $ci;
        return $this;
    

    public function getCI()
    
        return $this->ci;
    


我省略了LegalPerson,因为它与NaturalPerson 几乎相同,所以这就是问题所在。映射看起来不错,但我如何从Orders 获取相关记录?

这背后的想法是对于每个Orders,我需要知道Person 也属于哪个(订单)以及存储在NaturalPersonLegalPerson 的额外信息,具体取决于person.type

查看此代码:

public function getOrdersAction()

    $response = array();
    $em = $this->getDoctrine()->getManager();

    $entities = $em->getRepository("FrontendBundle:Orders")->findAll();

    if (!$entities)
    
        $response['message'] = "No se encontraron resultados";
    

    $orders = array();
    foreach ($entities as $entity)
    

        $personType = $entity->getPerson()->getPersonType();

        $order = array();
        $order[] = $entity->getNickname();

        // Here I'm trying to access to `Naturals` methods from `Orders` 
        if ($personType == 1)
        
            $order[] = $entity->getPerson()->getNaturals()[0]->getIdentificationType() . $entity->getPerson()->getNaturals()[0]->getCI();
        
        elseif ($personType == 2)
        
            $order[] = $entity->getPerson()->getLegals()[0]->getIdentificationType() . $entity->getPerson()->getLegals()[0]->getRIF();
        

        $orders[] = $order;
    

    $response['data'] = $orders;
    return new JsonResponse($response);

但我收到此错误:

错误:在 a 上调​​用成员函数 getIdentificationType() 非对象在 /var/www/html/tanane/src/Tanane/BackendBundle/Controller/OrderController.php 第 115 行

也许我的映射是错误的,因为我应该在 PersonNaturalPerson 之间有 OneToOne(这对我的逻辑来说听起来是错误的,如 DER 所示)或者可能不是,但是我不知道如何获取仅一条记录的相关属性,我阅读了文档here 和here 但他们没有谈论这部分,或者我没有看到它,有什么建议吗?想法?提示?

尝试使用 Repositories 和 DQL 解决问题

我正在 Repository 类中构建一个函数来获取数据,而不是像我的问题那样变得复杂,所以我这样做了:

public function getOrders($person_type = 1)

    $qb = $this->getEntityManager()->createQueryBuilder();

    $qb
            ->select('ord.*, ps.*')
            ->from("FrontendBundle:Orders", "ord")
            ->join('FrontendBUndle:Person', 'ps', 'WITH', 'ps.id = ord.person_id')
            ->orderBy('ord.created', 'DESC');

    if ($person_type == 1)
    
        $qb
                ->select('np.*')
                ->join('FrontendBundle:NaturalPerson', 'np', 'WITH', 'ps.id = np.person'); // Join NaturalPerson table
    
    elseif ($person_type == 2)
    
        $qb
                ->select('lp.*')
                ->join('FrontendBundle:LegalPerson', 'lp', 'WITH', 'ps.id = lp.person'); // Join NaturalPerson table
    

    return $qb->getQuery()->getResult();

我还没有经过测试,所以也许它不会起作用,但是,如果我的想法是获取两个表的额外信息,那么使用这个 DQL 我制作了我如何通过 $person_type 内部的 Person桌子?这有点复杂,至少对我来说是这样

运行原始查询以查看列是否为 NULL

我构建这个简单的查询只是为了测试结果是否为NULL

SELECT
    ord.id,
    ord.person_id as ord_person_id,
  ord.nickname,
  ps.id,
  ps.description,
  np.person_id as natural_person_id,
  np.identification_type,
    np.ci
FROM
    orders ord
LEFT JOIN person ps ON ord.person_id = ps.id
LEFT JOIN natural_person np ON np.person_id = ps.id
WHERE
    ps.person_type = 1;

这是查询返回的内容:

所以那里没有 NULL 列

CRUD 用于创建新订单

// Set Person entity
$entityPerson = new Person();
$person_type === 1 ? $entityPerson->setDescription($orders['nat']['person']['description']) : $entityPerson->setDescription($orders['leg']['person']['description']);
$person_type === 1 ? $entityPerson->setContactPerson($orders['nat']['person']['contact_person']) : $entityPerson->setContactPerson($orders['leg']['person']['contact_person']);
$entityPerson->setPersonType($person_type);

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

...

if ($person_type === 1)

    // Set NaturalPerson entity
    $entityNatural = new NaturalPerson();
    $entityNatural->setIdentificationType($orders['nat']['identification_type']);
    $entityNatural->setCI($orders['nat']['ci']);

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

elseif ($person_type === 2)

    // Set LegalPerson entity
    $entityLegal = new LegalPerson();
    $entityLegal->setIdentificationType($orders['leg']['identification_type']);
    $entityLegal->setRIF($orders['leg']['rif']);

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

【问题讨论】:

我猜Person 可能是NaturalPersonLegalPerson,对吧?还是两者兼而有之? 不,它可以是自然人或合法人之一 【参考方案1】:

由于 LegalPersonNaturalPersonPerson 的特化,我建议使用 Doctrine 所谓的类表继承 (documentation)。

你会:

Person.php

/**
 * @ORM\Table(name="person")
 * @ORM\Entity
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap(
 *     "natural" = "NaturalPerson",
 *     "legal" = "LegalPerson",
 * )
 */
class Person 
    /**
     * @ORM\OneToMany(targetEntity="Orders", mappedBy="person")
     * */
    private $orders;

    public function __construct()
    
        $this->orders = new ArrayCollection();
    

    public function getOrders()
    
        return $this->orders;
    


NaturalPerson.php

/**
 * @ORM\Table(name="natural_person")
 * @ORM\Entity
 */
class NaturalPerson extends Person 
    /**
     * @ORM\Column(name="identification_type", type="ci_type", nullable=false)
     * @DoctrineAssert\Enum(entity="Tanane\FrontendBundle\DBAL\Types\CIType")
     */
    protected $identification_type;

    /**
     * @ORM\Column(name="ci", type="integer", nullable=false)
     */
    protected $ci;

    public function setIdentificationType($identification_type)
    
        $this->identification_type = $identification_type;
        return $this;
    

    public function getIdentificationType()
    
        return $this->identification_type;
    

    public function setCI($ci)
    
        $this->ci = $ci;
        return $this;
    

    public function getCI()
    
        return $this->ci;
    

Order.php 保持不变。

如您所见,现在NaturalPersonLegalPerson 都扩展了Person。由于您更改了实体定义,因此您必须更新数据库架构。

现在,在您的 Controller 中,您只需要这样做:

foreach ($entities as $entity)

    $person = $entity->getPerson();

    $order = array();
    $order[] = $entity->getNickname();

    if ($person instanceof NaturalPerson)
    
        $order[] = $person->getIdentificationType() . $person->getCI();
    
    else // it has to be LegalPerson
    
        $order[] = $person->getIdentificationType() . $person->getRIF();
    

    $orders[] = $order;

不要忘记为NaturalPerson 添加use 语句!

这样您只能使用NaturalPersonLegalPerson 的实例。我相信您可以进一步改进这一点。

最后,您必须为此更改 CRUD。您不再直接使用Person(实际上应该是abstract),所以现在您需要分别处理NaturalPersonLegalPerson 的CRUD。每个都有其TypeController、视图等。

您的代码现在看起来像这样:

if ($person_type === 1)

    $entityPerson = new NaturalPerson();
    $entityPerson->setDescription($orders['nat']['person']['description']);
    $entityPerson->setContactPerson($orders['nat']['person']['contact_person']);
    $entityPerson->setIdentificationType($orders['nat']['identification_type']);
    $entityPerson->setCI($orders['nat']['ci']);

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

elseif ($person_type === 2)

    $entityPerson = new LegalPerson();
    $entityPerson->setDescription($orders['leg']['person']['description']);
    $entityPerson->setContactPerson($orders['leg']['person']['contact_person']);
    $entityPerson->setIdentificationType($orders['leg']['identification_type']);
    $entityPerson->setRIF($orders['leg']['rif']);

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

【讨论】:

我更喜欢这种方法,如果你注意到我的模型正是我试图通过使用 SQL 反模式类表继承来做的事情,我不知道 Doctrine 已经涵盖了这一点,现在我知道,无论如何我都会收到此错误:SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.discr' in 'field list' 我需要运行任何命令吗?也许是为了更新数据库? 好的,我已经更新了架构,但现在每当我尝试插入新记录时,我都会收到另一个错误 SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'discr' cannot be null 我应该在该字段上设置什么? 糟糕!我忘了解释那部分。我会尽快更新我的答案。 太好了,谢谢,我正在阅读你留下的文档,我看到了这个:When you do not use the SchemaTool to generate the required SQL you should know that deleting a class table inheritance makes use of the foreign key property ON DELETE CASCADE in all database implementations. A failure to implement this yourself will lead to dead rows in the database. 你能根据我的模型解释一下吗?我应该做哪些改变才不会发生这种情况? 使用这种架构,您最终会得到三个表:person、natural_person 和 legal_person。如果你坚持一个新的NaturalPerson 学说将在natural_person 中添加一行,在person 中添加另一行。文档告诉您的是,如果您通过其他方式删除 natural_person 中的行(例如在 phpMyAdmin 中手动删除),person 中仍然会留下一行,并且由于没有人再使用它,所以称为死排。【参考方案2】:

也许是其他方面的问题。您可能忘记将NaturalPersonLegalPerson 分配给Person 实体。所以在调用getIdentificationType()之前需要检查一下:

if($personType == 1)
    if(null !== $natural = $entity->getPerson()->getNaturals()[0])
       $order[] = $natural->getIdentificationType() . $natural->getCI();
    
elseif($personType == 2)
    if(null !== $legal = $entity->getPerson()->getLegals()[0])
       $order[] = $legal->getIdentificationType() . $legal->getRIF();
    

【讨论】:

以上是关于OneToMany 或 OneToOne,我是在正确还是错误的道路上?的主要内容,如果未能解决你的问题,请参考以下文章

EntityAudit - Doctrine2:可能在 OneToMany 和 OneToOne 中使用相同的实体

学说 oneToOne 单向:获取映射对象

使用公式休眠@OneToOne - 列不能外连接到子查询

奏鸣曲管理包中的内联可编辑 OneToOne 关系

Entity-Bean (JPA) 中的单向关系

休眠 OneToMany 和 ManyToOne?