SQLAlchemy – не применяет ограничение внешнего ключа для отношений

У меня есть Test модель / таблица и модель / таблица TestAuditLog с использованием SQLAlchemy и SQL Server 2008. Связь между ними – Test.id == TestAuditLog.entityId , причем один тест имеет много журналов аудита. TestAuditLog предназначен для хранения истории изменений в строках таблицы Test . Я хочу отслеживать, когда Test удаляется, но у меня возникают проблемы с этим. В SQL Server Management Studio я установил FK_TEST_AUDIT_LOG_TEST « FK_TEST_AUDIT_LOG_TEST ограничение внешнего ключа » FK_TEST_AUDIT_LOG_TEST «Нет», думая, что позволит TestAuditLog строка TestAuditLog с entityId которая больше не подключается к любому Test.id потому что Test удален , Однако, когда я пытаюсь создать TestAuditLog с SQLAlchemy, а затем удалить Test , я получаю сообщение об ошибке:

(IntegrityError) («23000», «[23000] [Microsoft] [Драйвер SQL-сервера ODBC] [SQL Server] Невозможно вставить значение NULL в столбец« AL_TEST_ID », таблицу« TEST_AUDIT_LOG », столбец не допускает нулей. Ошибка UPDATE. (515) (SQLExecDirectW); [01000] [Microsoft] [драйвер SQL-сервера ODBC] [SQL Server] Оператор завершен. (3621) ") u'UPDATE [TEST_AUDIT_LOG] SET [AL_TEST_ID] =? WHERE [TEST_AUDIT_LOG]. [AL_ID] =? ' (Нет, 8)

Я думаю, из-за внешних ключей между Test и TestAuditLog , после того, как я удаляю строку Test , SQLAlchemy пытается обновить все журналы аудита теста, чтобы иметь NULL entityId . Я не хочу, чтобы это делалось; Я хочу, чтобы SQLAlchemy оставила только журналы аудита. Как я могу сказать SQLAlchemy, чтобы разрешить существование журналов аудита, чей entityId не подключается к любому Test.id ?

Я попробовал просто удалить ForeignKey из своих таблиц, но я все равно хочу сказать myTest.audits и получить все журналы аудита теста, а SQLAlchemy жаловалась на то, что не знает, как присоединиться к Test и TestAuditLog . Когда я затем указывал primaryjoin на relationship , он ворчал о том, что не имеет ForeignKey или ForeignKeyConstraint со столбцами.

Вот мои модели:

 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 

One Solution collect form web for “SQLAlchemy – не применяет ограничение внешнего ключа для отношений”

Вы можете решить эту проблему:

  • POINT-1: не иметь ForeignKey ни на уровне RDBMS ни на уровне СА
  • 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 "ta: ", 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, и сделать столбец FK нулевым с опцией ON CASCADE SET NULL . Таким образом, вы можете проверить контрольный след удаленных объектов с помощью этого столбца.

  • Может ли SQLAlchemy с нетерпением / объединенными нагрузками быть подавленными после настройки?
  • Получение последнего идентификатора вставки с помощью SQLAlchemy
  • Как определить два отношения к одной таблице в SQLAlchemy
  • Доля моделей sqlalchemy между флягой и другими приложениями
  • Запрос SQLAlchemy, соединение по отношениям и порядку по счету
  • Как сделать INSERT INTO t1 (SELECT * FROM t2) в SQLAlchemy?
  • Как сопоставить один класс с несколькими таблицами с SQLAlchemy?
  • Транзакция в транзакции
  • SQLalchemy указывает, какой индекс использовать
  • Sqlalchemy динамически создает объединения
  • Flask в uWSGI вызывает 500 Internal Server Error только от импорта SQLAlchemy
  • Python - лучший язык программирования в мире.