外键字段在主义querybuilder上返回null 1您首先选择了错误的实体 2学说的联接/关系以及这如何影响结果集 3延迟加载的实体/关系关于n + 1问题的旁注

问题描述

我有这个查询

    /**
     * @return []
     */

    public function classificacao(): array
    {
        $qb = $this->createqueryBuilder('c')
        ->select('cla')
        ->andWhere('c.ativo =1')
        ->leftJoin('App\Entity\Classificacao','cla','with','cla.cliente = c.id')
        ->orderBy('c.codigo')
        ->getQuery();

        return $qb->execute();

    }

但是当我转储它时,fk实体的字段返回为null(并且在db上有值):

enter image description here

如果我也选择了'c'实体:->select('cla,c')它可以工作,但会再次传递整个'Cliente'实体:

enter image description here

在这里做错什么了,如何在cliente字段中获得带有实际值的第一张图片的返回?

*

EDIT 06 / nov

* 我按照贾科米的话做了:

Classificacao.PHP

/**
 * @var \Cliente
 *
 * @ORM\ManyToOne(targetEntity="Cliente",inversedBy="classificacao",cascade={"persist"})
 * @ORM\JoinColumn(name="cliente_id",referencedColumnName="id")
 */
private $cliente;

Cliente.PHP

    /**
* @ORM\OnetoMany(targetEntity="App\Entity\Classificacao",mappedBy="cliente")
*/
private $classificacao;

...

public function getClassificacao()
{
    return $this->classificacao;
}

但是现在我得到了这个错误

enter image description here

有什么想法吗?预先感谢!

解决方法

好的,这里有多个起作用,它们的组合会导致您的问题。

1。您首先选择了错误的实体

(据我所知)您的函数位于ClienteRepository中(在此处猜测),因为显然c中的$this->createQueryBuilder('c')与之相关。如果要使用Classificao-对象,则应将该函数放入ClassificaoRepository中。您的左联接将导致问题,即您的行如下所示(忽略此处的选择):

cliente1,classificao1
cliente2,null                  --because there is nothing linked
cliente3,null                  --because there is nothing linked
cliente4,classificao4
...

但是您只需要Classificao个对象,因此您将获得第二列,其中包含一些null。这是由于leftJoin允许第二列为空。

将其放入ClassificaoRepository或显式更改from(和leftJoin)都可以解决此问题。由于恕我直言,该函数属于ClassificaoRepository内部,因此我不会详细介绍更改from的情况,而是告诉您将此代码放入ClassificaoRepository中的函数中:

// in ClassificaoRepository
return $this->createQueryBuilder('cla')
     ->leftJoin('cla.cliente','c')     // <-- this works btw
     ->andWhere('c.ativo=1')
     ->orderBy('c.codigo')
     ->addSelect('c')       // <-- technically optional,but might improve performance
     ->getQuery()
     ->getResult();

(在查询中使用->innerJoin会删除null ,顺便说一句,它是间接实现的)

2。学说的联接/关系以及这如何影响结果集

->leftJoin('App\Entity\Classificacao','cla','with','cla.cliente = c.id')的问题在于,该学说不知道,cla.cliente = c.id的意思是“让我知道客户的分类”,但是它将被视为“某种习惯”联接添加了一些自定义条件”。这就是为什么即使将c添加到选择中也不会真正为您提供正确的结构的原因。但是,Doctrine绝对将->leftJoin('c.classificaos','cla')识别为“我想要那个客户的分类”-但是,您必须定义该关系的反面才能起作用(我在这里假设OneToMany,您可以将它与OneToOne一对一交换):

// in your src/Entity/Cliente.php

/**
 * @ORM\OneToMany(targetEntity=Classificao::class,mappedBy="cliente")
 */
private $classificaos; // if one-to-one,name it 'classificao'

// + getter

将该字段添加到Cliente实体并使用->addSelect('c') 应该也会加载相关实体。 (没有承诺)

3。延迟加载的实体/关系

最后,您在这里看到的是

fk实体的字段返回为null(并在db上具有值)

正如msg所指出的,

实际上是-教义如何处理延迟加载的数据。当您加载具有很多关系的实体时,您不希望所有不需要的相关实体也都被加载(也许它们的相关实体也可以)。但是,从方便和逻辑的角度来看,当您访问$classificao->getCliente()时,您并不希望结果不仅是id也不是null(因为未加载对象),所以您希望{{ 1}}对象。 Doctrine通过添加一个代理对象来解决此问题,该代理对象是一个可以用作占位符的嵌入式替换包装(可以这么说)。

在输出中,您可以通过类名Cliente末尾的小^来标识代理对象(尽管实际的类名有所不同,您可以将鼠标悬停在其上以查看该内容)并通过Cliente^属性(在您的示例中设置为__isInitialized__)。该属性表明包装的对象尚未加载。

现在,主义的代理对象已经足够“聪明”了,它们拥有已经知道的数据,即id(因为它是内部存储在false对象中)和本例中的Classificao因为您对此进行了选择。

一旦您尝试访问其上的任何其他属性(例如ativo),该对象将从数据库中加载,所有丢失的字段将被填充。只有少数几件事不会触发(延迟)延迟加载,$classificao->getCliente()->getCnpj()是其中之一,序列化也可能不会触发此延迟,还有其他一些事情。

您可以尝试这样做以查看其正确性:

dump

在加载对象时,代理将充当代理(duh)/包装器,即,对它的每次调用/访问都将转发到包装的(原始)对象。

您可以通过在注释中添加dump($classificao); // output with proxy not initialized $classificao->getCliente()->getCnpj(); dump($classificao); // output with proxy initialized and all values loaded 属性(如在the question/answer msg mentioned中)来强制进行预先加载(即,在加载实体A时同时加载与其相关的实体B),总是*加载相关实体(*确实有限制)。

关于n + 1问题的旁注

关于延迟加载的旁注:在某些情况下,您可以-在某些情况下显式触发急切加载以避免n + 1问题,即:您通过一个查询加载许多Classificao对象,然后访问每个的fetch="EAGER"方法中的每个方法都会触发一个数据库查询,因此您具有第一个查询Classificao对象和 n 查询Cliente对象(因此名称为n + 1)。选择方法-如上文1.中所述-应该避免这种情况(不确定,tbh)。无论如何:请考虑阅读ORM性能陷阱,以避免:https://tideways.com/profiler/blog/5-doctrine-orm-performance-traps-you-should-avoid