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

Posted

技术标签:

【中文标题】Doctrine ORM:使用由外键组成的复合主键持久化集合【英文标题】:Doctrine ORM: Persisting collections with Composite Primary Keys composed of Foreign Keys 【发布时间】:2014-01-18 15:16:00 【问题描述】:

我猜这是一个 Doctrine 错误(我已经在 J​​IRA 问题跟踪器上提交了一个问题),但万一这只是用户错误,我决定在这里发布。

概要

将实体集合保存在具有复合主节点的连接表中 由 2 个外键和一个元数据字段组成的键在某些情况下会失败。代码基于此处的说明:Doctrine docs

问题详情

    成功:当 FOREIGN KEY 1 在要持久化的集合中的项目之间相同时, 并且 FOREIGN KEY 2 大于任何现有 PRIMARY KEY 中的 FOREIGN KEY 2,实体 和集合中的相关实体正确持久化:

    示例:GPA“add val below”存在并具有评估值"assessment":6,"value":4 我们将尝试添加一个新的评估值,其中评估 ID > 任何现有的评估值 GPA 评估值“在下方添加 val”

    请求负载:"name":"add val below","courses":[],"assessmentValues":["assessment":6,"value":4,"assessment":7,"value":3]

    调试日志:

    [2013-12-31 11:48:48] app.INFO: GPA ID PRESAVE 在控制器中:9 [] [] [2013-12-31 11:48:48] app.INFO:保存在控制器评估 VAL 评估 ID:7 [] [] [2013-12-31 11:48:48] app.INFO: PRESAVE 在控制器评估 VAL POINTS:3 [] [] [2013-12-31 11:48:48] app.INFO: GPA ID PRESAVE 在控制器中:9 [] [] [2013-12-31 11:48:48] app.INFO:保存在控制器评估 VAL 评估 ID:6 [] [] [2013-12-31 11:48:48] app.INFO: PRESAVE 在控制器评估 VAL POINTS:4 [] [] [2013-12-31 11:48:48] 教义.调试:“开始交易”[][] [2013-12-31 11:48:48]教义.DEBUG:插入gpa_assessment_value(point_value,grade_point_average_id,assessment_id)值(?,?,?)“1”:3,“2”:“9”, 3":"7" [] [2013-12-31 11:48:48] 教义。调试:更新 gpa_assessment_value SET point_value = ?在哪里grade_point_average_id =? AND 评估 ID = ? [4,9,6] [] [2013-12-31 11:48:48] 学说.DEBUG: "COMMIT" [] []

    失败:当集合中的项之间的 FOREIGN KEY 1 相同,并且 FOREIGN KEY 2 小于任何现有的 FOREIGN KEY 2 时,工作单元会尝试插入现有实体并且不会对新实体进行操作。

    示例:GPA“add val above”存在并具有评估值"assessment":8,"value":2 我们将尝试添加一个新的评估值,其中assessment_id

    请求负载:"name":"add val above","courses":[],"assessmentValues":["assessment":6,"value":4,"assessment":8,"value":2]

    调试日志:

    [2013-12-31 11:53:59] app.INFO: GPA ID PRESAVE IN CONTROLLER:10 [] [] [2013-12-31 11:53:59] app.INFO:保存在控制器评估 VAL 评估 ID:8 [] [] [2013-12-31 11:53:59] app.INFO: PRESAVE 在控制器评估 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:保存在控制器评估 VAL 评估 ID:6 [] [] [2013-12-31 11:53:59] app.INFO: PRESAVE 在控制器评估 VAL POINTS:4 [] [] [2013-12-31 11:53:59] 学说。调试:“开始交易”[][] [2013-12-31 11:53:59] 教义.调试:插入 gpa_assessment_value(point_value,grade_point_average_id,assessment_id)值(?,?,?)“1”:2,“2”:“10”, 3":"8" [] [2013-12-31 11:53:59] 教义.调试:“回滚”[][] [2013-12-31 11:53:59] request.CRITICAL:未捕获的 php 异常 Doctrine\DBAL\DBALException:“执行'INSERT INTO gpa_assessment_value (point_value,grade_point_average_id,assessment_id) VALUES (?, ?, ?) ' 带参数 [2, "10", "8"]: SQLSTATE [23505]:唯一违规:7 错误:重复键值违反唯一约束“gpa_assessment_value_pkey”

