【问题标题】:yii2 batch insert with ActiveRecordyii2 使用 ActiveRecord 批量插入
【发布时间】:2017-01-28 17:39:33
【问题描述】:

我想使用 yii2 ActiveRecord 在我的表中插入多条记录。 我已经知道我可以使用这段代码了

$connection->createCommand()->batchInsert('user', ['name', 'age'], [
    ['Tom', 30],
    ['Jane', 20],
    ['Linda', 25],
])->execute();

但是通过这种方法,我的模型验证没有执行。 我已经读过这个问题 ActiveRecord batch insert (yii2)

但也通过以一种棘手的方式进行验证,考虑我想使用 ActiveRecords 事件填充 created_atupdated_at 列。

就这样

public function beforeSave($insert)
{
    if (parent::beforeSave($insert)) {
        if($insert)
            $this->created_at = date('Y-m-d H:i:s');
        $this->updated_at = date('Y-m-d H:i:s');
        return true;
    } else {
        return false;
    }
}

【问题讨论】:

    标签: database activerecord insert yii2


    【解决方案1】:

    我认为使用 beforeSave 事件(和类似的东西)不是一个好主意,因为它会触发 每个 模型。但是,您希望一次保存多个模型。我建议使用批量方法。

    在类似的情况下,我通常使用以下“批量”方法(代码未经测试,仅举例):

    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;
            }
    
        }
    
    }
    

    这个类可以用:

    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
    
            }
        }
    
    }
    

    此外,为了更方便地使用多个模型,可以开发特殊的 Collection 类,实现 \ArrayAccess\Iterator 接口。此集合可以作为简单数组进行迭代,但它包含用于批量操作的特殊方法。像这样的:

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

    【讨论】:

    • 感谢您的回答,如果您的模型有一个 beforeSave 事件,该事件在插入新记录之前触发,那么除了批量插入之外,您的所有记录都会发生一些事情。这个问题我们该怎么办?
    • 是的,我忘记了事件。我添加了对beforeSaveaftreSave 事件的示例支持(基于\yii\db\ActiveRecord::insertInternal)。当然,所描述的方法并不是$model->save() 的完全替代方案。至少插入成功后我们不能填充PK。
    猜你喜欢
    • 2015-02-05
    • 2020-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-23
    相关资源
    最近更新 更多