【问题标题】:Yii composite primary keys with isNewRecordYii 复合主键与 isNewRecord
【发布时间】:2013-03-04 12:37:06
【问题描述】:

我有一个带有复合键( user_id , category_id )的 mysql 表; 我正在尝试更新这些记录的最后一次访问,如下所示

$userCategory = new UserCategory;
$userCategory->user_id = 1;
$userCategory->category_id = 15;
echo $userCategory->isNewRecord; //always true
$userCategory->last_access = Now();

$userCategory->save();

{$userCategory->isNewRecord} 和当我尝试 save() 时,MySQL 会为复合主键生成重复错误。

我也将此添加到 UserCategory 模型中,但没有帮助

public function primaryKey() {
        return array('user_id', 'category_id');
    }

****更新: 对困惑感到抱歉。我的问题是如何在 Yii 框架中实现与“ON DUPLICATE KEY UPDATE”相同的结果。换句话说,如何在一个 SQL 查询中进行插入或更新。如果您查看save()的源代码

public function save($runValidation=true,$attributes=null)
{
    if(!$runValidation || $this->validate($attributes))
        //checking if new record
        return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);**
    else
        return false;
}

【问题讨论】:

  • 检查我的答案中的编辑以在适用时填充新记录。

标签: php mysql yii


【解决方案1】:

实际上,问题在于如果isNewRecord 始终为真,这意味着Yii 在将模型保存到数据库时将使用INSERT 语句而不是UPDATE 语句。这就是为什么你总是得到重复的pk错误,即使它是复合的。

Here 是关于IsNewRecord 的官方文档。所以,问题是你正在使用

$userCategory = new UserCategory; //Always a new record, tries to INSERT

因此,要解决此问题,您必须找到记录并评估是否在保存之前找到它。也可以阅读 Here 关于 find() 系列方法及其返回值的文档,find() 方法的返回值根据它们的性质略有不同:

find..() 返回找到的记录,如果没有找到记录,则返回 NULL

findAll..() 返回一个包含所有找到的记录的数组,如果没有找到记录,则返回一个空数组。

您可以使用此返回值来区分是否存在主键:

$userCategory = UserCategory::model()->findByAttributes(array('user_id '=>1,'category_id '=>15));
// if user does not exist, you need to create it
if ($userCategory == NULL) {
     $userCategory = new UserCategory;
     $userCategory->user_id = 1;
     $userCategory->category_id = 15;
}
echo $userCategory->isNewRecord; //you will see the difference if it does exist or not exist
$userCategory->last_access = Now();
$userCategory->save();

这将确保框架正确使用 INSERT 或 UPDATE 语句,避免出现重复的 PK 错误。

编辑:增强了示例代码以在新记录时正确填充记录。

【讨论】:

    【解决方案2】:

    在您的模型中,添加以下方法:

    /**
    * Uses the primary keys set on a new record to either create or update
    * a record with those keys to have the last_access value set to the same value
    * as the current unsaved model.
    *
    * Returns the model with the updated last_access. Success can be checked by
    * examining the isNewRecord property.
    *
    * IMPORTANT: This method does not modify the existing model.
    **/
    
    public function updateRecord(){
      $model = self::model()->findByPk(array('user_id'=>$this->user_id,'category_id'=>$this->category_id));
    
      //model is new, so create a copy with the keys set
      if(null === $model){
        //we don't use clone $this as it can leave off behaviors and events
        $model = new self;
        $model->user_id = $this->user_id;
        $model->category_id = $this->category_id;
      }
    
      //At this point we have a model ready for saving, 
      //and don't care if it is new or not
      $model->last_access = $this->last_access;
      $model->save(false);
      return $model;
    }
    

    以上内容的灵感来自一种更通用的方法,我经常使用该方法来执行创建或查找是否已存在的过程。

    使用以下代码执行此操作。

    $userCategory = new UserCategory;
    $userCategory->user_id = 1;
    $userCategory->category_id = 15;
    echo $userCategory->isNewRecord; //always true
    $userCategory->last_access = Now();
    
    $userCategory = $userCategory->updateRecord();
    

    请注意,只有最后一行与您的代码不同。使用new UserCategory 声明的模型实例未更改这一事实是预期行为。

    然后,您可以在代码中验证模型是否使用以下内容保存:

    if(!$userCategory->isNewRecord){
      echo 'save succeeded';
    }
    else{
      echo 'save failed';
    }
    

    【讨论】:

      【解决方案3】:

      如果您尝试更新,则应加载记录,而不是创建新记录。

      UserCategory::model()->findByPk(array('user_id'=> 1,'category_id '=> 15));
      $userCategory->last_access = Now();
      $userCategory->save();
      

      【讨论】:

      • 不知道是更新还是插入。如果是新记录,我需要插入,否则更新,类似于“重复更新”
      • 在你的控制器中有两个不同的函数,在 actionUpdate 中处理更新和在 actionCreate 中插入
      • 我不是在说控制器!这是一个不同的过程
      【解决方案4】:

      在 UserCategory.php 中

      public function isNewRecord()
      {
          $result = $this->findByAttributes(array('user_id'=>$this->user_id,'category_id'=>$this->category_id));
          if($result === NULL)
          {
              return true;
          }
          return false;
      }
      

      然后在控制器中

      $userCategory = new UserCategory;
      $userCategory->user_id = 1;
      $userCategory->category_id = 15;
      echo $userCategory->isNewRecord(); 
      ----
      

      【讨论】:

        【解决方案5】:

        另一种选择是修改模型以更改保存函数的条件,然后调用父保存函数:(此代码在 UserCategory 模型中)

        public function save($runValidation=true,$attributes=null) {
            $exists = UserCategory::model()->findByAttributes(array('category_id'=>$this->category_id,'user_id'=>$this->user_id));
            if($exists) {
                $this->isNewRecord = false;
            }
            return parent::save($runValidation,$attributes);
        }
        

        我刚刚做了一个测试,它似乎工作正常。你应该能够做到这一点:

        $userCategory = new UserCategory;
        $userCategory->user_id = 1;
        $userCategory->category_id = 15;
        $userCategory->last_access = Now();
        $userCategory->save();
        

        应该根据是否找到记录来插入或更新,因此您不必更改任何其他代码。

        【讨论】:

        • 仅供参考,我首先尝试在 beforeSave() 上执行此操作。但是,当 save() 函数由于某种原因运行时,isNewRecord 被重置为 true。
        猜你喜欢
        • 2012-02-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-11
        • 2018-10-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多