代码

迁移.sql

创建表评估 ( id bigserial NOT NULL, scale_id bigint 非空, 标题 varchar NOT NULL, 传递布尔值 NOT NULL, 排名int, 主键(id) ); 创建表评估_规模 ( id bigserial NOT NULL, 名称 varchar NOT NULL, 主键(id) ); -- ... 创建表grade_point_average ( id bigserial NOT NULL, 名称 varchar NOT NULL, additional_credit_allowance numeric(4, 2), 主键(id) ); -- ... 创建表 gpa_assessment_value ( grade_point_average_id bigint 非空, 评估id bigint 非空, point_value numeric(4, 2) NOT NULL, 主键(assessment_id,grade_point_average_id), FOREIGN KEY (assesment_id) REFERENCES 评估, 外键 (grade_point_average_id) 参考 Grade_point_average );

模型/GradePointAverage.php

命名空间 MyApp\Model; 使用 Doctrine\ORM\Mapping\Entity; 使用 Doctrine\ORM\Mapping\Id; 使用 Doctrine\ORM\Mapping\GeneratedValue; 使用 Doctrine\ORM\Mapping\Column; //... 使用 Doctrine\Common\Collections\Collection; 使用 Doctrine\Common\Collections\ArrayCollection; 使用 MyApp\Util\ConstructorArgs; 使用 MyApp\Model\GradePointAverage\AssessmentValue; // ... /** * @Entity("MyApp\Repository\GradePointAverageRepository") */ 班级 GradePointAverage 使用 ConstructorArgs; /** * @ID * @GeneratedValue * @Column(type="bigint") * * @var 整数 */ 私人 $id; // ... /** * @OneToMany(targetEntity="MyApp\Model\GradePointAverage\AssessmentValue", mappedBy="gradePointAverage", cascade="persist") * * @var 集合 */ 私人 $assesmentValues; // ... /** * @param 数组 $args */ 公共函数 __construct(数组 $args = []) $this->assessmentValues = new ArrayCollection; // ... $this->handleArgs($args); // ... /** * @return 集合 */ 公共函数 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 $assessmentValue) $this->assessmentValues->removeElement($assessmentValue); // ...

模型/GradePointAverage/AssessmentValue.php

命名空间 MyApp\Model\GradePointAverage; 使用 Doctrine\ORM\Mapping\Entity; 使用 Doctrine\ORM\Mapping\Table; 使用 Doctrine\ORM\Mapping\Column; 使用 Doctrine\ORM\Mapping\Id; 使用 Doctrine\ORM\Mapping\GeneratedValue; 使用 Doctrine\ORM\Mapping\ManyToOne; 使用 Doctrine\ORM\Mapping\JoinColumn; 使用 MyApp\Model\GradePointAverage; 使用 MyApp\Model\Assessment; 使用 MyApp\Util\ConstructorArgs; /** * @Entity("MyApp\Repository\GradePointAverage\AssessmentValueRepository") * @Table("gpa_assessment_value") */ 类评估值 使用 ConstructorArgs; /** * @ID * @ManyToOne(targetEntity="MyApp\Model\GradePointAverage") */ 私人 $gradePointAverage; /** * @ID * @ManyToOne(targetEntity="MyApp\Model\Assessment") */ 私人$评估; /** * @Column("point_value") * * @var 浮动 */ 私人价值; /** * @param 数组 $args */ 公共函数 __construct(数组 $args = []) $this->handleArgs($args); /** * @return GradePointAverage */ 公共函数 getGradePointAverage() 返回 $this->gradePointAverage; /** * @param GradePointAverage $gradePointAverage */ 公共函数 setGradePointAverage(GradePointAverage $gradePointAverage) $this->gradePointAverage = $gradePointAverage; /** * @return 评估 */ 公共函数 getAssessment() 返回 $this-> 评估; /** * @param 评估 $assesment */ 公共函数 setAssessment(Assessment $assessment) $this->评估 = $评估; /** * @return 浮点数 */ 公共函数 getValue() 返回 $this-> 值; /** * @param 浮动 $value */ 公共函数 setValue($value) $this->value = $value; /** * @return 评估量表 */ 公共函数 getAssessmentScale() 返回 $this->assessment->getScale();

