【问题标题】:Dependency rule tried to blank out primary key in SQLAlchemy, when foreign key constraint is part of composite primary key当外键约束是复合主键的一部分时,依赖规则试图在 SQLAlchemy 中清除主键
【发布时间】:2014-05-16 15:55:15
【问题描述】:

我有以下模型定义

class Foo(Base):
    __tablename__ = 'foo'

    id = Column(Integer, primary_key=True)
    name = Column(String(200))


class FooCycle(Base):
    __tablename__ = 'foocycle'

    foo_id = Column(
        String(50),
        ForeignKey('foo.id'),
        primary_key=True
    )
    some_number = Column(
        Integer,
        primary_key=True,
    )

    foo = relationship("Foo", backref="cycles")

以及下面的测试用例

class HierarchicModelTest(unittest.TestCase):
    def test_create_data_via_orm_save_twice(self):
        # get_session is a convenience wrapper to access a scoped session object
        s = get_session()

        def create_foo():
            foo = Foo(id="12345", name="fancy foo")
            foo.cycles = [FooCycle(some_number=1)]

            return foo

        # initially create foo
        foo = create_foo()
        s.add(foo)
        s.flush()

        # recreating foo, using merge to update into database
        foo = create_foo()
        s.merge(foo)

        # raises Exception: Dependency rule tried to blank-out primary key
        # column 'foocycle.foo_id' on instance '<FooCycle at 0x32e6b10>'
        s.flush()

测试失败,有一个整洁的小堆栈跟踪和最终断言错误,告诉我“依赖规则试图清除主键列 'foocycle.foo_id”。我假设 SQLAlchemy 不能或不想在 FooCycle 本身上计算 foo_id 的值。我可以自己在create_foo 中明确设置这个值:

def create_foo():
    foo = Foo(id="12345", name="fancy foo")
    foo.cycles = [FooCycle(some_number=1, foo_id="12345")]

    return foo

但是,出于简洁、架构方面的考虑以及不可否认的个人自豪感,我不想这样做。有没有一种简单的方法可以让 SQLAlchemy 解决这个问题。我还没有完全理解依赖规则的目的。关于该问题的任何指示/信息?

堆栈跟踪:

# Test 1 of 7:
# test_core.HierarchicModelTest.test_create_data_via_orm_save_twice
===============
HierarchicModelTest: test_create_data_via_orm_save_twice (tests.test_core.HierarchicModelTest)
Failed test "test_create_data_via_orm_save_twice (tests.test_core.HierarchicModelTest)"! Reason: Dependency rule tried to blank-out primary key column 'foocycle.foo_id' on instance '<FooCycle at 0x39cda10>'
Traceback (most recent call last):
  File "/home/xxx/xxx/xxx/backend/tests/test_core.py", line 115, in test_create_data_via_orm_save_twice
    s.flush()
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py", line 149, in do
    return getattr(self.registry(), name)(*args, **kwargs)
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1879, in flush
    self._flush(objects)
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1997, in _flush
    transaction.rollback(_capture_exception=True)
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", line 57, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1961, in _flush
    flush_context.execute()
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 370, in execute
    rec.execute(self)
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 479, in execute
    self.dependency_processor.process_saves(uow, states)
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/dependency.py", line 552, in process_saves
    uowcommit, False)
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/dependency.py", line 569, in _synchronize
    sync.clear(dest, self.mapper, self.prop.synchronize_pairs)
  File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/sync.py", line 53, in clear
    (r, orm_util.state_str(dest))
AssertionError: Dependency rule tried to blank-out primary key column 'foocycle.foo_id' on instance '<FooCycle at 0x39cda10>'

【问题讨论】:

  • 我不认为这是 SA 无法处理 foo_id 的问题。事实上,这是 SA 确实能够做到这一点的一大优点。我相信问题是在merge SA 期间删除FooCycle 的第一个实例并插入新的实例。删除第一个是问题,因为它不会删除它的行,而只是尝试删除关系。所以你需要为你的关系配置正确的cascade规则。
  • 感谢您的评论,为我指明了正确的方向。
  • @van 你能告诉我们在这种情况下 proper 级联规则是什么吗(最好作为答案,因为我猜这可能太多不适合评论)?你说它不会删除它的行,而只是尝试删除关系;但是如果行被删除,那么我们每次都会得到一个FooCycle 的新实例。有没有办法让 SA 更新现有的 FooCycle 行/实例?
  • @PiotrDobrogost:SA 不会自动更新现有的行/实例。您应该编写代码首先搜索关系,然后将新实例中的数据合并到现有实例中。
  • 我在 SQLAlchemy 新闻组 - Dependency rule tried to blank-out primary key column when trying to update using association proxy 上提出了类似的问题 - SQLAlchemy 的作者 Mike Bayer 对此进行了有趣的深入研究。

标签: python sqlalchemy


【解决方案1】:

根据 van 的评论,我能够找到解决方案。默认关系级联为"save-update, merge"。我必须将其设置为"save-update, merge, delete, delete-orphan"

添加delete 本身并没有改变行为,delete-orphan 是必要的。

仅添加delete-orphan 导致删除测试用例失败,出现问题中提到的“依赖规则”断言错误:

class HierarchicModelTest(unittest.TestCase):
    def test_delete_parent_object(self):
        foo = Foo(**foo_data).save()
        self.assertEqual(Foo.query.count(), 1)
        self.assertEqual(FooCycle.query.count(), 1)

        s = get_session()
        s.delete(foo)
        s.flush()

        self.assertEqual(Foo.query.count(), 0)
        self.assertEqual(FooCycle.query.count(), 0)

--

  File "/home/xxx/xxx/xxx/backend/tests/test_core.py", line 128, in test_delete_parent_object
     s.flush()
  [...]
  AssertionError: Dependency rule tried to blank-out primary key column 'foocycle.foo_id' on instance '<FooCycle at 0x37a1710>'

来自 SQLAlchemy 文档:

delete-orphan 级联向delete 级联添加行为,这样子对象在与父对象解除关联时将被标记为删除,而不仅仅是在父对象被标记为删除时。

那么,FooCycle模型的正确定义是

class FooCycle(Base):
    __tablename__ = 'foocycle'

    foo_id = Column(
        String(50),
        ForeignKey('foo.id'),
        primary_key=True
    )
    some_number = Column(
        Integer,
        primary_key=True,
    )

    foo = relationship("Foo",
                       backref=backref("cycles",
                                        cascade="save-update, merge, "
                                                "delete, delete-orphan"))

【讨论】:

  • 但是使用delete-orphan 会导致FooCycle 的现有实例被删除而不是更新。如果FooCycle 的身份很重要(例如,当我们想通过保留所有历史值的历史来对其进行版本化时),则无法使用此解决方案。
  • @room2web: 你有什么理由不简单地用cascade="all, delete-orphan" 代替cascade="save-update, merge, delete, delete-orphan"
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-23
  • 1970-01-01
  • 2015-06-23
  • 2016-07-20
  • 2015-08-20
  • 1970-01-01
相关资源
最近更新 更多