【问题标题】:SQLAlchemy session is not adding some of my objectsSQLAlchemy 会话没有添加我的一些对象
【发布时间】:2017-12-17 06:42:04
【问题描述】:

我使用 SQLAlchemy 中的声明式样式创建了一些映射对象。我有一个名为 ThermafuserReading 的映射,它有一个由 Time_stamp 列组成的组合主键,它是 DateTime 和 ThermafuserId 列,它是一个整数,并且还充当另一个名为 Thermafuser 的表的外键。这是类的定义

class ThermafuserReading(Base):
"""Class to map to the Thermafuser Readings table in the HVAC DB"""

__tablename__ = 'Thermafuser_Reading'

_timestamp = Column('Time_stamp', DateTime, primary_key = True)
_thermafuserId = Column('ThermafuserId', Integer, ForeignKey("Thermafuser.ThermafuserId"), primary_key = True)
_roomOccupied = Column('RoomOccupied', Boolean)
_zoneTemperature = Column('ZoneTemperature', Float)
_supplyAir = Column('SupplyAir', Float, nullable=True)
_airflowFeedback = Column('AirflowFeedback', Float, nullable=True)
_CO2Input = Column('CO2Input', Float, nullable=True)
_maxAirflow = Column('MaxAirflow', Float, nullable=True)
_minAirflow = Column('MinAirflow', Float, nullable=True)
_unoccupiedHeatingSetpoint = Column('UnoccupiedHeatingSetpoint', Float, nullable=True)
_unoccupiedCoolingSetpoint = Column('UnoccupiedCoolingSetpoint', Float, nullable=True)
_occupiedCoolingSetpoint = Column('OccupiedCoolingSetpoint', Float, nullable=True)
_occupiedHeatingSetpoint = Column('OccupiedHeatingSetpoint', Float, nullable=True)
_terminalLoad = Column('TerminalLoad', Float, nullable=True)

#Relationship between Thermafuser Reading and Thermafuser
_thermafuser = relationship("Thermafuser", back_populates = "_thermafuserReadings",  cascade = "all, delete-orphan", single_parent = True)

我正在通过以下方式创建会话

sqlengine = sqlalchemy.create_engine("mysql+mysqldb://user:password@localhost:3306/HVAC")
    Session = sessionmaker(bind=sqlengine)
    session = Session()

在我的代码中,我正在创建一个名为“Thermafuser Readings 读数”的列表,并通过 session.add_all(readings) 将此类列表添加到会话中

这是从列表读数中打印出来的一些示例元素:

<ThermafuserReading(thermafuserId = '21', timestamp = '2016-12-31 23:30:00')>
<ThermafuserReading(thermafuserId = '21', timestamp = '2016-12-31 23:35:00')>
<ThermafuserReading(thermafuserId = '21', timestamp = '2016-12-31 23:40:00')>
<ThermafuserReading(thermafuserId = '21', timestamp = '2016-12-31 23:45:00')>
<ThermafuserReading(thermafuserId = '21', timestamp = '2016-12-31 23:50:00')>
<ThermafuserReading(thermafuserId = '21', timestamp = '2016-12-31 23:55:00')>
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-01 00:00:00')>

问题是会话只保留此列表中的最后一项,即使我做了 session.add_all(readings) 例如这是会话内部的内容:

<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-01 00:00:00')>

我知道会话会跟踪具有相同主键的对象,因此在会话中仅插入此类对象的一个​​实例,但在这种情况下,主键(thermafuserId、时间戳)在每个实例中都不同。我不知道为什么会话只添加了我列表的最后一个元素而忽略了其他元素。

有什么想法吗?

编辑:

我一直在做一些测试,并找出了为什么只将列表的最后一个元素添加到会话中的原因。问题在于我的列表 readings 中每个对象的 identity_key。这是我用于测试的代码:

for reading in readings:
    print(reading, mapper.identity_key_from_instance(reading))

这是一些结果

