【问题标题】:How to clone a SQLAlchemy object with new primary key如何使用新的主键克隆 SQLAlchemy 对象
【发布时间】:2015-05-06 10:36:18
【问题描述】:

我想克隆一个 SQLAlchemy 对象:

我试过了:

product_obj = products.all()[0] #here products is service name

product_obj.product_uid = 'soemthing' #here product_uid is the pk of product model

products.save(product_obj)

但它只是更新旧对象。

这里是products.save函数的代码:

class Service(object):

        __model__ = None

       def save(self, model):
            self._isinstance(model)
            db.session.add(model)
            db.session.commit()
            return model

【问题讨论】:

    标签: python sqlalchemy flask-sqlalchemy


    【解决方案1】:

    这应该可行:

    product_obj = products.all()[0]
    
    db.session.expunge(product_obj)  # expunge the object from session
    make_transient(product_obj)  # http://docs.sqlalchemy.org/en/rel_1_1/orm/session_api.html#sqlalchemy.orm.session.make_transient
    
    product_obj.product_uid = 'something'
    db.session.add(product_obj)
    

    【讨论】:

    • make_transient 没有删除主键似乎很烦人。如果创建条目的副本是主要用例,那岂不是很有意义?
    • @SebK 问题是您可能需要对象的副本而不更改 PK(无论出于何种原因)。通过保留 PK,API 更具包容性,因为在需要时更改 ID 比在需要时保留 ID 引用更干净。
    • 谢谢!还请注意,像我这样的新手(1)在 from sqlalchemy.orm.session import make_transient 中找到 make_transient 和(2)将主键设置为 None 然后将在 session.add(obj)session.commit() 上使用自动生成主键。
    • 这是非常危险的,因为现在对该对象的所有引用都将引用新对象。甚至通过不同的执行路径获得不同名称的引用。按属性复制对象属性更安全。
    • 这个“解决方案”让我的很多值都为空。
    【解决方案2】:

    对于 sqlalchemy 1.3,我最终使用了一个辅助函数。

    1. 它将所有非主键列从输入模型复制到新模型实例。
    2. 它允许您直接将数据作为关键字参数传递。
    3. 它使原始模型对象保持不变。
    def clone_model(model, **kwargs):
        """Clone an arbitrary sqlalchemy model object without its primary key values."""
        # Ensure the model’s data is loaded before copying.
        model.id
    
        table = model.__table__
        non_pk_columns = [k for k in table.columns.keys() if k not in table.primary_key.columns.keys()]
        data = {c: getattr(model, c) for c in non_pk_columns}
        data.update(kwargs)
    
        clone = model.__class__(**data)
        db.session.add(clone)
        db.session.commit()
        return clone
    

    使用此功能,您可以使用以下方法解决上述问题:

    product_obj = products.all()[0]  # Get the product from somewhere.
    cloned_product_obj = clone_model(product_obj, product_uid='something')
    

    根据您的用例,您可能希望从此函数中删除对 db.session.commit() 的调用。


    此答案基于https://stackoverflow.com/a/13752442/769486(如何获取模型的列?)和How do I get the name of an SQLAlchemy object's primary key?(如何获取模型的主键?)。

    【讨论】:

    • 有人用这种方法处理关系吗?
    • @j_walker_dev,你问的是“深度克隆”,你不仅克隆一个对象,而且克隆所有包含/相关的对象?或者只是处理克隆对象中的关系?如果是后者,在添加到会话之前,我已经成功设置了关系键。例如。我有一个容器表和一个项目表。我创建一个新容器,克隆一堆项目,并将每个克隆项目的容器设置为新容器。在您提交之前,有些事情不会起作用,但通常可以正常工作
    • 这种方法依赖于模型上的列属性名称与列本身的名称相匹配。通常,这是真的 - 这是通常的做法,而且往往会让人感到困惑! - 但不能保证。构建要复制的属性字典而不是使用.__table__.columns 的一种不太漂亮但更健壮的方法是使用vars 循环模型类的属性,并使用@987654331 检查每个属性值是否是Column @.
    • .__mapper__.columns 列出模型的列名。
    【解决方案3】:

    一种可能的方法是使用dictalchemy,它扩展了 SQLAlchemy:

    Dictalchemy 将 utils.asdict() 和 utils.fromdict() 方法添加到 SQLAlchemy 模型。

    例子:

    new_instance = InstanceModel(**old_instance.asdict())
    

    【讨论】:

    • 我无法在网上任何地方找到课程InstanceModel。是在dictalchemySQLAlchemy 还是其他地方?我已经搜索了两个代码库。
    • 它不存在——这只是虚构示例类的名称。我可能因为不使用与原始问题相同的变量名称而不清楚,但如果您想象原始问题中产品的模型类是ProductModel,第一行将类似于product_obj = ProductModel(**products.all()[0].asdict())(提供dictalchemy)。或者,您可以使用 type: product_obj = type(products.all()[0])(**products.all()[0].asdict()) 找到对象的类。
    【解决方案4】:

    此方法将克隆任何 sqlalchemy db 对象。将它添加到模型的类中。 请注意,新对象的 id 将在提交期间创建(见评论):

    def clone(self):
        d = dict(self.__dict__)
        d.pop("id") # get rid of id
        d.pop("_sa_instance_state") # get rid of SQLAlchemy special attr
        copy = self.__class__(**d)
        db.session.add(copy)
        # db.session.commit() if you need the id immediately
        return copy
    

    【讨论】:

    • 您无需提交即可获得自动生成的id 字段来自动填充; db.session.flush() 应该够了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-17
    • 2018-03-14
    • 1970-01-01
    • 2017-06-19
    • 2023-03-18
    • 2010-11-08
    • 2012-11-17
    相关资源
    最近更新 更多