sqlalchemy flush () e inserir o id?

114

Eu quero fazer algo assim:

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

Mas f.idé Nonequando eu tento. Como posso fazer isso funcionar?

Eloff
fonte
2
Você pode inicializar o mecanismo SA com echo=Truee ver qual SQL é executado no tempo de liberação? O que você descreve deve funcionar e fornecer o id, mas pode haver algum outro problema que resulta em f.id sendo Nenhum.
Pavel Repin

Respostas:

63

Seu código de amostra deve ter funcionado como está. SQLAlchemy deve fornecer um valor para f.id, supondo que seja uma coluna de chave primária de geração automática. Os atributos de chave primária são preenchidos imediatamente no flush()processo à medida que são gerados e nenhuma chamada para commit()deve ser necessária. Portanto, a resposta aqui está em um ou mais dos seguintes:

  1. Os detalhes do seu mapeamento
  2. Se houver alguma peculiaridade do back-end em uso (como o SQLite não gera valores inteiros para uma chave primária composta)
  3. O que o SQL emitido diz quando você liga o eco
zzzeek
fonte
1
Você está correto, uma verificação rápida no shell mostra que ele preenche o campo da chave primária com um valor. Terei que investigar por que não estava funcionando na prática.
Eloff
3
"seu código de amostra deve fornecer um valor" soa como se você estivesse dizendo "você deveria ter fornecido um valor para id em primeiro lugar", em vez de "aquele código que você deveria ter funcionado como está". Ficou claro para mim que você se referia a este último, e não ao primeiro, somente após a terceira leitura. Você poderia esclarecer?
estocástico de
126

Acabei de encontrar o mesmo problema e, após o teste, descobri que NENHUMA dessas respostas é suficiente.

Atualmente, ou a partir de sqlalchemy .6+, há uma solução muito simples (não sei se isso existe na versão anterior, embora eu imagine que sim):

session.refresh ()

Portanto, seu código seria mais ou menos assim:

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.

É assim que se faz.

dpb
fonte
8
Isso está se aproximando de uma resposta que pode funcionar para mim, mas recebo o seguinte erro: InvalidRequestError: Não foi possível atualizar a instância '<....>'. Após a liberação, parece que a instância simplesmente não existe mais. Aprecie qualquer ideia.
PlaidFan
1
Você acabou de salvar minha bunda. Acho que nunca mais usarei o ORM vindo do Django. O comando flush () NÃO funciona como documentado IMHO.
Marc
2
Update, eu tive que usar sessionmaker(autoflush=True), aquele combo w / refresh () me forneceu o ID da linha. #grrr
Marc
5
Se você não entende a diferença entre flush()e commit(), aqui está uma boa explicação: stackoverflow.com/a/4202016/1252290
Epoc
2
@PlaidFan Em vez de flush()usar commit()e logo depois disso - atualize com session.refresh(f), funciona para mim e eu uso a versão SQLAlchemy0.6.7
Ricky Levi
20

Obrigado a todos. Resolvi meu problema modificando o mapeamento da coluna. Para mim, autoincrement=Trueé obrigatório.

origem:

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

depois de modificado:

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

então

session.flush()  
print(f.id)

está ok!

poloxue
fonte
6

ao contrário da resposta dada por dpb, uma atualização não é necessária. depois de liberar, você pode acessar o campo id, sqlalchemy automaticamente atualiza o id que é gerado automaticamente no backend

Eu encontrei esse problema e descobri o motivo exato após alguma investigação, meu modelo foi criado com id como integerfield e no meu formulário o id foi representado com hiddenfield (já que eu não queria mostrar o id no meu formulário). O campo oculto é, por padrão, representado como um texto. assim que mudei o formulário para integerfield com widget = hiddenInput ()) o problema foi resolvido.

Ryan
fonte
1
Conforme declarado, apenas refresh () funcionou para mim. Ao fazer uma migração de dados, eu precisava do ID da linha em um loop para preencher um FK. Eu tentei todas as combinações de commit, flush, sessão de hack e refresh () foi a única coisa que funcionou. Meus dados estão super limpos e estou apenas descobrindo que SQLA não é realmente tão bom (tendo uma experiência considerável em pelo menos 5 ORMS principais). Apenas envolvendo 5+ horas tentando obter o id da linha para add () -> commit () / flush ().
Marc
1

Certa vez, tive um problema com a atribuição 0de id antes de chamar o session.addmétodo. O id foi atribuído corretamente pelo banco de dados, mas o id correto não foi recuperado da sessão posterior session.flush().

babky
fonte
-7

Você deve tentar usar em session.save_or_update(f)vez de session.add(f).

Mohit Ranka
fonte
1
save_or_updatefoi descontinuado desde 0,5 ou mais. session.add()deve fazer isso.
Pavel Repin