You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

错误原因分析

  1. 数据结构错误:原代码将name、quantity、unit_price分别拆分为独立的数组再合并,导致最终的$data是分散的键值对数组,而非每条Asset记录对应的完整关联数组。
  2. 单条插入方法误用: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 13:20:35