Laravel Eloquent ORM - 在选择两个关系均不存在的所有模型时,生成的 SQL 和查询构建器结果不匹配

Laravel Eloquent ORM - Mismatch between generated SQL and querybuilder results when selecting all models where two relationships are both absent

假设我有一个模型 Foobar,其关系名为 bazquuxFoobar 属于 BazBaz 有一个 Foobar。这意味着 Foobar table 中有一个外键列用于 baz_id。这些关系在模型中被正确定义。如果相关,此模型缓存库用于两个模型:https://github.com/GeneaLabs/laravel-model-caching

我想查询所有Foobar没有任何关系;这意味着我只想 select Foobar bazquux 关系都不存在。我是这样做的:

Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->get();

现在假设 $foobar 是一个没有关系的 Foobar 实例,而 $baz 是一个新创建的 Baz 实例。我这样将两者联系起来:

$baz->foobar()->associate($foobar);
$baz->save(); 

现在,我运行再次进行上述查询。 T$foobar 表示的行仍然出现在结果中,尽管它不应该出现,因为它现在具有 non-null baz 关系。我的问题是:为什么会发生这种情况,我该如何解决?

我调试这个的时候在artisan控制台玩了一下。在同一个 artisan session:

Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->get();
// This gets a collection that is not empty, the first item is a Foobar instance that definitely has a baz

Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->first();
// This is null (?)

Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->count();
// This is 0 (?)

Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->get()->get(0)->baz;
// This gets the Baz object attached to the first instance

对 double-check SQL 是正确的:

Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->get()->toSql();
// This outputs the generated raw SQL.

我将原始 SQL 输入到 SQL 客户端并得到了正确的结果。

调试时我也尝试/检查过:

  1. $foobar->touch() - 没有改变结果。
  2. 确保 API 端点未被浏览器、Cloudflare 或其他任何东西缓存

您可能想试试这个:

Foobar::whereDoesntHave('baz')->whereDoesntHave('guux')->get();

我发现问题源于这些模型上使用的模型缓存库:https://github.com/GeneaLabs/laravel-model-caching

当我 运行 库的完全无效 Foobar 缓存的命令时,它解决了这个问题。在这种情况下,库似乎没有正确地使缓存无效,但如果不深入研究库,我无法解释原因。他们的自述文件表明这可能是一个已知问题。

php artisan modelCache:clear --model='App\Models\Foobar'

为了解决这个问题,我想到了几个选项:

  1. 以编程方式运行 每当关系更新时清除模型缓存的 artisan 命令。
  2. 重写查询以绕过模型缓存。
  3. 寻找不同的模型缓存库。

我们选择从这些模型中完全删除模型缓存;我们能够这样做是因为这是一个“简单”的过早优化,但可能不是必需的。我们的理由是我们现在将删除过早的优化,只有在它引起问题时再担心它。