问题描述
/**
* @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上有值):
如果我也选择了'c'实体:->select('cla,c')
它可以工作,但会再次传递整个'Cliente'实体:
我在这里做错什么了,如何在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;
}
但是现在我得到了这个错误:
有什么想法吗?预先感谢!
解决方法
好的,这里有多个起作用,它们的组合会导致您的问题。
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。延迟加载的实体/关系
最后,您在这里看到的是
正如msg所指出的,fk实体的字段返回为null(并在db上具有值)
实际上是-教义如何处理延迟加载的数据。当您加载具有很多关系的实体时,您不希望所有不需要的相关实体也都被加载(也许它们的相关实体也可以)。但是,从方便和逻辑的角度来看,当您访问$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