简体   繁体   English

Doctrine - 实体类中的水合物集合

[英]Doctrine - Hydrate collection in Entity class

I have a problem regarding a bi-directional OneToMany <-> ManyToOne relationship between my entities Device and Event .我有一个关于我的实体DeviceEvent之间的双向OneToMany <-> ManyToOne关系的问题。 This is how mapping looks:这是映射的样子:

// Device entity
    /**
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\Event", mappedBy="device")
     */
    protected $events;


// Event entity
    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Device", inversedBy="events")
     */
    protected $device;

The problem comes because Device is a Single Table Inheritance entity问题来了,因为Device单表继承实体

 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="device_class_type", type="string")

and each time I fetch and iterate over some Event entities, then $device is always eagerly fetched.每次我获取和迭代一些Event实体时,总是急切地获取$device This happens because it's a STI entity as reported in related documentation发生这种情况是因为它是相关文档中报告的 STI 实体

There is a general performance consideration with Single Table Inheritance: If the target-entity of a many-to-one or one-to-one association is an STI entity, it is preferable for performance reasons that it be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).单表继承有一个一般的性能考虑:如果多对一或一对一关联的目标实体是 STI 实体,出于性能原因,它最好是继承中的叶实体层次结构,(即没有子类)。 Otherwise Doctrine CANNOT create proxy instances of this entity and will ALWAYS load the entity eagerly.否则 Doctrine 无法创建该实体的代理实例,并且总是会急切地加载该实体。

Now there's another entity called Gateway which has relationships with both Device and Event :现在有另一个名为Gateway的实体,它与DeviceEvent都有关系:

/**
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Device", mappedBy="gateway")
 */
protected $devices;

/**
 * @ORM\OneToMany(targetEntity="targetEntity="AppBundle\Entity\Event", mappedBy="gateway")
 */
protected $events;


public function getEvents(): Collection
{
    return $this->events;
}

And of course each time I iterate over $gateway->getEvents() all related events devices are fetched eagerly.当然,每次我遍历$gateway->getEvents()都会急切地获取所有相关的事件设备。 This happens even if I don't get any $device info - an empty foreach is enough to let Doctrine execute 1 query for each object to fetch related $device即使我没有得到任何$device信息,也会发生这种情况 - 一个空的foreach足以让 Doctrine 为每个对象执行 1 个查询以获取相关的$device

foreach ($gateway->getEvents() as $event) {} 

Now I know I could use QueryBuilder to set a different hydration mode avoiding $device fetching现在我知道我可以使用QueryBuilder设置不同的水合模式,避免$device获取

return $this->getEntityManager()->createQueryBuilder()
            ->select('e')
            ->from('AppBundle:Event', 'e')
            ->where('e.gateway = :gateway')
            ->setParameter('gateway', $gateway)
            ->getQuery()->getResult(Query::HYDRATE_SIMPLEOBJECT);

but I would like to do it somehow directly in Gateway entity.但我想直接在Gateway实体中以某种方式进行。

So is it possible hydrate Gateway->events directly in Gateway entity class?那么是否可以直接在Gateway实体类中水合物Gateway->events

I'd suggest you couple of options to consider here.我建议你在这里考虑几个选择。

1) As per Doctrine's documentation you can use fetch="EAGER" to hint Doctrine that you want the relation eagerly fetched whenever the entity is being loaded: 1) 根据Doctrine 的文档,您可以使用fetch="EAGER"来提示 Doctrine 您希望在加载实体时立即获取关系:

/**
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Device", mappedBy="gateway", fetch="EAGER")
 */
protected $devices;

If used carefully this can save you from firing additional queries upon iteration but has it's own drawbacks either.如果谨慎使用,这可以避免您在迭代时触发额外的查询,但也有其自身的缺点。

