【问题标题】:Eager Loading: Use `with` on pivot with eloquent relationship急切加载:在具有雄辩关系的枢轴上使用`with`
【发布时间】:2016-10-01 00:46:26
【问题描述】:

有4张桌子:

  • bundles:身份证,姓名
  • products:身份证、姓名
  • prices:身份证、姓名
  • bundle_product: id、bundle_id、product_id、price_id

有 3 种型号:

  • Bundle
  • Product
  • Price

ProductBundle 中时具有Price。我想拥有所有 bundles 及其关联的 products 和关联的 prices 我可以获取所有包含其产品和价格 ID 的捆绑包:

// I created a Bundle Model with a products method
class Bundle extends Model
{
    public function products()
    {
        return $this->belongsToMany(Product::class)->withPivot('price_id');
    }
}

// Then I call this in a controller
$all_bundles = Bundle::with('products')->get();

// Then I can get the price Id of the first product of the first bundle
$price_id = Bundle::with('products')->first()
    ->products()->first()
    ->pivot->price_id;

但我不想要 price id,我想要 price Model。有什么方法可以从枢轴预加载价格(使用 Eager Loading)?

【问题讨论】:

    标签: php laravel laravel-5 eloquent


    【解决方案1】:

    这是一种解决方法,可以避免 N+1 查询问题,并且不需要 Arjon Jason Castro 提到的急切加载包:

    $all_bundles = Bundle::with('products')->get();
    $pivotCollection = new \Illuminate\Database\Eloquent\Collection();
    foreach ($all_bundles as $bundle) {
        $pivotCollection = $pivotCollection->concat($bundle->products->all());
    }
    $pivotCollection->load('price');
    

    它的工作方式是将所有枢轴模型实例收集到一个新的 Eloquent 集合中,并在其上集体调用 load() 以调用 Laravel 的逻辑来加载枢轴的关系,而不会出现任何 N+1 查询问题。

    由于这可确保使用关系更新相同的枢轴实例引用,您应该能够访问任何深层关系,如下所示:$all_bundles->first()->products->first()->price,而无需任何额外的数据库查询。

    【讨论】:

      【解决方案2】:

      当前接受的答案与原始数据结构有偏差。 我创建了一个包,它可以帮助您实现您想要的,并且它还维护了原始数据结构。请在此处阅读我的中等故事:https://medium.com/@ajcastro29/laravel-eloquent-eager-load-pivot-relations-dba579f3fd3a

      首先,创建您的自定义枢轴模型并在枢轴模型上定义关系,在您的情况下:

      use Illuminate\Database\Eloquent\Relations\Pivot;
      
      class BundleProduct extends Pivot
      {
          public function price()
          {
              return $this->belongsTo(Price::class);
          }
      }
      

      然后在关系中使用枢轴模型:

      class Bundle extends Model
      {
          public function products()
          {
              return $this->belongsToMany(Product::class)
              ->withPivot('price_id') // this is needed to query the relation `price`
              ->using(BundleProduct::class);
          }
      }
      

      确保在Product 模型中使用特征AjCastro\EagerLoadPivotRelations\EagerLoadPivotTrait,因为它是belongsToMany 关系中的相关模型。这让我们能够预先加载枢轴关系。

      use AjCastro\EagerLoadPivotRelations\EagerLoadPivotTrait;
      
      class Product extends Model 
      {
        use EagerLoadPivotTrait;
      }
      

      然后像这样急切地加载它:

      $bundle = Bundle::with('products.pivot.price')->first();
      $price = $bundle->products->first()->pivot->price;
      

      【讨论】:

        【解决方案3】:

        一种解决方案可能是为枢轴添加BundleProduct 模型。然后将 BundleProduct 对象链接到 Bundle 模型:

        class Bundle extends Model
        {
            public function bundleProducts()
            {
                return $this->hasMany(BundleProduct::class, 'bundle_id', 'id');
            }
        }
        

        要在此捆绑包中获取所有捆绑包及其相关产品和价格,只需执行以下操作:

        Bundle::with('bundleProducts.product', 'bundleProducts.price')->get();
        

        这对我有用,希望对其他人有帮助。

        【讨论】:

        • 我也想知道这个解决方案。我降落在这里并解决了我的问题。这是执行此操作的标准方法吗? @rap-2-h
        【解决方案4】:

        我认为您将很难找到一种仅通过急切加载来加载价格模型的方法,除非有一种方法可以将父级(在本例中为产品)传递给闭包 - 我很确定那里不是。

        $all_bundles = Bundle::with(['products.prices' => function($query) use (PARENT) {
            $query->where('id', PARENT->pivot->price_id);
        }])->get();
        

        您可以这样做以在初始查询之后仅加载相关价格模型,而不是所有价格模型:

        $all_bundles = Bundle::with('products')->get();
        
        foreach($all_bundles as $bundle) {
            foreach($bundle->products as $product) {
                $product->load(['prices' => function($query) use ($product) {
                    $query->where('id', $product->pivot->price_id);
                }]);
            }
        }
        

        【讨论】:

          【解决方案5】:

          文档:Eloquent: Relationships: Many To Many

          默认情况下,只有模型键会出现在数据透视对象上。如果您的数据透视表包含额外的属性,您必须在定义关系时指定它们:

          return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');

          这意味着withPivot() 方法仅将缺少的字段添加到pivot 对象。要检索实际的Price 模型,您必须设置另一个关系。它看起来像这样:

          /**
           * Class Bundle
           * 
           * Bundle model with the products relationship
           */
          class Bundle extends Model
          {
              public function products()
              {
                  return $this->belongsToMany(Product::class);
              }
          }
          
          /**
           * Class Product
           * 
           * Product model with the prices relationship
           */
          class Product extends Model
          {
              public function prices()
              {
                  return $this->belongsToMany(Price::class);
              }
          }
          
          /**
           * Class Price
           * 
           * Simple model, end result
           */
          class Price extends Model
          {
              // Model definition...
          }
          

          这是基本的,当然不是完整的代码,只是回答所需的功能。

          在此之后,您需要做的就是检索关系:

          // You can query a child relationship using the dot (.). Prices collection will be accessible within each member of the products collection 
          $all_bundles = Bundle::with('products.prices')->get();
          
          $price_id = $all_bundles->first()
              ->products->first()
              ->prices->first()->id;
          

          【讨论】:

          • 感谢您的回答。它不会像例外那样工作:它将返回产品的所有价格,而我只需要捆绑产品的产品价格。对不起,如果我的问题不是很清楚。
          猜你喜欢
          • 2019-09-14
          • 2014-09-19
          • 1970-01-01
          • 2013-09-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-17
          • 1970-01-01
          相关资源
          最近更新 更多