Laravel 8一对多关系中批量保存多条记录的实现求助
Laravel 8一对多关系批量保存多条Asset记录解决方案
问题背景
需要在Donor与Asset的一对多关系中,批量保存用户通过动态表单提交的多条Asset记录,现有代码存在仅插入donor_id或仅单条记录插入的问题。
现有代码参考
Donor 模型
class Donor extends Model { use HasFactory; protected $table ='donors'; protected $fillable = ['name','description']; public function assets() { return $this->hasMany(Asset::class); } public static $rules = [ 'name' => ['required','string','unique:donors'], 'description' => ['required','string'], ]; }
Asset 模型
class Asset extends Model { use HasFactory; protected $table ='assets'; protected $fillable = ['donor_id','name','quantity','unit_price']; public function donor() { return $this->belongsTo(Donor::class); } public static $rules = [ 'donor_id' => ['required','integer'], 'name' => ['required','string'], 'quantity' => ['required','integer'], 'unit_price' => ['required'], ]; }
原AssetController store方法
public function store(Request $request) { $donor = $request->input('donor_id'); $quantity = $quantity = $request->input('quantity', []); $quantityArray = []; foreach($quantity as $qty){ $quantityArray[] = ['quantity' => $qty]; } $name = $request->input('name', []); $nameArray = []; foreach($name as $nm){ $nameArray[] = ['name' => $nm]; } $unitprice = $request->input('unit_price', []); $unitpriceArray = []; foreach($unitprice as $up){ $unitpriceArray[] = ['unit_price' => $up]; } $data[] = [...$quantityArray,...$nameArray,...$unitpriceArray]; // Attempt 1 Asset::create(['donor_id'=> $donor , $data] ); // Attempt 2 Asset::create(['donor_id'=> $donor] + $data] ); // Attempt 3 Asset::create(['donor_id'=> $donor, 'unit_price' => $up, 'name' => $nm, 'quantity' => $qty]); return redirect()->route(static::PREFIX_ROUTE . 'index') ->withFlashSuccess(__('was successfully created.')); }
请求参数示例
array:5 [▼ "_token" => "VlSOGgIS6KzrDpCmTtGJDnYLbNzby38FHAdUV2I5" "donor_id" => "1" "name" => array:3 [▼ 0 => "speed boat" 1 => "Laptops" 2 => "Trailer" ] "quantity" => array:3 [▼ 0 => "5" 1 => "3" 2 => "2" ] "unit_price" => array:3 [▼ 0 => "4" 1 => "2" 2 => "2" ] ]
前端表单代码
<form action="{{ route("asset.store") }}" method="POST" > @csrf <div class="card"> <div class="card-header"> Add Asset </div> <div class="card-body"> <div class="form-group {{ $errors->has('donor_id') ? 'has-error' : '' }}"> <label for="donor_id">@lang('Donor')</label> <select name="donor_id" class="form-control" required> @foreach($donors as $donor) <option value="{{ $donor->id }}" @if($donor->id == old('donor_id', $donor->donor_id))selected="true"@endif> {{ $donor->name }}</option> @endforeach </select> @if($errors->has('donor_id')) <em class="invalid-feedback"> {{ $errors->first('donor_id') }} </em> @endif </div> <table class="table" id="assets_table"> <thead> <tr> <th>Assets Name</th> <th>Quantity</th> <th>Unit Price</th> </tr> </thead> <tbody> <tr id="asset0"> <td> <input type="text" name="name[]" class="form-control"/> </td> <td> <input type="number" name="quantity[]" class="form-control"/> </td> <td> <input type="number" name="unit_price[]" class="form-control"/> </td> </tr> <tr id="asset1"></tr> </tbody> </table> <div class="row"> <div class="col-md-12"> <button id="add_row" class=" pull-left">+ Add Row</button> <button id='delete_row' class="pull-right ">- Delete Row</button> </div> </div> </div> </div> <div> <input class="btn btn-success" type="submit" value="Save"> </div> </form>
错误原因分析
- 数据结构错误:原代码将
name、quantity、unit_price分别拆分为独立的数组再合并,导致最终的$data是分散的键值对数组,而非每条Asset记录对应的完整关联数组。 - 单条插入方法误用:
Asset::create()仅支持插入单条记录,无法直接处理批量数组;尝试3仅循环最后一次迭代的变量,因此只能插入单条记录。
正确实现方案
1. 调整验证规则
针对批量提交的数组数据,在控制器中定义适配的验证规则:
$validated = $request->validate([ 'donor_id' => ['required', 'integer', 'exists:donors,id'], 'name.*' => ['required', 'string'], 'quantity.*' => ['required', 'integer', 'min:1'], 'unit_price.*' => ['required', 'numeric', 'min:0'], ]);
2. 修正控制器store方法
构建正确的批量数据结构,提供两种实现方式:
方式一:使用insert()批量插入(性能更高,不触发模型事件)
public function store(Request $request) { // 验证请求数据 $validated = $request->validate([ 'donor_id' => ['required', 'integer', 'exists:donors,id'], 'name.*' => ['required', 'string'], 'quantity.*' => ['required', 'integer', 'min:1'], 'unit_price.*' => ['required', 'numeric', 'min:0'], ]); $donorId = $validated['donor_id']; $assetsData = []; // 构建每条Asset的完整数据 foreach ($validated['name'] as $index => $name) { $assetsData[] = [ 'donor_id' => $donorId, 'name' => $name, 'quantity' => $validated['quantity'][$index], 'unit_price' => $validated['unit_price'][$index], 'created_at' => now(), 'updated_at' => now(), ]; } // 批量插入 Asset::insert($assetsData); return redirect()->route(static::PREFIX_ROUTE . 'index') ->withFlashSuccess(__('Assets were successfully created.')); }
方式二:使用关联关系createMany()(触发模型事件,自动处理时间戳)
public function store(Request $request) { // 验证请求数据 $validated = $request->validate([ 'donor_id' => ['required', 'integer', 'exists:donors,id'], 'name.*' => ['required', 'string'], 'quantity.*' => ['required', 'integer', 'min:1'], 'unit_price.*' => ['required', 'numeric', 'min:0'], ]); $donor = Donor::findOrFail($validated['donor_id']); $assetsData = []; // 构建每条Asset的完整数据(无需手动添加时间戳) foreach ($validated['name'] as $index => $name) { $assetsData[] = [ 'name' => $name, 'quantity' => $validated['quantity'][$index], 'unit_price' => $validated['unit_price'][$index], ]; } // 通过关联关系批量保存 $donor->assets()->createMany($assetsData); return redirect()->route(static::PREFIX_ROUTE . 'index') ->withFlashSuccess(__('Assets were successfully created.')); }
3. 前端表单小修正
移除select标签中重复的name="donor_id"属性,避免表单提交异常:
<select name="donor_id" class="form-control" required>
注意事项
- 使用
insert()时,需手动添加created_at和updated_at字段(若模型开启了时间戳);createMany()会自动处理这些字段。 createMany()会触发模型的creating、created等事件,适合需要执行额外逻辑的场景;insert()性能更优,适合纯数据插入场景。
内容的提问来源于stack exchange,提问作者Kairaoi Ientumoa
相关产品推荐
相关产品推荐

