2014-05-01 109 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沒有被調用。你能提供更多的代碼示例嗎? –