Laravel 4 Eager正在加载n + 1期

时间:2014-04-14 17:51:56

标签: php laravel eager-loading

我已经整天解决了急切的加载/ n + 1问题,研究和阅读并观看了有关此问题的教程,但尚未解决。我已经为模型建立了关系,但是当我使用辅助函数传递数据时,我遇到了这个n + 1问题。 我想从网址site.com/Artist/songs中获取一个艺术家名字并获取所有歌曲并显示这样的网址。

site.com/$Artist/songs/$id

我的artists / index.blade.php视图如下所示http://i61.tinypic.com/2nqzatk.jpg 我不确定我在这里缺少什么。

提前致谢!

我的桌子

歌曲     id,title,body,slug,hits,artist_id,created_at,updated_at

艺术家     id,name,body,created_at,updated_at

routes.php文件

Event::listen('illuminate.query', function($query)
{
    var_dump($query);
});
...
Route::get('{artist}/songs', 'ArtistsController@index');
Route::get('{artist}/songs/{id}', ['as' => 'artist.songs.show', 'uses' => 'ArtistsController@show']);

型号:Song.php

class Song extends Eloquent {
    protected $guarded = ['id'];

    /**
     * Setting up relationship for the artist model for easier usage
     *
     */
    public function artist()
    {
        return $this->belongsTo('Artist');
    }

    // Override find method
    public static function find($id, $name = null)
    {
        $song = static::with('artist')->find($id);

        // If the song is found
        if ($song)
        {
            // If the song doesn't belong to that artist, throw an exception which will redirect to home, defined in global.php
            if ($name and $song->artist->name !== $name)
            {
                throw new Illuminate\Database\Eloquent\ModelNotFoundException;
            }

            return $song;
        }
        // If the song is not found, throw an exception which will redirect to home, defined in global.php
        else
        {
            throw new Illuminate\Database\Eloquent\ModelNotFoundException;
        }

    }

    // Get songs from artist
    public static function byArtist($name)
    {
        return Artist::byName($name)->songs;
    }

}

Model Artist.php

class Artist extends Eloquent {
    protected $fillable = [];

    /**
     * Setting up relationship with the song model for easier usage
     * $artist->songs;
     */
    public function songs()
    {
        return $this->hasMany('Song');
    }

    // Get artist by name
    public static function byName($name)
    {
        return static::whereName($name)->first();
    }

}

Controller:ArtistsController.php

class ArtistsController extends BaseController {

    // Set default layout for this controller
    protected $layout = 'layouts.master';

    /**
     * Display a listing of the resource.
     * GET /artists
     *
     * @return Response
     */
    public function index($name)
    {
        $this->data['songs'] = Song::byArtist($name);

        $this->layout->content = View::make('artists.index', $this->data);
    }

helpers.php

function link_to_artist_song(Song $song)
{
    return link_to_route('artist.songs.show', $song->title, [$song->artist->name, $song->id]);
}

艺术家的索引视图 artists / index.blade.php http://i61.tinypic.com/2nqzatk.jpg

@extends('layouts.master')

@section('content')

    @if(isset($songs))
        <h1>All Songs</h1>

        <ul class="list-group">
        @foreach($songs as $song)
            <li class="list-group-item">{{ link_to_artist_song($song) }}</li>
        @endforeach
        </ul>
    @endif
@stop

3 个答案:

答案 0 :(得分:2)

你从不急于加载任何东西,这就是你面临n + 1问题的原因。 如果我说得对,你在这里使用的代码有点难,你想要所有给定艺术家的歌曲带有来自url的$ name,对吗?

所以,这就是你需要的一切:

// controller
public function index($name)
{
    // with('songs') is eager loading related songs for you
    $this->data['artist'] = Artist::with('songs')->whereName($name)->first();

    $this->layout->content = View::make('artists.index', $this->data);
}

// the problem of your queries is in the helper:
function link_to_artist_song(Song $song)
{
    return link_to_route('artist.songs.show', $song->title, [
       $song->artist->name, // this is calling db query for each song to retrieve its artist (despite it is always the same)
       $song->id]);
}

// so instead use this in your view
@foreach($artist->songs as $song)
   <li class="list-group-item">
     {{ link_to_route('artist.songs.show', $song->title, [$artist->name, $song->id]) }}
   </li>
@endforeach

答案 1 :(得分:0)

当你有很多有很多艺术家的歌曲时,存在n + 1问题。它必须获得所有歌曲(1个查询),然后为每首歌曲获得艺术家(n个查询)。

在这种情况下,您已经知道了这首歌,因此这是一个查询,那么您需要该歌曲的所有艺术家,这只是一个额外的查询。

如果您试图找到某种类型的歌曲,那么n + 1问题才会发挥作用,这可能会返回许多歌曲。然后,对于每首歌曲,您将不得不进行额外的查询以获得该歌曲的艺术家。这将是渴望加载最有用的地方。

$song = Song::find($id);
$artist = $song->artist;

答案 2 :(得分:0)

每次你抓一首歌,并$song->artist它都会进行查询。

您也可以使用Query scopes

class Song extends Eloquent {

    public function scopeByArtist($query, $name) {
        return Artist::whereName($name)->first()->songs(); //->with('artist');
    }
}

像这样艺术家已经装好了。

查询:

$songs = Song::byArtist($name)->get();