sqlalchemy flush() и получить вставленный идентификатор?

Я хочу сделать что-то вроде этого:

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

Но f.id - это None, когда я пытаюсь это сделать. Как я могу заставить это работать?

Дан

10 ответов

Решение

Ваш пример кода должен работать как есть. Sqlalchemy должен обеспечивать значение для f.id, предполагая его автоматически генерирующийся столбец первичного ключа. атрибуты первичного ключа заполняются непосредственно в процессе flush(), поскольку они генерируются, и не требуется никакого вызова commit(). Таким образом, ответ здесь заключается в деталях вашего отображения, если есть какие-то странные особенности используемого бэкэнда (например, SQLite не генерирует целочисленные значения для составного первичного ключа) и / или что говорит испущенный SQL, когда вы включить эхо.

Я только что столкнулся с той же проблемой, и после тестирования я обнаружил, что ни одного из этих ответов не достаточно.

В настоящее время или в sqlalchemy .6+ существует очень простое решение (я не знаю, существует ли оно в предыдущей версии, хотя я предполагаю, что оно существует):

session.refresh ()

Итак, ваш код будет выглядеть примерно так:

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.

Вот как это сделать.

Спасибо всем. Я решил свою проблему, изменив сопоставление столбцов. Для меня, autoincrement=True необходимо.

происхождение: id = Column('ID', Integer, primary_key=True, nullable=False)

после изменения: id = Column('ID', Integer, primary_key=True, autoincrement=True, nullable=True)

затем session.flush()
print(f.id)

в порядке!

Последние пару часов/дней/независимо от того, я пытался заставить приведенные выше предложения работать. Изначально я написал все свои функции вставки так:

      _add = User(id, user_name, email, ...)

Где все элементы в круглых скобках являются переменными для None, "user a", " [email protected] ",...

Это моя таблица пользователей:

      class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True, autoincrement=True)
    user_name = Column(String(50), unique=True, nullable=False)
    email = Column(String(100), unique=True, nullable=False)

SQLAlchemy правильно обрабатывает запрос _add, так как вставляет запись с автоматически увеличивающимся идентификатором. Кроме того, как и должно быть, для столбца id не задано значение по умолчанию.

Я пробовал все вышеперечисленные варианты по-разному (с/без фиксации, с/без сброса, с/без обновления, один перед другим, тайм-ауты между операторами, вы называете это). Я даже несколько раз изменил все взаимодействие приложения/базы данных. Но во всех случаях «_add.id» либо возвращает 0, либо что-то вроде «Экземпляр» был удален, или его строка иначе отсутствует».

Только что я подумал: « Может быть, мне следует написать мой запрос _add немного по-другому, определив также имена столбцов для указанной таблицы », например:

      _add = User(id=None, user_name=user_name, email=email, etc)

Чтобы подчеркнуть, обратите внимание: id= , user_name= , email= в запросе _add. Теперь, со следующими операторами в этом порядке, SQLAlchemy действительно возвращает вставленный идентификатор!

      session.add(_add)
print(_add.id)    <-- returns None

session.flush()   <-- does **not** insert record into database, but does increment id,
                      waiting to be committed. Flush may be omitted, because
                      session.commit() unconditionally issues session.flush()*
print(_add.id)    <-- returns incremented id

session.commit()  <-- commit is needed to actually insert record into database
print(_add.id)    <-- returns incremented id

Хотя ответ был предоставлен, мне не было ясно, что отсутствующие имена столбцов в запросе _add и, следовательно, моя лень были причиной моих проблем. Я надеюсь, что это может помочь кому-то избежать того же устранения неполадок...

Документы по SQLAlchemy

Основное решение упоминалось в других гораздо более старых ответах, но в нем используется более новый асинхронный API.

с sqlalchemy==1.4 (стиль 2.0), похоже, работает следующее:

      from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine

engine = create_async_engine(
        "postgresql+asyncpg://user:pass@localhost/db",
        echo=False,
    )


# expire_on_commit=False will prevent attributes from being expired
# after commit.
async_session = sessionmaker(
    engine, expire_on_commit=False, class_=AsyncSession,
)
# default kwarg autoflush=True


async with async_session() as session: 
    async with session.begin(): 
        f = Foo(bar='x')
        session.add(f)
        print(f.id)
        # None

        await session.flush()
        print(f.id)
        # not None
    # commits transaction, closes session

В отличие от ответа, данного dpb, обновление не требуется. Как только вы очистите, вы можете получить доступ к полю id, sqlalchemy автоматически обновляет идентификатор, который автоматически генерируется на бэкэнде

Я столкнулся с этой проблемой и выяснил точную причину после некоторого исследования, моя модель была создана с идентификатором в качестве целочисленного поля, и в моей форме идентификатор был представлен скрытым полем (так как я не хотел показывать идентификатор в моей форме). Скрытое поле по умолчанию представлено в виде текста. как только я изменил форму на integerfield с widget=hiddenInput()), проблема была решена.

мой код работает так:

      f = Foo(bar="blabla")
session.add(f)
session.flush()
session.refresh(f, attribute_names=[columns name that you want retrieve]
# so now you can access the id inserted, for example
return f.id # id inserted will be returned

Я столкнулся с той же проблемой отсутствия значений идентификатора после создания объектов при использовании .

К счастьюreturn_defaultsпараметр вbulk_save_objectsдобавляет все идентификаторы к объектам без необходимости вызоваflushилиcommit.

      users: list[User] = [User(user_name=..., email=..., etc) for foo in bar]
session.bulk_save_objects(references, return_defaults=True)
assert users[0].id is not None # User.id is now populated.

См. документацию SQLAlchemy::param return_defaults: когда True, строки, в которых отсутствуют значения, генерирующие значения по умолчанию, а именно целочисленные значения по умолчанию и последовательности первичного ключа, будут вставлены по одной , чтобы значение первичного ключа было доступно.

У меня когда-то была проблема с назначением 0 идентифицировать перед звонком session.add метод. Идентификатор был правильно назначен базой данных, но правильный идентификатор не был получен из сеанса после session.flush(),

Вы должны попробовать использовать session.save_or_update(f) вместо session.add(f),

Другие вопросы по тегам