Eloquent 循环中的关系

Eloquent Relationship in Loop

我正在为现有数据库(名为 Epicor 的 ERP 系统)构建 Laravel 前端,以期在单独的(新)数据库中扩展该功能。目前,我正在尝试显示 "equipment" 的状态为正在运送给客户的部分,并包含来自部分 table 的信息。数据库关系都在那里,我可以使用 SSMS 获得我需要的所有信息 - 所以我相信我在使用 Eloquent 时一定是出错了。我有以下型号:

设备 - 这是系统中的序列号,因此实际上是零件的实例:

<?php

class Equipment extends Model
{
    protected $table = 'ERP.SerialNo';
    public $timestamps = false;
    protected $primaryKey = 'SerialNumber';
    protected $keyType = 'string';

    protected $fillable = [
        'SerialNumber',
        'SNStatus',
        'PartNum',
        'TerritoryID',
        'JobNum',
        'PackNum',
        'PackLine',
        'RMANum',
        'CustNum',
        'SNStatus'
    ];

    public function Part()
    {
        return $this->belongsTo(Part::class,'PartNum','PartNum');
    }

    public function Customer()
    {
        return $this->belongsTo(Customer::class,'CustNum', 'CustNum');
    }
}

部分

class Part extends Model
{
    protected $table = 'ERP.Part';
    public $timestamps = false;
    protected $primaryKey = 'PartNum';
    protected $keyType = 'string';

    protected $fillable = [
        'PartNum',
        'SearchWord',
        'Innactive',
        'PartDescription',
        'ClassID',
        'CommodityCode',
        'NetWeight'
    ];

    public function ShipmentLine()
    {
        return $this->hasMany(Shipment::class, 'PartNum', 'PartNum');
    }

    public function Equipment()
    {
        return $this->hasMany(Equipment::class,'PartNum', 'PartNum');
    }
}

客户控制员

public function show($CustID)
{
    $Customer = Customer::find($CustID);
    $Shipments = $Customer->Shipment->where('Voided', '0');
    $Equipments = $Customer->Equipment->where('SNStatus', 'SHIPPED');
    return view('Customer.show', compact('Equipments',     'Customer','Shipments', 'Parts'));
}

show.blade.php(在客户下)

<?php

@foreach($Equipments as $Equipment)
    <tr>
        <td>ClassID</td>
        <td><a href="{{ route('Part.show',$Equipment->PartNum)}}">{{$Equipment->PartNum}}</a></td>
        <td><a href="{{ route('Equipment.show',$Equipment->SerialNumber)}}">{{$Equipment->SerialNumber}}</a></td>
        <td>PartDescription is sometimes really really really long.....even longer than this!</td>
    </tr>
@endforeach

一切正常,我得到了一份状态为已运送给该客户的所有设备的清单。我现在想做的是,在设备列表中,包括 Part table 中相关的字段(ClassID 和 PartDescription)。

我已经尝试了一些方法,但感觉我在抓住救命稻草,所有尝试都失败了。我已经设法在设备 show.blade.php 上显示部件信息,所以我相信模型设置正常。

提前致谢,

理查德

首先,Part 模型(以及 Customer 模型)中的关系方法必须写成复数形式,因为您要匹配多个实体:

public function ShipmentLines()
{
    return $this->hasMany(Shipment::class, 'PartNum', 'PartNum');
}

public function Equipments()
{
    return $this->hasMany(Equipment::class,'PartNum', 'PartNum');
}

其次,您可以使用关系加载控制器中的设备,而不是使用延迟加载:

public function show($CustID)
{
    $Customer = Customer::find($CustID);
    $Shipments = $Customer->ShipmentLines()
        ->where('Voided', '0')
        ->get();
    $Equipments = $Customer->Equipments()
        ->with('Part') // load the Part too in a single query
        ->where('SNStatus', 'SHIPPED')
        ->get();
    return view('Customer.show', compact('Equipments', 'Customer', 'Shipments'));
}