If you start to extensively use forced eager loading you may find yourself in situation where loading an entity to read a simple attribute from it will result in loading tens and even hundreds of relations.如果您开始广泛使用强制预加载,您可能会发现自己处于加载实体以从中读取简单属性将导致加载数十甚至数百个关系的情况。 This may not look as bad from SQL point of view (perhaps a single query) but remember that all the results will be hydrated as objects and attached to the Unit Of Work to monitor them for changes.从 SQL 的角度来看,这可能看起来并没有那么糟糕(可能是单个查询),但请记住,所有结果都将作为对象进行水合,并附加到工作单元以监视它们的变化。

2) If you are using this for reporting purposes (eg display all events for a device) then it's better not to use entities at all but to request array hydration from Doctrine. 2)如果您将其用于报告目的(例如显示设备的所有事件),那么最好不要使用实体,而是从 Doctrine 请求阵列水化。 In this case you will be able to control what gets into the result by explicitly joining the relation (or not).在这种情况下,您将能够通过显式连接(或不连接)来控制进入结果的内容。 As an added benefit you'll skip the expensive hydration and monitoring by the UoM as it's unlikely to modify entities in such a case.作为一个额外的好处,您将跳过昂贵的水化和 UoM 监控,因为在这种情况下不太可能修改实体。 This is considered a "best practice" too when using Doctrine for reporting.在使用 Doctrine 进行报告时,这也被认为是“最佳实践”。

You'll need to write your own hydration method您需要编写自己的补水方法

You have a cyclical reference where one of those nodes (Device) will force a FETCH EAGER .您有一个循环引用,其中一个节点(设备)将强制执行FETCH EAGER Making matters worse, one of those nodes (Gateway) is acting like ManyToMany join table between the other two, resulting in FETCH EAGER loading everything in an near-infinite loop (or at least large blocks of related data).更糟糕的是,其中一个节点(网关)的行为就像其他两个节点之间的多对多连接表,导致FETCH EAGER在近乎无限的循环中加载所有内容(或至少是大块的相关数据)。

 +──<   OneToMany
 >──+   ManyToOne
 >──<   ManyToMany
 +──+   OneToOne

       ┌──────< Gateway >──────┐
       │                       │
       +                       +
     Event +──────────────< Device*

As you can see, when device does an EAGER fetch, it will collect many Gateways , thus many Events , thus many Devices , thus many more Gateways , etc. Fetch EAGER will keep going until all references are populated.如您所见,当设备执行 EAGER 提取时,它将收集许多Gateways ,因此许多Events ,因此许多Devices ,因此更多Gateways等。 Fetch EAGER将继续进行,直到填充所有引用。

Prevent "EAGER" Hydration, by building your own Hydrator.通过构建您自己的水合器来防止“急切”水合。

Building your own hydrator will require some careful data-manipulation, but will likely be somewhat simple for your use case.构建您自己的水化器需要一些仔细的数据操作,但对于您的用例来说可能会有些简单。 Remember to register your hydrator with Doctrine, and pass it as an argument to $query->execute([], 'GatewayHydrator');请记住使用 Doctrine 注册您的 hydrator,并将其作为参数传递给$query->execute([], 'GatewayHydrator');

class GatewayHydrator extends DefaultEntityHydrator
{
    public function hydrateResultSet($stmt)
    {
        $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $class = $this->em->getClassMetadata(Gateway::class);
        $gateway = $class->newInstance();

        $gateway->setName($data[0]['gateway_name']); // example only

        return $gateway;
    }
}

Alternatively, Remove the Mapped Field from Device to Gateway或者,删除从设备到网关的映射字段

Removing the $gateway => Gateway mapping from Device , and mappedBy="gateway" from the Gateway->device mapping, Device would effectively become a leaf from Doctrine's perspective.Device移除$gateway => Gateway映射,从Gateway->device映射中Gateway->device mappedBy="gateway" ,从 Doctrine 的角度来看,Device 将有效地变成一片叶子。 This would avoid that reference loop, with one drawback: the Device->gateway property would have to be manually set (perhaps in the Gateway and Event setDevice methods).这将避免引用循环,但有一个缺点:必须手动设置 Device->gateway 属性(可能在 Gateway 和 Event setDevice方法中)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM