推进 ORM 的问题

Posted

技术标签:

【中文标题】推进 ORM 的问题【英文标题】:Problem with Propel ORM 【发布时间】:2010-11-09 20:52:00 【问题描述】:

我在开发中使用 Propel ORM,我遇到了这个问题:

$mo = new Category();
$mo->setId(7); // This Id exists
$mo->setDescription('New description!');
$mo->save(); //it should update!

嗯,它不会更新值。它抛出一个异常并说我不能插入重复的键。有没有办法解决这个问题?

希望有人可以提供帮助, 大卫

[编辑]

我知道我可以做到:

$mo = CategoryQuery::create()
             ->findByPk(7)
             ->setDescription("something")
             ->save();

我知道这行得通,但由于我的项目的具体问题,我不能这样做。所以这就是问题的原因。

再次感谢!

【问题讨论】:

我正在使用 Doctrine,所以这只是猜测。问题可能是如果您在 id 上设置了自动增量。 根据 Propel Docs,这是正确的方法(您不想使用的方法)。是否可以在您的项目中将所需的对象传递给此函数并对其进行编辑和保存?你能给我们举个例子说明为什么不能使用“正确”的方法吗? 我想我得想办法解决这个问题 【参考方案1】:

这似乎就像旧线程无论如何这里是我想提到的更新答案

$q = array(1,2,3,4);
$rp = RolesPermissionsQuery::create()->filterById($roleId)->findOneOrCreate();
$rp->setRolesId($roleId);
$rp->setPermissions(serialize($q));
$rp->save();

在上面的代码中,propel 将根据条件创建新行或更新现有行。希望这可能对某人有所帮助。

【讨论】:

我想说 ->findOneOrCreate() 是这个问题的最佳答案,也是唯一的答案。它干净快捷。【参考方案2】:

“伪造”现有对象

Propel 根据对要保存的对象调用isNew() 的结果在插入和更新之间进行选择(请参阅BaseCategory::save() 函数:$isInsert = $this->isNew();)。因此,您可以通过自己更改此属性来欺骗它认为它是现有对象:$mo->setNew(false);isNew()setNew() 在 the BaseObject class 中定义。

一般来说,使用部分水合的对象可能不是一个好主意(这似乎是您在这里所做的:您创建了一个对象,但随后不要用它们的实际数据库填充所有属性值)。某些行为或您自己的代码可能依赖于对象的两个属性,然后给出不正确的结果。一个简化的例子:如果你有一个自动生成的字段nameAndDescription,你在保存时将它设置为namedescription字段的连接(通过扩展Propel对象,通过新的preInsert()和@ 987654334@ 钩子),如果您像在此处那样更新对象,这将不会达到您的预期。但这是我所知道的唯一警告,这可能是您可以控制的情况。

在不创建对象的情况下更新字段

如果您只想更新某些字段,甚至可能更新多个对象(例如:对于今天之前具有executionDate 的所有Order 对象,将status 设置为"archived"),您可以这样做自己打电话给BasePeer::doUpdate()。第一个参数是您选择的 Criteria 对象:所有 Order 对象在今天之前具有 executionDate。第二个参数也是一个 Criteria 对象,但它用于存储新值:status"archived"。它应该看起来像这样(未经测试):

// This probably also works with a Query object
$selectCriteria = new Criteria(OrderPeer::DATABASE_NAME);
$selectCriteria->add(OrderPeer::EXECUTION_DATE, time(), Criteria::LESS_THAN);

// And this too, it's just used as a simple hash table
$valueCriteria = new Criteria(OrderPeer::DATABASE_NAME);
$valueCriteria->add(OrderPeer::STATUS, "archived");

$con = Propel::getConnection(OrderPeer::DATABASE_NAME, Propel::CONNECTION_WRITE);
BasePeer::doUpdate($selectCriteria, $valueCriteria, $con);

这个方法当然不会执行你在 php 代码中定义的任何 preUpdate()postUpdate() 钩子,因为生成的对象被完全绕过了。因此,仅在绝对必要时使用它(出于性能原因?),并且您知道周围没有其他“陈旧”对象。

【讨论】:

【参考方案3】:

解决此问题的最快方法是首先查询实体,然后对其进行更新(这样您就不会使用分离的实体):

$mo = CategoryPeer::retrieveByPK(7);
$mo->setDescription('New description!');
$mo->save(); // should work since the entity was retrieved first

如果您需要能够在不先查询的情况下进行更新,恐怕我太像 Propel 新手了,无法就此提出建议。可能可以执行手动 SQL 语句,但也可能有一种方法可以附加您手动设置 id 的实例。也许另一个回答者可以启发我们。

【讨论】:

我同意你的观点,它也许是更好的解决方案,但不是我想要的,我会编辑这个问题。 edit应该是 CategoryQuery::create()->finByPk(7); 很高兴知道,感谢您更新问题。不过,恐怕我没有您想要的答案。【参考方案4】:

使用 Find One Or Create 这将查找或创建一个对象

$mo = CategoryQuery::create() ->filterByPk(7) ->findOneOrCreate();

$mo->setDescription("something");

if ($mo->validate()) $mo->save()

【讨论】:

以上是关于推进 ORM 的问题的主要内容,如果未能解决你的问题,请参考以下文章

现实世界中的ORM

推进:没有交叉表的多对多关系

ZF 整合的推进或原则

推动 ORM:选择“不在”条件下的条件

我会为此使用啥查询

推进 2 的问题