【问题标题】:SQL Server triggers and sqlalchemy interference problem. Help neededSQL Server 触发器和 sqlalchemy 干扰问题。需要帮助
【发布时间】:2011-06-15 02:34:10
【问题描述】:

我需要对一些关键表进行“版本控制”,并尝试以一种相当简单的方式实现它:

CREATE TABLE [dbo].[Address] (
  [id] bigint IDENTITY(1, 1) NOT NULL,
  [post_code] bigint NULL,
...
)

CREATE TABLE [dbo].[Address_History] (
  [id] bigint NOT NULL,
  [id_revision] bigint NOT NULL,
  [post_code] bigint NULL,
...
  CONSTRAINT [PK_Address_History] PRIMARY KEY CLUSTERED ([id], [id_revision]),
  CONSTRAINT [FK_Address_History_Address]...
  CONSTRAINT [FK_Address_History_Revision]...
)

CREATE TABLE [dbo].[Revision] (
  [id] bigint IDENTITY(1, 1) NOT NULL,
  [id_revision_operation] bigint NULL,
  [id_document_info] bigint NULL,
  [description] varchar(255) COLLATE Cyrillic_General_CI_AS NULL,
  [date_revision] datetime NULL,
...
)

每个表都有一组插入/更新/删除触发器,用于存储其更改。

我的应用程序基于 PyQt + sqlalchemy,当我尝试插入存储在版本化表中的实体时,sqlalchemy 会触发错误:

The target table 'Heritage' of the DML statement cannot have 
any enabled triggers if the statement contains 
an OUTPUT clause without INTO clause. 
(334) (SQLExecDirectW); [42000] 
[Microsoft][ODBC SQL Server Driver]
[SQL Server]Statement(s) could not be prepared. (8180)")

我该怎么办?我必须使用 sqlalchemy。 如果有人可以给我一个建议,我如何在没有触发器的情况下实现版本控制,那就太酷了。

【问题讨论】:

  • 正如错误所说,您的 SQL 语句显然包含 OUTPUT 子句,对吧?你能把它变成OUTPUT (columns) INTO (table variable) 子句吗?这应该(希望)解决错误提到的问题......
  • sqlalchemy 是 ORM 工具,它自己进行插入、更新和删除实体的 sql 查询:|
  • 不熟悉 SQL Alchemy - 有没有办法在配置它时对它“撒谎”,以至于它认为它正在与 SQL Server 2000 实例(而不是更高版本,并假设你不需要以后的功能)? SQL Server 2000 没有 OUTPUT 子句,所以它可能不会生成这样的 SQL。
  • @Damien_The_Unbeliever 好主意,但没有办法。 sqlalchemy 从 sql 驱动程序获取实例类型 :) 但它有数据映射选项,可以解决问题。但是现在,当我将实体插入数据库时​​,保存查询中没有获得它的 id,我必须强制 sqlalchemy 重新读取它!我宁愿重新实现版本控制并消除触发器,这个问题相当糟糕:/

标签: sql sql-server triggers sqlalchemy


【解决方案1】:

您应该将 'implicit_returning' 设置为 'False' 以避免在 SQLAlchemy 生成的查询中使用“OUTPUT”(这应该可以解决您的问题):

class Company(sqla.Model):
    __bind_key__ = 'dbnamere'
    __tablename__ = 'tblnamehere'
    __table_args__ = {'implicit_returning': False}  # http://docs.sqlalchemy.org/en/latest/dialects/mssql.html#triggers
    id = sqla.Column('ncompany_id', sqla.Integer, primary_key=True)
    ...

【讨论】:

  • 在任何人实施此之前,请阅读以下答案。
【解决方案2】:

我似乎无法添加评论,所以添加另一个答案。

它并没有那么复杂,我建议它不如将 1/2 的业务逻辑放在域中,另一半放在数据库触发器中那么脆弱。

我个人会编写自己的列表对象,并引用 some_list_of_other_entities 的历史列表,并在 Remove 和 Add 方法中维护您的历史记录。

这样你的对象在保存到你的 ORM 之前就会自动更新。

public class ListOfOtherEntities : System.Collections.IEnumerable
{
    // Add list stuff here...

    public void Remove(MyEntity obj)
    {
        this.List.Remove(obj);
        this.History.Add(new History("Added a object!");
    }

    public void Remove(MyEntity obj)
    {
        this.List.Remove(obj);
        this.History.Add(new History("Removed a object!");
    }
}

这样,您的对象甚至在将它们保存到您的 ORM 之前就会自动更新,并且查看代码的其他开发人员可以很容易地看到您所做的工作。

【讨论】:

  • 谢谢,这个主意不错。我可以对 sqlalchemy 的版本化实体的检测列表进行猴子补丁并获得我想要的行为,感谢 Python 是动态语言:)
  • 当我将单个实体提交到数据库时,我仍然无法得到这种行为,因为实体不知道如何处理数据库,但我想我可以修改 db mapper。此外,有趣的是,如何使用静态类型语言 f.x 来做到这一点。基于CLR的和像NHibernate这样的ORM...
  • 我猜你已经让你的实体维护了需要保存到数据库的历史记录:entity.some_list_of_other_entities 和 entity.list_of_history_for_other_entities...
  • 出于兴趣,大约 6 个月前我遇到了同样的问题,我不得不在添加历史记录之前添加一个检查以查看该项目是否是新的,但其余部分运行良好。
【解决方案3】:

这不会直接回答您的问题,但根据我使用触发器的经验,会导致无尽的痛苦,因此请不惜一切代价避免使用它们。如果您自己管理所有数据,那么简单的答案就是自己填充版本历史记录表。这也意味着您将所有业务逻辑集中在一个地方,这是一种奖励!

【讨论】:

  • 我使用 ORM,所以我宁愿避免在我的应用程序中放置一些明确的历史存储逻辑。想象一下,在您的代码中的某处: entity.some_list_of_other_entities.remove(entity_to_delete); entity.history.append(entity-to-delete) 在您的代码中随处可见。它很复杂,非常脆弱,并且倾向于绑定实体及其在 ORM 层上的历史,这就是 BAAAAAD。所以,我只是接受了这种奇怪的行为并记住,在某些情况下我必须从数据库调用刷新。
  • 您能给个建议吗,如何使用关系数据正确实现版本控制 对象数据 业务逻辑方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多