Laravel中如何用Eloquent ORM处理单行多关联ID的关联查询?
问题描述
我的series表数据格式如下:
{"id": 9770,"name": "Name of serie","type": null,"release_date": "2021-09-30 00:00:00","year": 2021,"genre": "4,600,2815"}
我希望通过genre列中逗号分隔的多个ID值,从genres表获取对应数据,并将genres字段包含在返回的主数据中。
当前的Serie和Genre模型代码:
class Serie extends Model { protected $table = 'series'; public function genres() { $genreIds = explode(',', $this->genre); return Genre::whereIn('id', $genreIds)->get(); } } class Genre extends Model { protected $table = 'genres'; }
控制器代码:
$page = $request->input('page', 1); $perPage = $request->input('perPage', 10); $allSeries = Serie::with('genres')->paginate($perPage, ['*'], 'page', $page); return response()->json($allSeries, 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
尝试修改genres关联方法后,查询成功但返回的genres数据为空:
public function genres() { $genreIds = explode(',', $this->genre); return $this->hasMany(Genre::class, 'id', 'genre')->whereIn('id', $genreIds); }
也尝试过以下查询方式,同样无效:
$query = Serie::query(); $query->with([ 'genres' => function ($query) { $genreIds = explode(',', $this->genre); $query->whereIn('id', $genreIds); }, ]); $allSeries = $query->paginate($perPage, ['*'], 'page', $page);
限制条件:无法在genres表中添加关联主数据的字段,请问在Laravel中该如何解决?
解决方案
方法一:使用访问器(推荐)
由于genre字段是逗号分隔的ID字符串,不符合Laravel默认的关联规则(需要关联表存在外键),最直接的方式是给Serie模型添加访问器,自动加载并返回对应分类数据:
class Serie extends Model { protected $table = 'series'; // 自动将genres字段追加到JSON返回结果中 protected $appends = ['genres']; public function getGenresAttribute() { if (empty($this->genre)) { return collect(); } $genreIds = explode(',', $this->genre); return Genre::whereIn('id', $genreIds)->get(); } }
控制器无需再使用with('genres'),直接查询即可:
$page = $request->input('page', 1); $perPage = $request->input('perPage', 10); $allSeries = Serie::paginate($perPage, ['*'], 'page', $page); return response()->json($allSeries, 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
方法二:手动预加载优化(避免N+1查询)
如果担心访问器导致N+1查询(每条Serie单独查询一次genres),可以手动批量获取分类数据再匹配:
$page = $request->input('page', 1); $perPage = $request->input('perPage', 10); $allSeries = Serie::paginate($perPage, ['*'], 'page', $page); // 收集所有Serie的genre ID $allGenreIds = []; foreach ($allSeries as $serie) { if (!empty($serie->genre)) { $allGenreIds = array_merge($allGenreIds, explode(',', $serie->genre)); } } $allGenreIds = array_unique($allGenreIds); // 批量获取分类数据并按ID分组 $genres = Genre::whereIn('id', $allGenreIds)->get()->keyBy('id'); // 给每个Serie匹配对应分类 foreach ($allSeries as $serie) { if (empty($serie->genre)) { $serie->genres = collect(); continue; } $genreIds = explode(',', $serie->genre); $serie->genres = $genres->only($genreIds); } return response()->json($allSeries, 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
之前方法无效的原因
- 最初的
genres方法直接返回get()结果,不属于Laravel的关联定义,with('genres')无法识别并预加载,会导致逻辑错误。 - 修改后的
hasMany写法错误:hasMany的参数顺序是关联模型类、关联表外键、当前模型本地键,但genres表不存在指向series的外键,关联逻辑不成立,所以返回空数据。 - 闭包预加载中,
$this->genre无法获取单个Serie的字段值,因为闭包是在全局查询上下文执行,而非针对单个模型实例,导致$genreIds无效。
内容的提问来源于stack exchange,提问作者spo spo
相关产品推荐
相关产品推荐

