2014-05-01 105 views
5

我想给一个实体(发票,订单,预订等)一个唯一的序列号,但只在该年内唯一。因此,每年(或另一个字段,如客户)的第一张发票开始有id 1。这意味着可以有一个复合主键(年,id)或一个主键(即invoice_id)和另外两个列独一无二。使用复合主键生成自动增量ID

我的问题:使用Doctrine2和Symfony2为对象提供自动生成的ID和另一个值的唯一组合的最佳方式是什么?

复合键学说限制

学说不能分配一个自动生成的ID的实体与一个复合主键(http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html):

具有复合关键字的每个实体不能使用比“已分配”的其他 ID生成器。这意味着在您致电EntityManager#persist($entity)之前,ID字段必须设置其值 。

设置序列号手动

所以我必须手动分配一个ID。为了做到这一点,我试图在某一年寻找最高的ID,并给予ID + 1的新实体。我怀疑这是最好的方式,即使它是,我还没有找到最好的干)的方式来做到这一点。由于我认为这是一个通用的问题,我想阻止XY-problem,我已经开始了这个问题。

常规期权:只有MyISAM数据

我发现基于this answer的 '香草' 的MySQL/MyISAM的解决方案:

CREATE TABLE IF NOT EXISTS `invoice` (
    `year` int(1) NOT NULL, 
    `id` mediumint(9) NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY (`year`,`id`) 
) ENGINE=MyISAM; 

因为Doctrine2限制是行不通的,所以我我正在寻找这种香草MySQL解决方案的Doctrine ORM等价物。

其他解决方案

没有为InnoDB的一个解决方案,以及:Defining Composite Key with Auto Increment in MySQL

+0

##畏缩的(可能)问题的常规期权是,你必须锁定**整个表**在您的交易期间,这相当于杀死了并发的INSERT。你为什么要每年重复IDS?一个实际的id值是没有意义的;如果要输出序列,请执行自连接和“COUNT()”。 –

+0

由于财务原因,发票必须有序列号(每年以id = 1开头)。它不一定是主键的一部分,我会编辑我的问题。 –

+1

不,好吧,是的,你可能确实需要骑自行车,然后持续的顺序......你可能无法摆脱这种做法,但至少可以解决表锁问题。将计数器存储在另一个表中(按年份键入),然后使用存储过程或触发器(在他们自己的事务中)将其打开。如果有人不提交他们的行,最终会出现差距,但至少长时间运行的交易不会锁定其他人。那么你只需要在这些列上使用'UNIQUE KEY'。 –

回答

3

的ORM相当于你链接将是一个生命周期回调插入预触发的解决方案。你可以阅读更多关于他们here

一个天真的解决方案看起来像这样。

services.yml

services: 
    invoice.listener: 
     class: MyCompany\CompanyBundle\EventListener\InvoiceListener 
     tags : 
      - { name: doctrine.event_subscriber, connection: default } 

InvoiceListener.php

<?php 

namespace MyCompany\CompanyBundle\EventListener; 

use Symfony\Component\EventDispatcher\EventDispatcherInterface; 
use Doctrine\Common\EventSubscriber; 
use Doctrine\ORM\Event\OnFlushEventArgs; 
use Doctrine\ORM\Event\PostFlushEventArgs; 

use MyCompany\CompanyBundle\Entity\Invoice; 

class InvoiceListener implements EventSubscriber { 

    protected $invoices; 

    public function getSubscribedEvents() { 
     return [ 
      'onFlush', 
      'postFlush' 
     ]; 
    } 

    public function onFlush(OnFlushEventArgs $event) { 
     $this->invoices = []; 
     /* @var $em \Doctrine\ORM\EntityManager */ 
     $em = $event->getEntityManager(); 
     /* @var $uow \Doctrine\ORM\UnitOfWork */ 
     $uow = $em->getUnitOfWork(); 

     foreach ($uow->getScheduledEntityInsertions() as $entity) { 
      if ($entity instanceof Invoice) { 
       $this->invoices[] = $entity; 
      } 
     } 
    } 

    public function postFlush(PostFlushEventArgs $event) { 
     if (!empty($this->invoices)) { 

      /* @var $em \Doctrine\ORM\EntityManager */ 
      $em = $event->getEntityManager(); 

      foreach ($this->invoices as $invoice) { 
       // Get all invoices already in the database for the year in question 
       $invoicesToDate = $em 
        ->getRepository('MyCompanyCompanyBundle:Invoice') 
        ->findBy(array(
         'year' => $invoice->getYear() 
         // You could include e.g. clientID here if you wanted 
         // to generate a different sequence per client 
        ); 
       // Add your sequence number 
       $invoice->setSequenceNum(count($invoicesToDate) + 1); 

       /* @var $invoice \MyCompany\CompanyBundle\Entity\Invoice */ 
       $em->persist($invoice); 
      } 

      $em->flush(); 
     } 
    } 
} 
+0

不能得到它的工作,但看起来像我需要:)。谢谢! Entity \ Invoice如何看起来像?顺便说一句,''return []''在这里给出了一个错误,我把它改成''return array()' –

+0

对Invoice实体的唯一补充将是它的序列号的一个单独的整数属性(setSequenceNum )。这不是一个唯一的标识符,只是一个普通的旧整数。我也假设它在这里有一年属性(getYear),但你可以明显地做一个日期列或其他什么。 简短的数组语法实际上是一个PHP 5.4的增加(见[这里](https://php.net/manual/en/migration54.new-features.php)),所以你会得到这个错误,因为你'重新使用旧版本的PHP。 – Peter

+0

我仍然无法使它工作。看起来postFlush没有被调用。你能提供更多的代码示例吗? –