2017-05-15 70 views
4

我们有两个模型“Foo”和“Bar”,如下图所示(一个Foo可以有很多条)。更新一对多关系条目而不触及未受影响的行

database tables scheme

比方说,我们想从$_POST更新Foo模型值。由于FooBar的关系,我们也想要更新Bar模型,其值为相同的$_POSTFoo更新过程与往常一样(即通过foo ID从数据库加载Foo模型,然后加载$_POSTFoo模型并保存模型)。但Foo可以有很多Bar s,这就是为什么我们删除条形表中有$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条目,我们必须删除旧的和添加新的。我们认为这种方法并不是最优的,因为如果我们有很多条目和用户更改,我们必须删除所有条目并再次插入相同的条目和更新的条目。 (就像上面的例子,即使“bar2”未被触摸,所有三个Bar条目也将被删除)。

是否还有其他更好的方法比这一个(我们想忽略不变的行,只改变受影响的行)?

+0

您可能会考虑只删除不在提交的$ _POST中的Bar。之后,插入忽略或替换为只添加新条。 – xangxiong

+0

假设您使用$ bar获取相关的$ foo数据,当您通过id获取$ foo进行更新时,请创建一个比较算法,查看$ _POST数组与$ foo相关$ bar数据中的数据。然后你会知道该忽略什么以及要更新什么。 – dataskills

+0

我会跟踪ID(s)...添加隐藏的ID字段,当条形图条目被提交时,会发布该隐藏的ID字段,与数据库进行比较,如果有更改,则会更新这些条目。帮助? – Hmmm

回答

2

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

简短摘要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), 
    ]; 
} 

而在酒吧类,我们可以写这个方法做两件事以同样方法(删除和插入):

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

我写了一个扩展来加载并保存一个多维数组来执行此操作:https://github.com/e-frank/yii2-data

您可以在增量和完整/完整关系之间进行选择,并将缺失/删除​​的相关行删除或取消关联。 反馈欢迎。