模型/Assessment.php

命名空间 MyApp\Model; 使用 Doctrine\ORM\Mapping\Entity; 使用 Doctrine\ORM\Mapping\Id; 使用 Doctrine\ORM\Mapping\GeneratedValue; 使用 Doctrine\ORM\Mapping\Column; 使用 Doctrine\ORM\Mapping\ManyToOne; 使用 MyApp\Model\Assessment\Scale; 使用 MyApp\Util\ConstructorArgs; /** * @Entity("MyApp\Repository\AssessmentRepository") */ 班级评估 使用 ConstructorArgs; /** * @ID * @GeneratedValue * @Column(type="bigint") * * @var 整数 */ 私人 $id; // ... /** * @param 数组 $args */ 公共函数 __construct(数组 $args = []) $this->handleArgs($args); /** * @return 整数 */ 公共函数 getId() 返回 $this->id; // ...

存储库/GradePointAverageRepository.php

命名空间 MyApp\Repository; 使用 Doctrine\ORM\EntityRepository; // ... 使用 MyApp\Model\GradePointAverage; 类 GradePointAverageRepository 扩展 BaseRepository 实现 GradePointAverageRepositoryInterface // ... /** * @param GradePointAverage $gradePointAverage */ 公共函数保存(GradePointAverage $gradePointAverage) $this->getEntityManager()->persist($gradePointAverage); $this->getEntityManager()->flush();

存储库/GradePointAverage/AssessmentValueRepository.php

命名空间 MyApp\Repository\GradePointAverage; 使用 Doctrine\ORM\EntityRepository; 使用 MyApp\Model\GradePointAverage\AssessmentValue; 类 AssessmentValueRepository 扩展了 EntityRepository /** * @param AssessmentValue $assessmentValue */ 公共功能保存(评估值$评估值) $this->getEntityManager()->persist($assessmentValue); $this->getEntityManager()->flush();

经理/GradePointAverageManager.php

命名空间 MyApp\Manager; 使用 InvalidArgumentException; 使用 Symfony\Component\Validator\ValidatorInterface; 使用 JMS\DiExtraBundle\Annotation\Service; 使用 JMS\DiExtraBundle\Annotation\InjectParams; 使用 JMS\SecurityExtraBundle\Annotation\PreAuthorize; 使用 Knp\Component\Pager\Pagination\PaginationInterface; 使用 MyApp\Repository\GradePointAverageRepository; 使用 MyApp\PaginationFactory\GradePointAveragePaginationFactory 接口; 使用 MyApp\Model\GradePointAverage; /** * @Service("grade_point_average_manager") */ 类 GradePointAverageManager /** * @var GradePointAverageRepository */ 私人 $gradePointAverageRepository; /** * @var GradePointAveragePaginationFactoryInterface */ 私人 $gradePointAveragePaginationFactory; /** * @var 验证器接口 */ 私人 $validator; /** * @InjectParams * * @param GradePointAverageRepository $gradePointAverageRepository * @param GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory * @param ValidatorInterface $validator */ 公共函数 __construct( GradePointAverageRepository $gradePointAverageRepository, GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory, 验证器接口 $validator ) $this->gradePointAverageRepository = $gradePointAverageRepository; $this->gradePointAveragePaginationFactory = $gradePointAveragePaginationFactory; $this->validator = $validator; /** * @PreAuthorize("isAllowedToManageTheGradePointAverage(#gradePointAverage)") * @param GradePointAverage $gradePointAverage * @throws InvalidArgumentException */ 公共函数保存(GradePointAverage $gradePointAverage) $violationList = $this->validator->validate($gradePointAverage); if ($violationList->count()) 抛出新的 InvalidArgumentException; $this->gradePointAverageRepository->save($gradePointAverage);

