【问题标题】:SQLAlchemy error when inserting into a table whose Primary Key is also a Foreign Key插入主键也是外键的表时出现 SQLAlchemy 错误
【发布时间】:2019-09-15 20:26:42
【问题描述】:

我正在使用 SQLAlchemy 与 SQL Server 数据库进行交互。

我的数据库中的一个表有一个主键,它也是一个外键(建模可选的一对一关系)。尝试使用 SQLAlchemy ORM 插入此表会产生意外错误; SQLAlchemy 似乎试图在外表中创建一个新行,然后将其 ID 用作外键/主键列的值 - 完全忽略该列的显式指定值。

具体来说,我的数据库架构的相关部分如下:

CREATE TABLE [dbo].[DataType] (
  [Id] INT IDENTITY (1,1) NOT NULL,
  [Name] NVARCHAR(200) NOT NULL UNIQUE,
  [DataTable] NVARCHAR(50) NOT NULL,
  CONSTRAINT [PK_Type] PRIMARY KEY CLUSTERED ([Id] ASC),
)

CREATE TABLE [dbo].[DataSet] (
  [Id] INT IDENTITY (1,1) NOT NULL,
  [DataTypeId] INT NOT NULL,
  CONSTRAINT [PK_DataSet] PRIMARY KEY CLUSTERED ([Id] ASC),
  CONSTRAINT [FK_DataSet_DataType] FOREIGN KEY ([DataTypeId]) REFERENCES [dbo].[Type] ([Id]),
)

CREATE TABLE [dbo].[ScalarData] (
  [DataSetId] INT NOT NULL,
  [Value] VARBINARY(MAX) NOT NULL,
  CONSTRAINT [PK_ScalarData] PRIMARY KEY CLUSTERED ([DataSetId] ASC),
  CONSTRAINT [FK_ScalarData_DataSet] FOREIGN KEY ([DataSetId]) REFERENCES [dbo].[DataSet] ([Id]) ON DELETE CASCADE,
)

我使用sqlacodegen 工具自动生成适当的 SQLAlchemy 模型代码,产生以下输出:

from sqlalchemy import Column, ForeignKey, Integer, LargeBinary, Unicode
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
metadata = Base.metadata


class DataType(Base):
    __tablename__ = "DataType"

    Id = Column(Integer, primary_key=True)
    Name = Column(Unicode(200), nullable=False, unique=True)


class DataSet(Base):
    __tablename__ = "DataSet"

    Id = Column(Integer, primary_key=True)
    TypeId = Column(ForeignKey("DataType.Id"), nullable=False)

    DataType = relationship("DataType")


class ScalarData(DataSet):
    __tablename__ = "ScalarData"

    DataSetId = Column(ForeignKey("DataSet.Id"), primary_key=True)
    Value = Column(LargeBinary, nullable=False)

尝试使用 ORM session.add() 样式插入 ScalarData 时会出现问题。看来,当添加 ScalarData 对象时,SQLAlchemy always 尝试创建一个新的 DataSet 对象以在 DataSetId 中引用 - 但这失败了,因为新的 DataSet 对象提供DataTypeId 的空值,不可为空。

所需的行为是我可以显式创建一个DataSet,然后将其Id 作为DataSetId 的值传递给创建一个新的ScalarData 对象-但是当我这样做时,似乎DataSetId 的传入值被完全忽略,SQLAlchemy 仍然尝试创建一个新的 DataSet。

奇怪的是,如果我使用session.execute() 插入新的ScalarData,似乎不会出现问题。

以下是重现错误的最小示例,从具有上述架构的空数据库开始:

from sqlalchemy import create_engine
from sqlalchemy.orm import Session

engine = create_engine(
    "mssql+pyodbc://username:password@localhost/my_database?driver=ODBC Driver 17 for SQL Server"
)

session = Session(bind=engine)

datatype = DataType(Name="foo")
session.add(datatype)
session.flush()

dataset1 = DataSet(TypeId=datatype.Id)
session.add(dataset1)
session.flush()

dataset2 = DataSet(TypeId=type.Id)
session.add(dataset2)
session.flush()

session.execute(
    ScalarData.__table__.insert().values(DataSetId=dataset1.Id, Value=b"123")
)
session.flush() # this goes through just fine

data = ScalarData(DataSetId=dataset2.Id, Value=b"123")
session.add(data)
session.flush() # error raised here

引发的异常如下:

sqlalchemy.exc.IntegrityError: (pyodbc.IntegrityError) ('23000', "[23000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot insert the value NULL into column 'DataTypeId', table 'my_database.dbo.DataSet'; column does not allow nulls. INSERT fails. (515) (SQLExecDirectW)")
[SQL: INSERT INTO [DataSet] ([DataTypeId]) OUTPUT inserted.[Id] VALUES (?)]
[parameters: (None,)]

我尝试禁用DataTypeId 上的NOT NULL 约束,只是为了看看当查询成功时会发生什么。在这种情况下,生成的 SQL 如下:

INSERT INTO [DataSet] ([DataTypeId]) OUTPUT inserted.[Id] VALUES (?)
(None,)
INSERT INTO [ScalarData] ([DataSetId], [Value]) VALUES (?, ?)
(27, bytearray(b'123'))

上面的值 27 确实是新创建的DataSet 行的Id 值(当然,这会随着每次调用而变化)。无论传递给ScalarData 的DataSetId 的值如何,都会发生这种情况。

我尝试将autoincrement=False 添加到ScalarData 模型定义中的DataSetId = Column(...) 调用,但行为完全没有改变。

在这一点上我非常难过。任何关于如何解决这个问题的见解,甚至是为什么会发生,都会很棒。

【问题讨论】:

    标签: python sql-server orm sqlalchemy


    【解决方案1】:

    我知道这有点晚了,但为什么 ScalarData 继承自 DataSet?如果那是直接来自 sqlacodegen,我认为这是一个错误。

    【讨论】:

      猜你喜欢
      • 2020-05-19
      • 1970-01-01
      • 1970-01-01
      • 2019-12-25
      • 1970-01-01
      • 2014-11-21
      • 2017-06-20
      • 1970-01-01
      • 2019-05-22
      相关资源
      最近更新 更多