最后,在刀刃模板中,可以很方便的使用装备零件:

@foreach ($Equipments as $Equipment)
        <tr>
            <td>{{$Equipment->Part->ClassID}}</td>
            <td><a href="{{ route('Part.show',$Equipment->PartNum)}}">{{$Equipment->PartNum}}</a></td>
            <td><a href="{{ route('Equipment.show',$Equipment->SerialNumber)}}">{{$Equipment->SerialNumber}}</a></td>
            <td>PartDescription is sometimes really really really long.....even longer than this!</td>
        </tr>
@endforeach

此外,我建议使用 @forelse 而不是 @foreach 来涵盖没有设备存在的情况:

@forelse ($Equipments as $Equipment)
        <tr>
            <td>{{$Equipment->Part->ClassID}}</td>
            <td><a href="{{ route('Part.show',$Equipment->PartNum)}}">{{$Equipment->PartNum}}</a></td>
            <td><a href="{{ route('Equipment.show',$Equipment->SerialNumber)}}">{{$Equipment->SerialNumber}}</a></td>
            <td>PartDescription is sometimes really really really long.....even longer than this!</td>
        </tr>
@empty
        <tr>
            <td colspan="4">There is no existing equipment!</td>
        </tr>
@endforelse

我想你要找的是with()

不过,在我开始之前,您实际上遇到了一个比看起来更严重的问题。马太·米海其实也提到了这一点。

当你有类似 $Customer->Equipment 的东西时,你实际上是在利用 Eloquent 的 "dynamic properties"。这意味着,某处有一个神奇的 __get(),如果目标模型上不存在所需的 属性,请检查它是否具有该名称的关系方法。如果是这样,如果尚未通过 with()load().

预先加载它,请延迟加载它

因此,当您执行 $Customer->Equipment 时,它基本上是 $Customer->Equipment()->get() 的快捷方式。

接下来要考虑的是 get() 的结果是一个 Eloquent\Collection, which is a child-class to Support\Collections. And Support\Collections have their own version of the where() 方法。

综上所述,$Customer->Equipment->where('SNStatus', 'SHIPPED') 不会 导致 运行 查询如下所示:

SELECT * FROM Equipment WHERE customerID = ? AND SNStatus = 'SHIPPED'

你正在做的是 运行 这个:

SELECT * FROM Equipment WHERE customerID = ?

然后要求集合 class 按 SNStatus='SHIPPED' 之后 过滤结果集。这可能是一个巨大的性能损失,甚至会根据 table 的大小来最大化您的服务器 RAM。我想你真正要找的是这个:

$Customer->Equipment()->where('SNStatus', 'SHIPPED')->get()

通过调用实际的 Equipment() 方法而不是动态的 属性,您是在告诉 Eloquent 您还没有完全准备好执行查询,因为您仍在为其附加条件。

(顺便提一句,您的命名约定对我的强迫症造成了一点伤害,方法应始终为 "camelCased"。只有 class 个名称的首字母大写。)


所以...回到你实际提出的问题,包括对 Model::where()Collection::where() 之间区别的理解,我们有这样的东西:

$resutls = $Customer->Equipment()->with(['Part'])->where('SNStatus', 'SHIPPED')->get();

由于您想在您真正关心的部分 table 中指定几个字段,您可以使用 constrained eager-load

$resutls = $Customer->Equipment()->with(['Part' => function (Illuminate\Database\Eloquent\Builder $query) {
    $query->select([
        'PartNum',  //Per Equipment::Part(), This needs to be there for the relation to be mated with its parent
        'ClassID',
        'PartDescription'
    ]);
    // Since PHP always handles objects by-reference, you don't actually need to return $query after having altered it here.
}])->where('SNStatus', 'SHIPPED')->get();

这将为您提供一个嵌套的 Part 对象,其中仅包含您在 Eloquent\Collection 结果中的每个 Equipment 模型元素上关心的字段。

至于如何在你的 blade 文件中处理这些结果,我会与 Matei Mihai 不同,我认为这个答案很好。