Symfony ORM原则:使用由外键组成的复合主键持久化集合

Symfony ORM原则:使用由外键组成的复合主键持久化集合,symfony,orm,doctrine-orm,doctrine-orm-postgres,Symfony,Orm,Doctrine Orm,Doctrine Orm Postgres,我猜这是一个条令错误(我已经在JIRA issue tracker上提交了一个问题),但如果只是用户错误,我决定将其发布在这里 提要 在具有复合主对象的联接表中持久化实体集合 由两个外键和一个元数据字段组成的密钥在某些情况下会失败。代码基于以下说明: 发行详情 成功:当要持久化的集合中的项目的外键1相同时, 并且外键2大于实体中任何现有主键中的外键2 和集合中的相关实体已正确持久化: 示例:GPA“在下方添加val”存在并具有评估值{“评估”:6,“值”:4} 我们将尝试添加一个新的评估值,其

我猜这是一个条令错误(我已经在JIRA issue tracker上提交了一个问题),但如果只是用户错误,我决定将其发布在这里

提要 在具有复合主对象的联接表中持久化实体集合 由两个外键和一个元数据字段组成的密钥在某些情况下会失败。代码基于以下说明:

发行详情
  • 成功:当要持久化的集合中的项目的外键1相同时, 并且外键2大于实体中任何现有主键中的外键2 和集合中的相关实体已正确持久化:

    • 示例:GPA“在下方添加val”存在并具有评估值
      {“评估”:6,“值”:4}
      我们将尝试添加一个新的评估值,其中评估id>任何现有评估值 GPA的评估值“在下面添加val”

    • 请求有效负载:
      {“名称”:“在下面添加val”,“课程”:[],“评估值”:[{“评估”:6,“价值”:4},{“评估”:7,“价值”:3}}

    • 调试日志:

    • [2013-12-31 11:48:48] app.INFO: GPA ID PRESAVE IN CONTROLLER:9 [] [] [2013-12-31 11:48:48] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:7 [] [] [2013-12-31 11:48:48] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:3 [] [] [2013-12-31 11:48:48] app.INFO: GPA ID PRESAVE IN CONTROLLER:9 [] [] [2013-12-31 11:48:48] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:6 [] [] [2013-12-31 11:48:48] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:4 [] [] [2013-12-31 11:48:48] doctrine.DEBUG: "START TRANSACTION" [] [] [2013-12-31 11:48:48] doctrine.DEBUG: INSERT INTO gpa_assessment_value (point_value, grade_point_average_id, assessment_id) VALUES (?, ?, ?) {"1":3,"2":"9","3":"7"} [] [2013-12-31 11:48:48] doctrine.DEBUG: UPDATE gpa_assessment_value SET point_value = ? WHERE grade_point_average_id = ? AND assessment_id = ? [4,9,6] [] [2013-12-31 11:48:48] doctrine.DEBUG: "COMMIT" [] [] [2013-12-31 11:53:59] app.INFO: GPA ID PRESAVE IN CONTROLLER:10 [] [] [2013-12-31 11:53:59] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:8 [] [] [2013-12-31 11:53:59] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:2 [] [] [2013-12-31 11:53:59] app.INFO: GPA ID PRESAVE IN CONTROLLER:10 [] [] [2013-12-31 11:53:59] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:6 [] [] [2013-12-31 11:53:59] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:4 [] [] [2013-12-31 11:53:59] doctrine.DEBUG: "START TRANSACTION" [] [] [2013-12-31 11:53:59] doctrine.DEBUG: INSERT INTO gpa_assessment_value (point_value, grade_point_average_id, assessment_id) VALUES (?, ?, ?) {"1":2,"2":"10","3":"8"} [] [2013-12-31 11:53:59] doctrine.DEBUG: "ROLLBACK" [] [] [2013-12-31 11:53:59] request.CRITICAL: Uncaught PHP Exception Doctrine\DBAL\DBALException: "An exception occurred while executing 'INSERT INTO gpa_assessment_value (point_value, grade_point_average_id, assessment_id) VALUES (?, ?, ?)' with params [2, "10", "8"]: SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "gpa_assessment_value_pkey"
  • 代码 migration.sql 创建表评估 ( id bigserial不为空, scale_id bigint不为空, title varchar不为空, 传递布尔值不为NULL, 排名int, 主键(id) ); 创建表格评估量表 ( id bigserial不为空, 名称varchar不为空, 主键(id) ); -- ... 创建表格坡度\点\平均值 ( id bigserial不为空, 名称varchar不为空, 额外的信用额度数字(4,2), 主键(id) ); -- ... 创建表gpa\U评估值 ( 等级\点\平均值\ id bigint不为空, 评估id bigint不为空, 点_值数值(4,2)不为空, 主键(评估id、分数id、平均id), 外键(评估id)参考评估, 外键(grade\u point\u average\u id)引用grade\u point\u average ); Model/GradePointAverage.php 名称空间MyApp\Model; 使用条令\ORM\Mapping\Entity; 使用条令\ORM\Mapping\Id; 使用条令\ORM\Mapping\GeneratedValue; 使用条令\ORM\Mapping\Column; //... 使用条令\公共\集合\集合; 使用条令\Common\Collections\ArrayCollection; 使用MyApp\Util\ConstructorArgs; 使用MyApp\Model\GradePointAverage\AssessmentValue; // ... /** *@Entity(“MyApp\Repository\GradePointAverageRepository”) */ 班级平均分 { 使用ConstructorArgs; /** *@Id *@GeneratedValue *@Column(type=“bigint”) * *@var int */ 私人$id; // ... /** *@OneToMany(targetEntity=“MyApp\Model\GradePointAverage\AssessmentValue”,mappedBy=“GradePointAverage”,cascade=“persist”) * *@var集合 */ 私人$assessmentValues; // ... /** *@param数组$args */ 公共函数构造(数组$args=[]) { $this->assessmentValues=newarraycollection; // ... $this->handleArgs($args); } // ... /** *@返回集合 */ 公共函数getAssessmentValues() { 返回$this->assessmentValues; } /** *@param ArrayCollection$assessmentValues */ 公共函数setAssessmentValues(ArrayCollection$assessmentValues) { $this->assessmentValues=$assessmentValues; } /** *@param AssessmentValue$AssessmentValue */ 公共功能addAssessmentValue(AssessmentValue$AssessmentValue) { $this->assessmentValues->add($assessmentValue); } /** *@param AssessmentValue$AssessmentValue */ 公共功能removeAssessmentValue(评估值$AssessmentValue) { $this->assessmentValues->removelement($assessmentValue); } // ... } Model/GradePointAverage/AssessmentValue.php 名称空间MyApp\Model\GradePointAverage; 使用条令\ORM\Mapping\Entity; 使用条令\ORM\Mapping\Table; 使用条令\ORM\Mapping\Column; 使用条令\ORM\Mapping\Id; 使用条令\ORM\Mapping\GeneratedValue; 使用条令\ORM\Mapping\manytone; 使用条令\ORM\Mapping\JoinColumn; 使用MyApp\Model\GradePointAverage; 使用MyApp\Model\Assessment; 使用MyApp\Util\ConstructorArgs; /** *@Entity(“MyApp\Repository\GradePointAverage\AssessmentValueRepository”) *@表(“gpa\U评估值”) */ 班级评估价值 { 使用ConstructorArgs; /** *@Id *@ManyToOne(targetEntity=“MyApp\Model\GradePointAverage”) */ 私家车平均售价$1; /** *@Id *@ManyToOne(targetEntity=“MyApp\Model\Assessment”) */ 私人部门$评估; /** *@列(“点值”) * *@var浮动 */ 私人美元价值; /** *@param数组$args */ 公共函数构造(数组$args=[]) { $this->handleArgs($args); } /** *@return-GradePointAverage */ 公共函数getGradePointAverage() { 返回$this->gradePointAverage; } /** *@param GradePointAverage$GradePointAverage */ 公共函数setGradePointAverage(GradePointAverage$GradePointAverage) { $this->gradePointAverage=$gradePointAverage; } /** *@回报评估 */ 公共职能评估() { 返回$this->assessment; } /** *@param Assessment$Assessment */ 公共职能评估(评估$Assessment) { $this->assessment=$assessment; } /** *@返回浮动 */ 公共函数getValue() { 返回$this->value; } /** *@param float$value */ 公共函数setValue($value) { $this->value=$value; } /** *@回报评估表 */ 公共职能评估量表() { 返回$t CREATE TABLE assessment ( id bigserial NOT NULL, scale_id bigint NOT NULL, title varchar NOT NULL, passing boolean NOT NULL, rank int, PRIMARY KEY (id) ); CREATE TABLE assessment_scale ( id bigserial NOT NULL, name varchar NOT NULL, PRIMARY KEY (id) ); -- ... CREATE TABLE grade_point_average ( id bigserial NOT NULL, name varchar NOT NULL, additional_credit_allowance numeric(4, 2), PRIMARY KEY (id) ); -- ... CREATE TABLE gpa_assessment_value ( grade_point_average_id bigint NOT NULL, assessment_id bigint NOT NULL, point_value numeric(4, 2) NOT NULL, PRIMARY KEY (assessment_id, grade_point_average_id), FOREIGN KEY (assessment_id) REFERENCES assessment, FOREIGN KEY (grade_point_average_id) REFERENCES grade_point_average ); namespace MyApp\Model; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Column; //... use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; use MyApp\Util\ConstructorArgs; use MyApp\Model\GradePointAverage\AssessmentValue; // ... /** * @Entity("MyApp\Repository\GradePointAverageRepository") */ class GradePointAverage { use ConstructorArgs; /** * @Id * @GeneratedValue * @Column(type="bigint") * * @var int */ private $id; // ... /** * @OneToMany(targetEntity="MyApp\Model\GradePointAverage\AssessmentValue", mappedBy="gradePointAverage", cascade="persist") * * @var Collection */ private $assessmentValues; // ... /** * @param array $args */ public function __construct(array $args = []) { $this->assessmentValues = new ArrayCollection; // ... $this->handleArgs($args); } // ... /** * @return Collection */ public function getAssessmentValues() { return $this->assessmentValues; } /** * @param ArrayCollection $assessmentValues */ public function setAssessmentValues(ArrayCollection $assessmentValues) { $this->assessmentValues = $assessmentValues; } /** * @param AssessmentValue $assessmentValue */ public function addAssessmentValue(AssessmentValue $assessmentValue) { $this->assessmentValues->add($assessmentValue); } /** * @param AssessmentValue $assessmentValue */ public function removeAssessmentValue(AssessmentValue $assessmentValue) { $this->assessmentValues->removeElement($assessmentValue); } // ... } namespace MyApp\Model\GradePointAverage; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Table; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\ManyToOne; use Doctrine\ORM\Mapping\JoinColumn; use MyApp\Model\GradePointAverage; use MyApp\Model\Assessment; use MyApp\Util\ConstructorArgs; /** * @Entity("MyApp\Repository\GradePointAverage\AssessmentValueRepository") * @Table("gpa_assessment_value") */ class AssessmentValue { use ConstructorArgs; /** * @Id * @ManyToOne(targetEntity="MyApp\Model\GradePointAverage") */ private $gradePointAverage; /** * @Id * @ManyToOne(targetEntity="MyApp\Model\Assessment") */ private $assessment; /** * @Column("point_value") * * @var float */ private $value; /** * @param array $args */ public function __construct(array $args = []) { $this->handleArgs($args); } /** * @return GradePointAverage */ public function getGradePointAverage() { return $this->gradePointAverage; } /** * @param GradePointAverage $gradePointAverage */ public function setGradePointAverage(GradePointAverage $gradePointAverage) { $this->gradePointAverage = $gradePointAverage; } /** * @return Assessment */ public function getAssessment() { return $this->assessment; } /** * @param Assessment $assessment */ public function setAssessment(Assessment $assessment) { $this->assessment = $assessment; } /** * @return float */ public function getValue() { return $this->value; } /** * @param float $value */ public function setValue($value) { $this->value = $value; } /** * @return AssessmentScale */ public function getAssessmentScale() { return $this->assessment->getScale(); } } namespace MyApp\Model; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\ManyToOne; use MyApp\Model\Assessment\Scale; use MyApp\Util\ConstructorArgs; /** * @Entity("MyApp\Repository\AssessmentRepository") */ class Assessment { use ConstructorArgs; /** * @Id * @GeneratedValue * @Column(type="bigint") * * @var int */ private $id; // ... /** * @param array $args */ public function __construct(array $args = []) { $this->handleArgs($args); } /** * @return int */ public function getId() { return $this->id; } // ... } namespace MyApp\Repository; use Doctrine\ORM\EntityRepository; // ... use MyApp\Model\GradePointAverage; class GradePointAverageRepository extends BaseRepository implements GradePointAverageRepositoryInterface { // ... /** * @param GradePointAverage $gradePointAverage */ public function save(GradePointAverage $gradePointAverage) { $this->getEntityManager()->persist($gradePointAverage); $this->getEntityManager()->flush(); } } namespace MyApp\Repository\GradePointAverage; use Doctrine\ORM\EntityRepository; use MyApp\Model\GradePointAverage\AssessmentValue; class AssessmentValueRepository extends EntityRepository { /** * @param AssessmentValue $assessmentValue */ public function save(AssessmentValue $assessmentValue) { $this->getEntityManager()->persist($assessmentValue); $this->getEntityManager()->flush(); } } namespace MyApp\Manager; use InvalidArgumentException; use Symfony\Component\Validator\ValidatorInterface; use JMS\DiExtraBundle\Annotation\Service; use JMS\DiExtraBundle\Annotation\InjectParams; use JMS\SecurityExtraBundle\Annotation\PreAuthorize; use Knp\Component\Pager\Pagination\PaginationInterface; use MyApp\Repository\GradePointAverageRepository; use MyApp\PaginationFactory\GradePointAveragePaginationFactoryInterface; use MyApp\Model\GradePointAverage; /** * @Service("grade_point_average_manager") */ class GradePointAverageManager { /** * @var GradePointAverageRepository */ private $gradePointAverageRepository; /** * @var GradePointAveragePaginationFactoryInterface */ private $gradePointAveragePaginationFactory; /** * @var ValidatorInterface */ private $validator; /** * @InjectParams * * @param GradePointAverageRepository $gradePointAverageRepository * @param GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory * @param ValidatorInterface $validator */ public function __construct( GradePointAverageRepository $gradePointAverageRepository, GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory, ValidatorInterface $validator ) { $this->gradePointAverageRepository = $gradePointAverageRepository; $this->gradePointAveragePaginationFactory = $gradePointAveragePaginationFactory; $this->validator = $validator; } /** * @PreAuthorize("isAllowedToManageTheGradePointAverage(#gradePointAverage)") * @param GradePointAverage $gradePointAverage * @throws InvalidArgumentException */ public function save(GradePointAverage $gradePointAverage) { $violationList = $this->validator->validate($gradePointAverage); if ($violationList->count()) { throw new InvalidArgumentException; } $this->gradePointAverageRepository->save($gradePointAverage); } } namespace MyApp\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Log\LoggerInterface; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Doctrine\Common\Collections\ArrayCollection; use FOS\RestBundle\View\View; use JMS\DiExtraBundle\Annotation\Service; use JMS\DiExtraBundle\Annotation\InjectParams; use JMS\SecurityExtraBundle\Annotation\PreAuthorize; use Knp\Component\Pager\Pagination\PaginationInterface; use MyApp\Manager\GradePointAverageManager; use MyApp\Model\GradePointAverage; use MyApp\Model\GradePointAverage\AssessmentValue; /** * @Service("grade_point_average_controller", parent="app.controller.abstract") * @Route("/gpa", service="grade_point_average_controller") */ class GradePointAverageController extends BaseController { /** * @var GradePointAverageManager */ private $gradePointAverageManager; private $logger; /** * @InjectParams * * @param GradePointAverageManager $gradePointAverageManager * @param LoggerInterface $logger */ public function __construct(GradePointAverageManager $gradePointAverageManager, LoggerInterface $logger) { $this->gradePointAverageManager = $gradePointAverageManager; $this->logger = $logger; } // ... /** * @Route("/{id}", name="gpa.edit", requirements={"id" = "\d+"}) * @Method("PUT") * * @param Request $request * @param GradePointAverage $gpa * @return View */ public function editAction(Request $request, GradePointAverage $gpa) { $form = $this->formFactory->createNamed(null, 'gpa', $gpa, [ 'method' => 'PUT', ]); $form->handleRequest($request); foreach ($gpa->getAssessmentValues() as $av) { $this->logger->info('GPA ID PREVALIDATE IN CONTROLLER:'.$gpa->getId()); $this->logger->info('PREVALIDATE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:'.$av->getAssessment()->getId()); $this->logger->info('PREVALIDATE IN CONTROLLER ASSESSMENT VAL POINTS:'.$av->getValue()); } /* // try reversing the order of the collection to see if that helps $assessmentVals = $gpa->getAssessmentValues()->toArray(); $reversed = array_reverse($assessmentVals); $reversedColl = new ArrayCollection($reversed); $gpa->setAssessmentValues($reversedColl); */ if ($form->isValid()) { foreach ($gpa->getAssessmentValues() as $av) { $this->logger->info('GPA ID PRESAVE IN CONTROLLER:'.$gpa->getId()); $this->logger->info('PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:'.$av->getAssessment()->getId()); $this->logger->info('PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:'.$av->getValue()); } $this->gradePointAverageManager->save($gpa); return new View($gpa, 204); } return new View($form); } // ... }
    CREATE TABLE gpa_assessment_value
    (
        id                     bigserial       NOT NULL,
        grade_point_average_id bigint        NOT NULL,
        assessment_id          bigint        NOT NULL,
        point_value            numeric(4, 2) NOT NULL,
    
        PRIMARY KEY (id,assessment_id, grade_point_average_id),
        FOREIGN KEY (assessment_id) REFERENCES assessment,
        FOREIGN KEY (grade_point_average_id) REFERENCES grade_point_average
    );