yii2 使用 ActiveRecord 批量插入

Posted

技术标签:

【中文标题】yii2 使用 ActiveRecord 批量插入【英文标题】:yii2 batch insert with 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;
    

【问题讨论】:

【参考方案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。

以上是关于yii2 使用 ActiveRecord 批量插入的主要内容,如果未能解决你的问题,请参考以下文章

Yii2 批量插入

Yii2中的Active Record可以批量插入数据吗

如何在Yii2中插入批量数据库?

php Yii2批量插入助手。支持“忽略”和“重复密钥更新”策略

yii2之ActiveRecord 模型

使用 ActiveRecord 和 Yii2 记录实际的 SQL 查询?