<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-14 23:15:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-14 23:20:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-14 23:25:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-14 23:30:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-14 23:35:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-14 23:40:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-14 23:45:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-14 23:50:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-14 23:55:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))
<ThermafuserReading(thermafuserId = '21', timestamp = '2017-01-15 00:00:00')> (<class 'hvacDBMapping.ThermafuserReading'>, (datetime.datetime(2017, 1, 15, 0, 0), 21))

如您所见,函数 sqlalchemy.orm.util.identity_key_from_instance() 没有为我的日期时间对象正确创建身份密钥。

有人可以帮我解释一下原因吗?

编辑

这是说明问题的简化代码。此代码中没有与数据库的连接。首次出现此问题的代码涉及更多,发布它只会造成混乱,但此代码会重现错误。

Session = sessionmaker()
session = Session() 

mapper = inspect(ThermafuserReading)

#Open the csv file
csvFilePath = "/Users/davidlaredorazo/Box Sync/Data/Zone4/1C1A/1C1A 2016-12-31.csv"
with open(csvFilePath, 'r') as csvfile:

    reader = csv.reader(csvfile)
    componentId = 1
    count = 0

    reading = ThermafuserReading(None, componentId)

    for row in reader:

        if count == 0:
            count += 1
            continue

        #print(row)
        timestamp = parse(row[0], None, ignoretz = True)

        reading.timestamp = timestamp
        new_object = copy.copy(reading)
        new_object.timestamp = timestamp

        print(new_object, mapper.identity_key_from_instance(new_object))
        session.add(new_object)

print("new elements")
for new in session.new:
    print(new, mapper.identity_key_from_instance(new_object))

【问题讨论】:

  • 您需要在创建这些实例的位置发布代码。 (您确定您不只是创建一个实例并将同一个实例添加到列表中吗?)以及您如何打印这些实例。 (据我们所知,您正在打印timestamp = &lt;completely different field than self._timestamp&gt;。)
  • 感谢您的评论。我刚刚发布了一个重现错误的代码 sn-p。我确信我不会只创建一个实例,因为我正在使用 copy.copy 复制实例。
  • 好吧,那是你的问题。您正在使用copy 弄乱 SQLAlchemy 内部结构。不要那样做。如果你真的想要,你需要将_sa_instance_state排除在被复制之外。
  • 那么在会话中插入对象的正确方法是什么。问题是我创建了我想要插入的对象的单个实例,然后我修改它的属性并复制该实例并将其插入会话中。那么正确的方法是什么?感谢您的评论
  • 不要使用copy。手动创建ThermafuserReading 实例并自己复制属性。

标签: python mysql session sqlalchemy


【解决方案1】:

正如 cmets 中提到的 univerio。我做错了什么是使用 copy.copy 来复制我的映射对象的实例,这与 _sa_instance_state 相混淆。解决方案是为我的实例创建一个“临时”复制功能。这是我使用的复制功能,这个确实解决了问题。

def copy_sqla_object(obj, omit_fk=True):
    """Given an SQLAlchemy object, creates a new object (FOR WHICH THE OBJECT
    MUST SUPPORT CREATION USING __init__() WITH NO PARAMETERS), and copies
    across all attributes, omitting PKs, FKs (by default), and relationship
    attributes."""
    cls = type(obj)
    mapper = class_mapper(cls)
    newobj = cls()  # not: cls.__new__(cls)
    pk_keys = set([c.key for c in mapper.primary_key])
    rel_keys = set([c.key for c in mapper.relationships])
    prohibited = pk_keys | rel_keys
    if omit_fk:
        fk_keys = set([c.key for c in mapper.columns if c.foreign_keys])
        prohibited = prohibited | fk_keys
    for k in [p.key for p in mapper.iterate_properties if p.key not in prohibited]:
        try:
            value = getattr(obj, k)
            setattr(newobj, k, value)
        except AttributeError:
            pass
    return newobj

您可以在

中看到有关此问题的更详细讨论

https://groups.google.com/forum/#!topic/sqlalchemy/HVSxndh23m0

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-09
    • 1970-01-01
    • 1970-01-01
    • 2013-08-26
    • 2010-09-29
    • 1970-01-01
    • 2012-04-26
    相关资源
    最近更新 更多