我有一个方法需要从三个相关模型中提取信息。我有一个有效的解决方案,但我担心我仍然遇到N + 1查询问题(也在寻找解决方案,我可以检查我是否渴望正确加载)。
这三个模型是Challenge,Entrant,User。
挑战模型包含:
/**
* Retrieves the Entrants object associated to the Challenge
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
参与者模型包含:
/**
* Retrieves the Challenge object associated to the Entrant
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function challenge()
{
return $this->belongsTo('App\Challenge', 'challenge_id');
}
/**
* Retrieves the User object associated to the Entrant
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
和用户模型包含:
/**
* Retrieves the Entrants object associated to the User
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
我试图使用急切加载的方法如下所示:
/**
* Returns an array of currently running challenges
* with associated entrants and associated users
* @return array
*/
public function liveChallenges()
{
$currentDate = Carbon::now();
$challenges = Challenge::where('end_date', '>', $currentDate)
->with('entrants.user')
->where('start_date', '<', $currentDate)
->where('active', '1')
->get();
$challengesObject = [];
foreach ($challenges as $challenge) {
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all();
$entrantsObject = [];
foreach ($entrants as $entrant) {
$user = $entrant->user;
$entrantsObject[] = [
'entrant' => $entrant,
'user' => $user
];
}
$challengesObject[] = [
'challenge' => $challenge,
'entrants' => $entrantsObject
];
}
return $challengesObject;
}
我觉得我遵循了文档推荐的内容:https://laravel.com/docs/5.5/eloquent-relationships#eager-loading
但不确定如何检查以确保我没有做出反对2的N + 1查询。欢迎任何有关代码的提示或建议,以及检查热切加载是否正常工作的方法。
当你做->with('entrants.user')
时,一旦你到达->get()
它就会加载进入者和用户。当你执行->load('user')
时,它会运行另一个查询来获取用户。但你不需要这样做,因为你在运行->with('entrants.user')
时已经拉过它。
如果你使用->loadMissing('user')
而不是->load('user')
,它应该阻止冗余调用。
但是,如果你利用Collection methods,你可以在你声明$challenges
的开头只运行1个查询:
foreach ($challenges as $challenge) {
// at this point, $challenge->entrants is a Collection because you already eager-loaded it
$entrants = $challenge->entrants->sortByDesc('current_total_amount');
// etc...
您不需要使用->load('user')
,因为$challenge->entrants
已经填充了参赛者和相关用户。所以你可以利用Collection方法->sortByDesc()
对php中的列表进行排序。
此外,您不需要运行->all()
,因为这会将其转换为模型数组(您可以将其保留为模型集合并仍然可以预测它)。
使用Laravel Debugbar检查Laravel应用程序为每个请求创建的查询。
您的Eloquent查询应该只生成3个原始SQL查询,您需要确保此行不会生成N个其他查询:
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all()