sqlalchemy flush () y obtener la identificación insertada?

114

Quiero hacer algo como esto:

f = Foo(bar='x')
session.add(f)
session.flush()

# do additional queries using f.id before commit()
print f.id # should be not None

session.commit()

Pero f.ides Nonecuando lo intento. ¿Cómo puedo hacer que esto funcione?

Eloff
fuente
2
¿Puede inicializar el motor SA con echo=Truey ver qué SQL se ejecuta en el tiempo de descarga? Lo que describa debería funcionar y darle la identificación, pero puede haber algún otro problema que haga que f.id sea None.
Pavel Repin

Respuestas:

63

Su código de muestra debería haber funcionado como está. SQLAlchemy debería proporcionar un valor para f.id, asumiendo que es una columna de clave primaria que se genera automáticamente. Los atributos de clave primaria se completan inmediatamente dentro del flush()proceso a medida que se generan, y no se commit()debe requerir ninguna llamada a . Entonces, la respuesta aquí radica en uno o más de los siguientes:

  1. Los detalles de tu mapeo
  2. Si hay alguna peculiaridad extraña del backend en uso (como, por ejemplo, SQLite no genera valores enteros para una clave primaria compuesta)
  3. Lo que dice el SQL emitido cuando enciendes el eco
zzzeek
fuente
1
Estás en lo correcto, una verificación rápida en el shell muestra que llena el campo de clave principal con un valor. Tendré que investigar por qué no funcionó en la práctica.
Eloff
3
"su código de muestra debería proporcionar un valor" suena como si estuviera diciendo "debería haber dado un valor para id en primer lugar", en lugar de "ese código que tiene debería haber funcionado como está". Me quedó claro que se refería a lo último y no a lo primero solo después de la tercera lectura. ¿Podrías aclarar?
estocástico
126

Acabo de encontrarme con el mismo problema y, después de probar, descubrí que NINGUNA de estas respuestas es suficiente.

Actualmente, o a partir de sqlalchemy .6+, hay una solución muy simple (no sé si existe en la versión anterior, aunque imagino que sí):

session.refresh ()

Entonces, su código se vería así:

f = Foo(bar=x)
session.add(f)
session.flush()
# At this point, the object f has been pushed to the DB, 
# and has been automatically assigned a unique primary key id

f.id
# is None

session.refresh(f)
# refresh updates given object in the session with its state in the DB
# (and can also only refresh certain attributes - search for documentation)

f.id
# is the automatically assigned primary key ID given in the database.

Así es como se hace.

dpb
fuente
8
Esto se está acercando a una respuesta que podría funcionar para mí, pero recibo el siguiente error: InvalidRequestError: No se pudo actualizar la instancia '<....>'. Después de la descarga, parece que la instancia simplemente ya no existe. Aprecie cualquier idea.
PlaidFan
1
Me acabas de salvar. No creo que vuelva a usar el ORM procedente de Django. El comando flush () NO funciona como se documenta en mi humilde opinión.
Marc
2
Actualizar, tuve que usar sessionmaker(autoflush=True), ese combo w / refresh () me proporcionó el ID de fila. #grrr
Marc
5
Si no entiende la diferencia entre flush()y commit(), aquí hay una buena explicación: stackoverflow.com/a/4202016/1252290
Epoc
2
@PlaidFan En lugar de flush()usar commit()y justo después de eso, actualícelo con session.refresh(f), funciona para mí y uso la versión SQLAlchemy0.6.7
Ricky Levi
20

Gracias a todos. Resolví mi problema modificando el mapeo de columnas. Para mí, autoincrement=Truees obligatorio.

origen:

id = Column('ID', Integer, primary_key=True, nullable=False)

después de modificado:

id = Column('ID', Integer, primary_key=True, autoincrement=True, nullable=True)

luego

session.flush()  
print(f.id)

esta bien!

poloxue
fuente
6

a diferencia de la respuesta dada por dpb, no es necesaria una actualización. una vez que se descarga, puede acceder al campo de identificación, sqlalchemy actualiza automáticamente la identificación que se genera automáticamente en el backend

Encontré este problema y descubrí la razón exacta después de una investigación, mi modelo se creó con id como integerfield y en mi formulario, el id se representó con hiddenfield (ya que no quería mostrar el id en mi formulario). El campo oculto se representa por defecto como texto. una vez que cambié el formulario a integerfield con widget = hiddenInput ()) el problema se resolvió.

Ryan
fuente
1
Como se indicó, solo refresh () funcionó para mí. Al hacer una migración de datos, necesitaba el ID de fila en un bucle para completar un FK. Probé cada combinación de commit, flush, session hacking y refresh () fue lo único que funcionó. Mis datos están súper limpios y estoy descubriendo que SQLA no es realmente tan bueno (tiene una experiencia considerable en al menos 5 ORMS principales). Simplemente terminando en más de 5 horas tratando de obtener el ID de fila para un add () -> commit () / flush ().
Marc
1

Una vez tuve un problema con haber asignado 0a id antes de llamar al session.addmétodo. La base de datos asignó correctamente la identificación, pero la identificación correcta no se recuperó de la sesión posterior session.flush().

babky
fuente
-7

Deberías intentar usar en session.save_or_update(f)lugar de session.add(f).

Mohit Ranka
fuente
1
save_or_updateha quedado en desuso desde 0.5 aproximadamente. session.add()Deberías hacerlo.
Pavel Repin