AppEngine(或Django)中有叫insert_or_update的快捷方法吗?为什么没有?

2 投票
3 回答
765 浏览
提问于 2025-04-17 17:20

我发现我经常会遇到这样的情况:(我觉得这应该很常见吧?)

Google App Engine

# Insert if object doesn't exist, Otherwise Update Object
obj = get_or_insert('123', title='Hello World')
obj.title = 'Hello World'
obj.puts()

Django

# Insert if object doesn't exist, Otherwise Update Object
obj, created = Model.objects.get_or_create(id='123', 
                   defaults={'title':'Hello World'}
if not created:
    obj.title = 'Hello World'
    obj.save()

如果我能直接调用 insert_or_update 方法,那该多好啊,对吧?比如:

# AppEngine
obj = insert_or_update('123', title='Hello World')

# Django
obj = Model.objects.insert_or_update(id='123', defaults={'title': 'Hello World'})

更新

回到 App Engine 的 文档,我发现了关于 get_or_insert 方法的说明。大概是这样的:

def txn(key_name, **kwds):
    entity = Story.get_by_key_name(key_name, parent=kwds.get('parent'))
    if entity is None:
        entity = Story(key_name=key_name, **kwds)
        entity.put()
    return entity

def get_or_insert(key_name, **kwargs):
    return db.run_in_transaction(txn, key_name, **kwargs)

get_or_insert('some key', title="The Three Little Pigs")

我在想,为什么在 App Engine 和 Django 中都没有创建一个 insert_or_update 方法呢。

App Engine 的解决方法

@classmethod
def insert_or_update(cls, key_name, parent=None, **kwargs):
    def _tx():
        entity = cls.get_by_key_name(key_name, parent=parent)
        if entity:
            for key in kwargs:
                setattr(entity, key, kwargs[key])
        else:
            entity = cls(key_name=key_name, parent=parent, **kwargs)
        entity.put()
        return entity
    return db.run_in_transaction(_tx)

Django 的解决方案

见下文

3 个回答

0

这可能没有你想的那么普遍。就我个人而言,我并不常常需要这样做,偶尔需要的时候,通常也只是比你提到的 insert_or_update 方法多两三行代码,这并不足以让我觉得一定要有这个功能,或者让我有动力自己去添加它。不过,我过去确实用过 MySQL 的 INSERT … ON DUPLICATE KEY UPDATE 这个功能很多次。

我自己也有过几次这样的经历,就在几周前,我在想为什么 Django 没有 M2M(多对多)关系的 toggle 方法……最后我自己做了一个:

https://github.com/Matt-Stevens/django/commit/f7c36

编辑

Django 1.7 看起来会有这个 "UPSERT" 的功能,https://github.com/django/django/commit/6272d

0

如果我理解这个问题没错的话:

obj, created = Model.objects.get_or_create(id='123', 
               defaults={'title':'Hello World'})

上面的内容应该换成下面这个:

obj, created = Model.objects.get_or_create(foo='alpha', bar='beta' 
               defaults={'title':'Hello World'})

这里的 foobar 是模型中的字段。这样可以解决在django中出现的重复问题。至于appengine的情况就不太确定了。希望这对你有帮助!

1

Django

update_or_create(来自Django官方文档)

update_or_create(defaults=None, **kwargs)

在Django 1.7版本中新增加的功能。

这个方法很方便,可以用给定的 kwargs 来更新一个对象,如果没有这个对象,就会创建一个新的。defaults 是一个字典,里面包含了(字段,值)的配对,用来更新对象。

这个方法会返回一个元组 (object, created),其中 object 是被创建或更新的对象,而 created 是一个布尔值,表示是否创建了一个新对象。

update_or_create 方法会根据给定的 kwargs 从数据库中查找一个对象。如果找到了,就会更新 defaults 字典中传入的字段。

这个方法的目的是为了简化一些重复的代码。例如:

try:
    obj = Person.objects.get(first_name='John', last_name='Lennon')
    for key, value in updated_values.iteritems():
        setattr(obj, key, value)
    obj.save()
except Person.DoesNotExist:
    updated_values.update({'first_name': 'John', 'last_name': 'Lennon'})
    obj = Person(**updated_values)
    obj.save()

当模型中的字段数量增加时,这种模式会变得相当复杂。上面的例子可以用 update_or_create() 这样重写:

obj, created = Person.objects.update_or_create(
    first_name='John', last_name='Lennon', defaults=updated_values)

关于如何解析传入 kwargs 的名称的详细描述,可以查看 get_or_create()

正如上面在 get_or_create() 中所描述的,这个方法可能会出现竞争条件,如果数据库层面没有强制唯一性,可能会导致同时插入多行数据。

AppEngine

目前还不太幸运,正如Matt之前提到的,这可能不是一个常见的使用场景。也许在AppEngine中,这种使用场景并不多。

撰写回答