控制器/GradePointAverageController.php

命名空间 MyApp\Controller; 使用 Symfony\Component\HttpFoundation\Request; 使用 Symfony\Component\HttpFoundation\Response; 使用 Symfony\Component\HttpKernel\Log\LoggerInterface; 使用 Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 使用 Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; 使用 Doctrine\Common\Collections\ArrayCollection; 使用 FOS\RestBundle\View\View; 使用 JMS\DiExtraBundle\Annotation\Service; 使用 JMS\DiExtraBundle\Annotation\InjectParams; 使用 JMS\SecurityExtraBundle\Annotation\PreAuthorize; 使用 Knp\Component\Pager\Pagination\PaginationInterface; 使用 MyApp\Manager\GradePointAverageManager; 使用 MyApp\Model\GradePointAverage; 使用 MyApp\Model\GradePointAverage\AssessmentValue; /** * @Service("grade_point_average_controller", parent="app.controller.abstract") * @Route("/gpa", service="grade_point_average_controller") */ 类 GradePointAverageController 扩展 BaseController /** * @var GradePointAverageManager */ 私人 $gradePointAverageManager; 私人 $logger; /** * @InjectParams * * @param GradePointAverageManager $gradePointAverageManager * @param LoggerInterface $logger */ 公共函数 __construct(GradePointAverageManager $gradePointAverageManager, LoggerInterface $logger) $this->gradePointAverageManager = $gradePointAverageManager; $this->logger = $logger; // ... /** * @Route("/id", name="gpa.edit", requirements="id" = "\d+") * @Method("PUT") * * @param 请求 $request * @param GradePointAverage $gpa * @return 查看 */ 公共函数editAction(请求$request,GradePointAverage $gpa) $form = $this->formFactory->createNamed(null, 'gpa', $gpa, [ '方法' => 'PUT', ]); $form->handleRequest($request); foreach ($gpa->getAssessmentValues() as $av) $this->logger->info('GPA ID 在控制器中预验证:'.$gpa->getId()); $this->logger->info('在控制器评估 VAL 评估 ID 中预验证:'.$av->getAssessment()->getId()); $this->logger->info('在控制器评估 VAL 点中预验证:'.$av->getValue()); /* // 尝试颠倒集合的顺序,看看是否有帮助 $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); 返回新视图($gpa,204); 返回新视图($form); // ...

【问题讨论】:

你有从实体生成的sql还是从sql生成实体? 我正在手动编写 SQL,然后手动编写实体。我们没有在项目中使用生成器。 你能不能用理论命令行生成实体并检查你和他们的映射文件(只是为了确保你在实体中写的内容与理论生成的实体是一样的。) 我会看一下,但我不确定这会有什么不同,因为生成器必须引用现有架构或注释中的现有实体映射。 因此,为了实现这一点,我从以下说明书条目的 SQL 文件生成了映射:symfony.com/doc/current/cookbook/doctrine/…。它无法识别带有元数据的连接表。它只是尝试在GradePointAverageAssessment 之间映射ManyToMany,同时忽略点值的元数据字段。 【参考方案1】:

尝试向表 gpa_assessment_value 添加一个 Id,然后使用命令行生成您的实体。如果不添加 id 字段,从数据库映射 orm 文件不会创建 gpa_assessment_value.orm.yml。

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
);

【讨论】:

以上是关于Doctrine ORM:使用由外键组成的复合主键持久化集合的主要内容,如果未能解决你的问题,请参考以下文章

JPA,Composite Key 由外键和表列成员组成

与复合唯一约束的关系(symfony + 学说)

SQLAlchemy ORM 不能使用复合外键

Hibernate:insertable = false,updatable = false 属于涉及外键的复合主键星座中的哪里?

外键(FK_ 必须与引用的主键具有相同的列数

Doctrine2 Merge() 因复合索引而失败教义2错误?