外键字段在 doctrine querybuilder 上返回 null

Foreign key fields returning null on doctrine querybuilder

我有这个查询:

    /**
     * @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 实体的字段 return 转储为 null(并在数据库上具有值)时:

如果我也 select 'c' 实体:->select('cla, c') 它有效但再次通过整个 'Cliente' 实体:

我在这里做错了什么,我如何获得第一张图像的 return 以及客户字段中的实际值?

*

编辑 11 月 6 日

* 我按照 Jakumi 所说的做了:

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。你 select 首先输入了错误的实体

你的函数(据我所知)在 ClienteRepository 中(这里猜测),因为显然 $this->createQueryBuilder('c') 中的 c 与它相关联。如果你想要 Classificao-Objects,你应该把那个函数放到 ClassificaoRepository 中。您的左连接会导致问题,您的行看起来像这样(忽略此处的 select):

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。学说的 joins/relations 及其对结果集的影响

->leftJoin('App\Entity\Classificacao','cla', 'with', 'cla.cliente = c.id') 的问题是,那个学说不知道,那个 cla.cliente = c.id 意思是“给我 那个 客户的分类”,但是它会处理它作为“一些自定义连接添加了一些自定义条件”。这就是为什么即使将 c 添加到 select 也不会为您提供正确的结构,真的。但是,Doctrine 绝对将 ->leftJoin('c.classificaos', 'cla') 识别为“我想要 that Client's Classificao” - 但是,您必须定义该关系的反面才能使其起作用(我假设这里是OneToMany,如果是one-to-one):

可以换成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。 lazy-loadedentities/relations

最后,你在那里看到的是

the fields of the fk's entity return as null (and have value on the db)

实际上 - 正如 msg 指出的那样 - 学说如何处理 lazy-loaded 数据。当您加载大量具有大量关系的实体时,您不希望在不需要时也加载所有相关实体(可能还有它们的相关实体)。但是,从方便和逻辑的角度来看,当您访问 $classificao->getCliente() 时,您不希望结果既不是 id 也不是 null (因为未加载对象),您期望的是 Cliente 对象。 Doctrine 通过添加一个代理对象来解决这个问题,它是一个 drop-in 替代包装器(可以这么说),它将充当占位符。

在输出中,您可以通过 class 名称 Cliente^ 末尾的小 ^ 来识别代理对象(尽管实际的 class 名称不同,您可以将鼠标悬停在它上面以查看)以及 __isInitialized__ 属性,在您的示例中设置为 false。 属性 表示 wrapped 对象尚未加载。

现在,学说的代理对象足够“聪明”,它们拥有已知的数据,即 id(因为它内部存储在 Classificao-object 中),在本例中 ativo 因为你 select 对此进行了编辑。

只要您尝试访问上面的任何其他 属性,例如 $classificao->getCliente()->getCnpj(),就会从数据库加载该对象,并填充所有缺失的字段。只有少数事情不会触发(后期)lazy-loading,dumping 就是其中之一,序列化也可能不会触发它,还有一些其他事情。

您可以试试看是否正确:

dump($classificao); // output with proxy not initialized
$classificao->getCliente()->getCnpj();
dump($classificao); // output with proxy initialized and all values loaded

加载对象时,代理将充当代理(duh)/包装器,即对其进行的每个 call/access 都将转发到包装的(原始)对象。

您可以通过将 fetch="EAGER" 属性添加到注释中(如 the question/answer msg mentioned ),它将始终*加载相关实体(*适用限制)。

关于 n+1 问题的旁注

关于 lazy-loading 的附注:在某些情况下,您可能会在查询中显式触发预加载以避免 n+1 问题,即:您使用一个查询加载大量 Classificao 对象然后访问它们的每个 ->getCliente() 方法,每个方法都会触发数据库查询,因此您首先查询 Classificao 对象,然后 n 查询 Cliente 对象(因此名字 n+1)。通往 select 的方法 - 在上面的 1. 中描述 - 应该 避免这种情况(不太确定,tbh)。无论如何:考虑阅读 ORM 性能陷阱以避免:https://tideways.com/profiler/blog/5-doctrine-orm-performance-traps-you-should-avoid