yii2 batch insert with ActiveRecord

后端 未结 1 781
孤街浪徒
孤街浪徒 2021-01-17 05:12

I want to insert multiple record in my table using yii2 ActiveRecord. I already know that I can use this code

$connection->createCommand()->batchInser         


        
1条回答
  •  爱一瞬间的悲伤
    2021-01-17 05:21

    I think is not good idea to use beforeSave events (and similar stuff) because it will trigger for each model. However you want save multiple models at once. I recommend to use bulk methods.

    In similar cases I use usually following "bulk" approach (code not tested, just for example):

    namespace common\components;
    
    class Model extends yii\base\Model {
    
        /**
         * Saves multiple models.
         *
         * @param ActiveRecord[] $models
         * @return bool
         */
        public static saveMultiple($models){
    
            if(count($models) > 0){
    
                $firstModel      = reset($models);
                $columnsToInsert = $firstModel->attributes();   // here you can remove excess columns. for example PK column.
                $modelsToInsert  = [];
                $rowsToInsert    = [];
    
                foreach($models as $model){
                    if ($this->beforeSave(true)) {
                        $modelsToInsert[] = $model;
                    }
                }
    
                foreach($modelsToInsert as $model){
                    $rowsToInsert[] = array_values($model->attributes);     // here you can remove excess values
                }
    
                $numberAffectedRows = \Yii::$app->db->createCommand()
                    ->batchInsert($firstModel->tableName(), $columnsToInsert, $rowsToInsert)
                    ->execute();
    
                $isSuccess = ($numberAffectedRows === count($models));
    
                if($isSuccess){
                    $changedAttributes = array_fill_keys($columnsToInsert, null); 
    
                    foreach($modelsToInsert as $model){
                        $model->afterSave(true, $changedAttributes);
                    }
                }
    
                return $isSuccess;
    
            } else {
    
                return true;
            }
    
        }
    
    }
    

    This class can be used:

    use common\components\Model;
    
    /**
     * @var SomeActiveRecord[] $models Array that contains array of active records (type SomeActiveRecord)
     */ 
    
    // ...
    
    if (Model::validateMultiple($models)){
    
        if(!Model::saveMultiple($models)){
            // ... some error handling
        }
    
    } else {
    
        foreach($models as $model){
            if($model->hasErrors()){
    
                $errors = $model->getFirtsErrors();
                // ... some error handling
    
            }
        }
    
    }
    

    Additionally, for more convenient working with multiple models can be developed special Collection class that implements \ArrayAccess and \Iterator interfaces. This collection can iterated as simple array, however it contains special methods for bulk operations. Something like this:

    foreach($modelCollection as $model){
        // ...
    }
    
    $modelCollection->validate();   // works similar to common\components\Model::validateMultiple()
    $modelCollection->save();       // works similar to common\components\Model::saveMultiple()
    

    0 讨论(0)
提交回复
热议问题