【问题标题】:Update one-to-many relation entries without touching unaffected rows更新一对多关系条目而不触及未受影响的行
【发布时间】:2017-05-15 06:23:15
【问题描述】:

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

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

例如,用户打开表单,他可以在其中更改Foo 名称并添加/更改/删除许多条形名称。假设当前 foo 名称为“foo”,当前柱为“bar1”、“bar2”和“bar3”。用户将 foo 名称更改为“fooChanged”,同时将“bar1”更改为“bar10”并删除“bar3”。 注意:未触及“bar2”。提交表单后,控制器加载Foo 模型,加载 foo 更改(现在“foo”已更改为“fooChanged”)并保存模型。对于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();
}

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

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

【问题讨论】:

  • 您可能会考虑只删除不在提交的 $_POST 中的 Bar。然后在此之后执行插入忽略或替换以仅添加新条。
  • 假设您正在获取与 $bar 相关的 $foo 数据,当您通过 id 获取 $foo 进行更新时,创建一个比较算法,将 $_POST 数组与您的 $foo 相关$bar 数据。然后你就会知道要忽略什么以及要更新什么。
  • 我会跟踪 id(s) ... 添加隐藏的 ID 字段,该字段将在提交 Bar 条目时发布,与 DB 进行比较,如果有任何更改,则更新这些条目。有帮助吗?

标签: php mysql yii2


【解决方案1】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-20
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    • 2014-12-08
    • 1970-01-01
    • 2018-08-18
    相关资源
    最近更新 更多