簡體   English   中英

SQLAlchemy - 不對關系強制執行外鍵約束

[英]SQLAlchemy - don't enforce foreign key constraint on a relationship

我有一個Test模型/表和一個TestAuditLog模型/表,使用SQLAlchemy和SQL Server 2008.兩者之間的關系是Test.id == TestAuditLog.entityId ,其中一個測試有許多審計日志。 TestAuditLog旨在保留Test表中行的更改歷史記錄。 我想跟蹤Test被刪除的時間,但是我遇到了麻煩。 在SQL Server Management Studio中,我設置了FK_TEST_AUDIT_LOG_TEST關系的“ 強制執行外鍵約束 ”屬性設置為“否”,認為將允許TestAuditLog行與存在entityId不再連接到任何Test.id因為Test已被刪除。 但是,當我嘗試使用SQLAlchemy創建TestAuditLog然后刪除Test ,我收到一個錯誤:

(IntegrityError)('23000',“[23000] [Microsoft] [ODBC SQL Server驅動程序] [SQL Server]無法將值NULL插入列'AL_TEST_ID',表'TEST_AUDIT_LOG';列不允許空值.UPDATE失敗。 (515)(SQLExecDirectW); [01000] [Microsoft] [ODBC SQL Server驅動程序] [SQL Server]語句已終止。(3621)“)u'UPDATE [TEST_AUDIT_LOG] SET [AL_TEST_ID] =? WHERE [TEST_AUDIT_LOG]。[AL_ID] =?' (無,8)

我認為由於TestTestAuditLog之間的外鍵關系,在我刪除Test行之后,SQLAlchemy試圖將所有測試的審計日志更新為具有NULL entityId 我不希望它這樣做; 我希望SQLAlchemy單獨保留審計日志。 如何讓SQLAlchemy允許審計日志存在,其entityId不與任何Test.id連接?

我嘗試從表中刪除ForeignKey ,但我仍然可以說myTest.audits並獲得所有測試的審計日志,而SQLAlchemy抱怨不知道如何加入TestTestAuditLog 當我然后指定一個primaryjoin上的relationship ,它抱怨不具有ForeignKeyForeignKeyConstraint與列。

這是我的模特:

class TestAuditLog(Base, Common):
    __tablename__ = u'TEST_AUDIT_LOG'
    entityId = Column(u'AL_TEST_ID', INTEGER(), ForeignKey(u'TEST.TS_TEST_ID'),
        nullable=False)
    ...

class Test(Base, Common):
    __tablename__ = u'TEST'
    id = Column(u'TS_TEST_ID', INTEGER(), primary_key=True, nullable=False)
    audits = relationship(TestAuditLog, backref="test")
    ...

這是我如何在保持審計日志的同時刪除測試,其entityId完好無損:

    test = Session.query(Test).first()
    Session.begin()
    try:
        Session.add(TestAuditLog(entityId=test.id))
        Session.flush()
        Session.delete(test)
        Session.commit()
    except:
        Session.rollback()
        raise

你可以通過以下方式解決

  • POINT-1:RDBMS級別和SA級別上都沒有ForeignKey
  • POINT-2:明確指定關系的連接條件
  • POINT-3:標記關系級聯依賴passive_deletes標志

下面的完整工作代碼片段應該給你一個想法( code中突出顯示):

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
engine = create_engine('sqlite:///:memory:', echo=False)

Session = sessionmaker(bind=engine)

class TestAuditLog(Base):
    __tablename__ = 'TEST_AUDIT_LOG'
    id = Column(Integer, primary_key=True)
    comment = Column(String)

    entityId = Column('TEST_AUDIT_LOG', Integer, nullable=False,
                     # POINT-1
                     #ForeignKey('TEST.TS_TEST_ID', ondelete="CASCADE"),
                     )

    def __init__(self, comment):
        self.comment = comment

    def __repr__(self):
        return "<TestAuditLog(id=%s entityId=%s, comment=%s)>" % (self.id, self.entityId, self.comment)

class Test(Base):
    __tablename__ = 'TEST'
    id = Column('TS_TEST_ID', Integer, primary_key=True)
    name = Column(String)

    audits = relationship(TestAuditLog, backref='test',
                # POINT-2
                primaryjoin="Test.id==TestAuditLog.entityId",
                foreign_keys=[TestAuditLog.__table__.c.TEST_AUDIT_LOG],
                # POINT-3
                passive_deletes='all',
            )

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return "<Test(id=%s, name=%s)>" % (self.id, self.name)


Base.metadata.create_all(engine)

###################
## tests
session = Session()

# create test data
tests = [Test("test-" + str(i)) for i in range(3)]
_cnt = 0
for _t in tests:
    for __ in range(2):
        _t.audits.append(TestAuditLog("comment-" + str(_cnt)))
        _cnt += 1
session.add_all(tests)
session.commit()
session.expunge_all()
print '-'*80

# check test data, delete one Test
t1 = session.query(Test).get(1)
print "t: ", t1
print "t.a: ", t1.audits
session.delete(t1)
session.commit()
session.expunge_all()
print '-'*80

# check that audits are still in the DB for deleted Test
t1 = session.query(Test).get(1)
assert t1 is None
_q = session.query(TestAuditLog).filter(TestAuditLog.entityId == 1)
_r = _q.all()
assert len(_r) == 2
for _a in _r:
    print _a

另一種選擇是復制FK中使用的列,並使用ON CASCADE SET NULL選項使FK列可為ON CASCADE SET NULL 通過這種方式,您仍然可以使用此列檢查已刪除對象的審計跟蹤。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM