[英]Laravel nested relations eager load respect ancestor id
假設我有一個名為Research
的 model。 每個研究都屬於多個Location
模型。 並且每個Location
model BelongsToMany Contact
模型。 但是,每個Contact
也與Research
相關。
class Research extends Model {
protected $table = 'researches';
public function locations()
{
return BelongsToMany( Location::class, 'research_locations_list', 'research_id', 'location_id' );
}
}
class Location extends Model {
protected $table = 'locations';
public function researches()
{
return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
}
public function contacts()
{
return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
}
class Contact extends Model {
protected $table = 'contacts';
public function locations()
{
return BelongsToMany( Location::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
}
researches
表:
+----+------------+
| id | research |
+----+------------+
| 1 | Research 1 |
| 2 | Research 2 |
+----+------------+
locations
表:
+----+---------------+
| id | location |
+----+---------------+
| 1 | United States |
| 2 | Great Britain |
| 3 | Germany |
+----+---------------+
contacts
表:
+----+---------+
| id | contact |
+----+---------+
| 1 | Jack |
| 2 | John |
| 3 | Hanz |
+----+---------+
research_locations_list
表:
+----+-------------+-------------+
| id | research_id | location_id |
+----+-------------+-------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 2 |
| 4 | 2 | 3 |
+----+-------------+-------------+
所以研究 1 在美國和英國進行,研究 2 在英國和德國進行
location_contacts_list
表:
+----+-------------+------------+-------------+
| id | location_id | contact_id | research_id |
+----+-------------+------------+-------------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 2 | 1 | 2 |
| 4 | 3 | 3 | 2 |
+----+-------------+------------+-------------+
研究 1 應該有 Jack 和 John 在美國的聯系人,在其他地方沒有聯系人;
研究 2 應該有英國的 John 和德國的 Hanz 聯系人;
現在,通過延遲加載我可以實現:
$researches = Research::all();
foreach( $researches as $research )
{
foreach( $research->locations as $location )
{
$contacts = $location->contacts()->wherePivot( 'research_id', $research->id )->get();
// Will return John and Jack in United States for Research 1 and John in Great Britain and Hanz in Germany for Research 2
}
}
現在,問題是:如何通過預加載來實現這一點?
$researches = Research::with( 'locations.contacts' )->all();
foreach( $researches as $research )
{
foreach( $research->locations as $location )
{
$contacts = $location->contacts;
// Will return John and Jack in United States, John in Great Britain ( which is not supposed to happen ) for Research 1 and John in Great Britain and Hanz in Germany for Research 2
}
}
也許我可以以某種方式指示聯系人尊重祖先身份? 喜歡:
$research = Research::with( 'locations.contacts' )->where( 'researches.id = location_contacts_list.research_id' )->all();
更新
我最接近解決這個問題的方法是像這樣修改Location
model:
class Location extends Model {
protected $table = 'locations';
public function researches()
{
return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
}
public function contacts()
{
return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
// Modify contacts attribute getter
public function getContactsAttribute()
{
$contacts = $this->contacts();
if( !empty( $this->pivot->research_id ) )
{
$contacts = $contacts->wherePivot( 'research_id', $this->pivot->research_id );
}
return $contacts->get();
}
}
但是看起來有點臟。。。
在您的解決方案中,您會遇到 N+1 查詢問題。 我可以建議以下解決方案:
class Research extends Model
{
protected $table = 'researches';
public function locations(): BelongsToMany
{
return $this->belongsToMany(Location::class, 'research_locations_list');
}
public function contacts(): BelongsToMany
{
return $this->belongsToMany(Contact::class, 'location_contacts_list')
->withPivot('location_id');
}
public function contactsByLocationAttribute(int $locationId): Collection
{
return $this->contacts
->filter(static function ($contact) use ($locationId) {
return $contact->pivot->location_id === $locationId;
});
}
}
$researches = Research::with(['locations', 'contacts'])->get();
foreach ($researches as $research) {
foreach ($research->locations as $location) {
$contacts = $research->contactsByLocation($location->id);
}
}
這里總是只有 3 個對數據庫的查詢。 並且只會加載必要的模型
如果我做對了,您想在with
語句中添加一些條件。 如果你想使用 eloquent 語法,你可以這樣做:
$research = Research::with(['YOUR RELATION' => function ($query) {
$query->where('YOUR COLUMN', 'EQUALS TO SOMETHING');
}])->get();
請記住,由於內部with
您使用嵌套關系,例如locations.contacts
,查詢中的where
function 將僅過濾最后一個 model (在本例中為contacts
)。 如果你想根據某些條件過濾位置和聯系人,你必須寫類似這樣的東西(只是一個例子):
$research = Research::with(['locations' => function ($query) {
$query->where('id', 1)->with(['contacts' => function ($query) {
$query->where('name', 'Tim');
}]);
})->get();
但是,為了做到這一點,您還需要與 pivot 表建立關系(如果您還想在條件內使用它)。 否則,您必須使用不同的語法,使用連接。 從文檔中查看此頁面以獲取查詢構建器https://laravel.com/docs/9.x/queries#main-content
也許,這個https://laravel.com/docs/9.x/eloquent-relationships#has-many-through可以幫助你。 你應該試試這個
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.