sqlalchemy flush () et obtenir un identifiant inséré?

114

Je veux faire quelque chose comme ça:

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()

Mais f.idc'est Nonequand je l'essaye. Comment puis-je faire fonctionner cela?

Eloff
la source
2
Pouvez-vous initialiser le moteur SA avec echo=Trueet voir quel SQL est exécuté au moment du vidage? Ce que vous décrivez devrait fonctionner et vous donner l'identifiant, mais il se peut qu'il y ait un autre problème qui fait que f.id soit None.
Pavel Repin

Réponses:

63

Votre exemple de code doit avoir fonctionné tel quel. SQLAlchemy doit fournir une valeur pour f.id, en supposant qu'il s'agit d'une colonne de clé primaire à génération automatique. Les attributs de clé primaire sont remplis immédiatement dans le flush()processus au fur et à mesure qu'ils sont générés, et aucun appel à commit()ne doit être requis. La réponse ici réside donc dans un ou plusieurs des éléments suivants:

  1. Les détails de votre cartographie
  2. S'il y a des bizarreries étranges du backend utilisé (par exemple, SQLite ne génère pas de valeurs entières pour une clé primaire composite)
  3. Ce que dit le SQL émis lorsque vous activez l'écho
zzzeek
la source
1
Vous avez raison, une vérification rapide dans le shell montre qu'il remplit le champ de clé primaire avec une valeur. Je vais devoir rechercher pourquoi cela ne fonctionnait pas dans la pratique.
Eloff
3
"votre exemple de code devrait fournir une valeur" ressemble à ce que vous dites "vous devriez avoir donné une valeur pour id en premier lieu", plutôt que "ce code que vous avez devrait avoir fonctionné tel quel". Il m'est apparu clairement que vous parliez de ce dernier plutôt que du premier seulement après la troisième lecture. Pourriez-vous clarifier?
stochastique du
126

Je viens de rencontrer le même problème, et après les tests, j'ai trouvé qu'aucune de ces réponses n'est suffisante.

Actuellement, ou à partir de sqlalchemy .6+, il existe une solution très simple (je ne sais pas si cela existe dans la version précédente, même si j'imagine que c'est le cas):

session.refresh ()

Donc, votre code ressemblerait à ceci:

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.

Voilà comment procéder.

dpb
la source
8
Cela se rapproche d'une réponse qui pourrait fonctionner pour moi, mais je reçois l'erreur suivante: InvalidRequestError: Impossible d'actualiser l'instance '<....>'. Il apparaît après le vidage que l'instance n'existe tout simplement plus. Appréciez toute perspicacité.
PlaidFan
1
Vous venez de sauver mon cul. Je ne pense pas que j'utiliserai à nouveau l'ORM venant de Django. La commande flush () ne fonctionne PAS comme indiqué à mon humble avis.
Marc
2
Update, j'ai dû utiliser sessionmaker(autoflush=True), ce combo w / refresh () m'a fourni l'ID de ligne. #grrr
Marc
5
Si vous ne comprenez pas la différence entre flush()et commit(), voici une bonne explication: stackoverflow.com/a/4202016/1252290
Epoc
2
@PlaidFan Au lieu d' flush()utiliser commit()et juste après - actualisez-le avec session.refresh(f), fonctionne pour moi, et j'utilise la version SQLAlchemy0.6.7
Ricky Levi
20

Merci pour tout le monde. J'ai résolu mon problème en modifiant le mappage des colonnes. Pour moi, autoincrement=Truec'est nécessaire.

origine:

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

après modification:

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

puis

session.flush()  
print(f.id)

c'est ok!

poloxue
la source
6

contrairement à la réponse donnée par dpb, un rafraîchissement n'est pas nécessaire. une fois que vous videz, vous pouvez accéder au champ id, sqlalchemy actualise automatiquement l'identifiant qui est automatiquement généré au backend

J'ai rencontré ce problème et j'ai compris la raison exacte après quelques recherches, mon modèle a été créé avec id comme integerfield et dans ma forme l'id était représenté avec hiddenfield (puisque je ne voulais pas montrer l'id dans mon formulaire). Le champ masqué est par défaut représenté sous forme de texte. une fois que j'ai changé le formulaire en integerfield avec widget = hiddenInput ()), le problème a été résolu.

Ryan
la source
1
Comme indiqué, seul refresh () a fonctionné pour moi. En effectuant une migration de données, j'avais besoin de l'ID de ligne dans une boucle pour remplir un FK. J'ai essayé toutes les combinaisons de commit, flush, piratage de session et refresh () était la seule chose qui fonctionnait. Mes données sont super propres et je trouve juste que SQLA n'est pas vraiment bon (ayant une exp considérable dans au moins 5 ORMS majeurs). Juste après plus de 5 heures pour essayer d'obtenir l'ID de ligne pour un add () -> commit () / flush ().
Marc
1

Une fois, j'ai eu un problème avec l'attribution 0à id avant d'appeler la session.addméthode. L'ID a été correctement attribué par la base de données mais l'ID correct n'a pas été récupéré de la session après session.flush().

babky
la source
-7

Vous devriez essayer d'utiliser à la session.save_or_update(f)place de session.add(f).

Mohit Ranka
la source
1
save_or_updateest obsolète depuis environ 0,5. session.add()devrait le faire.
Pavel Repin