symfony - 为什么 Doctrine 试图通过关系插入新实体?
问题描述
我拥有以下 Doctrine 实体EntityA
:
<?php
class EntityA
{
/**
* @ORM\ManyToOne(targetEntity="EntityB" , inversedBy="propertyA")
*/
private $propertyB;
// ....
public function setEntityB(EntityB $entBRef)
{
$this->propertyB = $entBRef;
}
}
class EntityB
{
/**
* @ORM\OneToMany(targetEntity="EntityA", mappedBy="propertyB")
*/
private $propertyA;
// ....
}
我也有一RepositoryB
堂课:
<?php
class RepositoryB extends \Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository
{
public function __construct(\Doctrine\Common\Persistence\ManagerRegistry $registry)
{
parent::__construct($registry, EntityB::class);
}
}
然后在我的课堂上,我试图坚持一个EntityA
对象:
<?php
class SomeService
{
/** @var RepositoryB */
private $repositoryB;
/** @var EntityManagerInterface */
private $entityManager;
public function __construct(RepositoryB $repositoryB, EntityManagerInterface $entityManager)
{
$this->repositoryB = $repositoryB;
$this->entityManager = $entityManager;
}
public function testSomething()
{
$entBRef = $this->repositoryB->find(1);
$newEnt = (new EntityA());
$newEnt->setEntityB($entBRef);
$this->entityManager->persist($newEnt);
$this->flush();
}
}
但我有以下错误:
通过关系“EntityA#propertyB”找到了一个新实体,该关系未配置为实体的级联持久操作:EntityB。要解决此问题:在此未知实体上显式调用 EntityManager#persist() 或配置级联
这是我的doctrine.yaml
文件的样子:
parameters:
env(DATABASE_URL_WRITE): ''
services:
gedmo.listener.timestampable:
class: Org\CompBundle\Gedmo\Timestampable\TimestampableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ "@annotation_reader" ] ]
gedmo.listener.loggable:
class: Gedmo\Loggable\LoggableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ "@annotation_reader" ] ]
gedmo.listener.deleteable:
class: Gedmo\SoftDeleteable\SoftDeleteableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ '@annotation_reader' ] ]
doctrine:
dbal:
default_connection: default
types:
microseconds: Org\CompBundle\DBAL\Types\DateTimeMicrosecondsType
connections:
read_only:
url: '%env(resolve:DATABASE_URL_READ)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
default:
url: '%env(resolve:DATABASE_URL_WRITE)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
orm:
auto_generate_proxy_classes: '%kernel.debug%'
default_entity_manager: default
entity_managers:
filters:
filters:
softdeleteable:
class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
enabled: true
read_only:
connection: read_only
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
mappings:
gedmo_loggable:
type: annotation
prefix: Gedmo\Loggable\Entity
dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Loggable/Entity"
alias: GedmoLoggable
is_bundle: false
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/vendor/org/comp-bundle/Entity'
prefix: 'Org\CompBundle\Entity'
alias: Drm
dql:
datetime_functions:
timetosec: DoctrineExtensions\Query\Mysql\TimeToSec
timediff: DoctrineExtensions\Query\Mysql\TimeDiff
now: DoctrineExtensions\Query\Mysql\Now
numeric_functions:
rand: DoctrineExtensions\Query\Mysql\Rand
default:
connection: default
filters:
softdeleteable:
class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
enabled: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
gedmo_loggable:
type: annotation
prefix: Gedmo\Loggable\Entity
dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Loggable/Entity"
alias: GedmoLoggable
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/vendor/org/comp-bundle/Entity'
prefix: 'Org\CompBundle\Entity'
alias: Drm
dql:
datetime_functions:
timetosec: DoctrineExtensions\Query\Mysql\TimeToSec
timediff: DoctrineExtensions\Query\Mysql\TimeDiff
now: DoctrineExtensions\Query\Mysql\Now
numeric_functions:
rand: DoctrineExtensions\Query\Mysql\Rand
最后但并非最不重要的一点是,我确实有以下设置org\compbundle\Resources\services.yaml
:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
bind:
$logger: '@Psr\Log\LoggerInterface'
Org\CompBundle\Repository\:
resource: '../../Repository/{Case,Main}'
exclude: ['../../Repository/**/*Interface.php']
tags: ['doctrine.repository_service']
Org\CompBundle\Interfaces\Queues\QueueRepositoryInterface:
class: Org\CompBundle\Repository\DrmCase\QueueRepository
Org\CompBundle\Interfaces\Cases\CasesRepositoryInterface:
class: Org\CompBundle\Repository\DrmCase\CasesRepository
Doctrine\Common\Persistence\ManagerRegistry: '@doctrine'
到目前为止我做了什么?
- 在这里阅读很多关于 SO 的帖子,导致相同的解决方案添加
cascade={"persist"}
到我所做的拥有方,我必须说没有工作。 - 阅读 Doctrine 文档,寻找关于我的实体定义的错误,但到目前为止,我觉得一切都很好。
我确实发现这很有帮助,但我一直在问自己,为什么 Doctrine 会仅仅因为引用而尝试插入和存在实体?有什么办法可以让这个工作吗?
我不希望 EntityB 持久化或更新,它已经存在。我确实希望新的 EntityA 对 EntityB 中的现有记录有一个 FK。
解决方案
在周末,一位工作同事确实非常努力地找出导致问题的原因,经过大量时间调试 Doctrine 和 UoW 的工作原理后,他发现了问题所在:Doctrine 和存储库模式的工作原理。让我稍微解释一下。
如果您仔细检查并深入了解 Doctrine,您会注意到以下代码如何导致两个不同的实体管理器:
public function __construct(RepositoryB $repositoryB, EntityManagerInterface $entityManager)
{
$this->repositoryB = $repositoryB; // spin his own EM
$this->entityManager = $entityManager; // this is a new EM object
}
所以:
$entBRef = $this->repositoryB->find(1);
正在通过另一个 EM 获取,并且由于我$this->entityManager
用于持久化/刷新最近创建的entityA
对象,因此 Doctrine 将$entBRef
其视为必须持久化的新实体。
我们的解决方案是通过我不太喜欢的同一个存储库类获取所有内容,因为我们正在查询不属于那里的东西,并且我们不得不使用相同的 EM 持久化/刷新。
如果您有任何其他解决方案,非常欢迎。
推荐阅读
- c# - 如何在mySql中不指定数据库名的情况下获取当前数据库的所有列
- flutter - 为什么我的 Flutter 页面有时无法在发布版本中完全呈现?
- javascript - 来自 3rd 方库的 Next/image 组件引发 next.config.js 错误
- haskell - 函数 g = (.).(.) 的类型是什么?
- java - 如何使用 shiftColumns 方法删除列
- flutter - 如何在 Flutter 中处理动态生成的 ListView 的选定项
- node.js - AWS Codebuild 节点版本更新不起作用
- reactjs - 如何使用 KotlinJS 和 React 提交表单?
- webpack - “编译”参数必须是 Office 加载项中编译错误的实例
- python - 从python函数分配可变数量的返回的优雅方法