更新一对多关系条目,而不触及未受影响的行

时间:2017-05-15 06:23:16

标签: php mysql yii2

我们有两个模型“Foo”和“Bar”,如下图所示(一个Foo可以有很多条)。

database tables scheme

假设我们要使用Foo中的值更新$_POST模型。由于与Foo的{​​{1}}关系,我们还希望使用来自相同Bar的值更新Bar模型。 $_POST更新过程与通常相同(即通过foo ID从数据库加载Foo模型,然后将Foo加载到$_POST模型并保存模型)。但是Foo可以包含多个Foo,这就是为什么我们会从条形图表中删除Bar的所有条目,并从$fooId创建条形表的新条目。

例如,用户打开表单,他可以在其中更改$_POST名称并添加/更改/删除许多栏名称。假设当前foo名称为“foo”,当前条形为“bar1”,“bar2”和“bar3”。用户将foo名称更改为“fooChanged”,同时将“bar1”更改为“bar10”并删除“bar3”。 注意:未触及“bar2”。提交表单后,控制器加载Foo模型,加载foo更改(现在“foo”已更改为“fooChanged”)并保存模型。使用Foo模型,它有点不同。首先,控制器删除所有具有Bar的条目,并使用$fooId创建新条目(请参阅下面的代码)。

控制器:

batchInsert

酒吧模特:

public function actionUpdateFoo($fooId = null)
{
    $foo = Foo::findOne($fooId);
    $foo->load(Yii::$app->request->post());

    $transaction = Yii::$app->db->beginTransaction();
    if ($foo->save() && Bar::deleteAll(['foo_id' => $fooId]) && Bar::create($fooId, Yii::$app->request->post())) {
        $transaction->commit();
    } else {
        $transaction->rollBack();
    }

    return $this->render('foo', [
        'foo' => $foo,
    ]);
}

我们面临的问题是,为了更新许多public static function create($fooId, $post) { $array = []; foreach ($post['Bar'] as $item) { array_push($array, [ 'foo_id' => $fooId, 'name' => $item['name'], ]); } return Yii::$app->db->createCommand()->batchInsert(self::tableName(), ['foo_id', 'name'], $array)->execute(); } 条目,我们必须删除旧条目并添加新条目。我们认为这种方法不是最优的,因为如果我们有很多条目和用户更改只有一个,我们必须删除所有条目并再次插入相同的条目和更新的条目。 (如上例所示,即使未触及“bar2”,也会删除所有三个Bar条目。

有没有比这更好的方法(我们想忽略未更改的行,只更改受影响的行)?

1 个答案:

答案 0 :(得分:2)

不必先删除所有行,然后再重新添加。我们使用一种简单的方法来检测更改并仅更新更新的行。虽然这可能不会减少代码中写入的行数,但它会减少使用的查询量,从而提高加载速度。

actionUpdateFoo($fooId = null)的简短摘要:

我们正在使用新值加载Foo。我们还选择了分配给Bars模型的所有Foo。使用foreach(),我们遍历Bar并将每个找到的行的ID放到一个变量($dependantBars)。使用方法,我们(总是)获得一个大小为2的数组(第一个元素是一个旧值数组,第二个元素是一个新值数组)。在if()中,我们保存更新的Foo模型,并检查删除和插入是否成功。

/**
 * Let's say, we have in this example:
 * $dependantBars = [0, 1, 2, 3]; (old choices)
 * $foo['choices'] = [0, 1, 5, 7]; (loaded from Yii::$app->request->post()['Foo']['choices'])
 */
public function actionUpdateFoo($fooId = null)
{
    $foo = Foo::findOne($fooId);
    $foo->load(Yii::$app->request->post());

    $subFoo = Bar::findAll($fooId);
    $dependantBars = [];
    foreach ($subFoo as $foo) {
        $dependantBars[] = $foo->id;
    }

    $distinction = self::getDifferencesInArrays($dependantBars, $foo['choices']);

    $transaction = Yii::$app->db->beginTransaction();
    if ($foo->save() && Bar::updateUserChoices($distinction)) {
        $transaction->commit();
    } else {
        $transaction->rollBack();
    }

    // Do something else
}

控制器中的单独方法以获得差异:

/**
 * Checks the difference between 2 arrays.
 *
 * @param array $array1 Old values that are still saved in database.
 * @param array $array2 New values that are selected by user.
 * @return array
 */
public static function getDifferencesInArrays($array1 = [], $array2 = [])
{
    return [
        array_diff($array1, $array2),
        array_diff($array2, $array1),
    ];
}

在Bar类中,我们可以编写这个方法来在同一个方法中执行这两个方法(删除和插入):

public static function updateUserChoices($distinction)
{
    $deletedRows = true;
    $insertedRows = true;

    if (!empty($distinction[0])) {
        $deletedRows = self::deleteAll(['foo_id' => $distinction[0]]);
    }

    if (!empty($distinction[1])) {
        $insertedRows = Bar::create(); // Something here
    }

    return $deletedRows && $insertedRows;
}