繁体   English   中英

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

[英]Update one-to-many relation entries without touching unaffected rows

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

数据库表方案

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

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

我们面临的问题是,为了更新许多Bar条目,我们必须删除旧条目并添加新条目。 我们认为这种方法不是最优的,因为如果我们有很多条目和用户更改只有一个,我们必须删除所有条目并再次插入相同的条目和更新的条目。 (如上例所示,即使未触及“bar2”,也会删除所有三个Bar条目)。

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

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

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

我们正在使用新值加载Foo 我们还选择了分配给Foo模型的所有Bars 使用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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM