Как сохранить несколько связанных экземпляров Django ORM за один раз без отдельных обращений к базе данных по сети?

Вот пример:

article1 = Article(title="Global Warming", content="...")
article2 = Article(title="Visiting Mars", content="...")
comment1 = Comment(content="Some comment", article=article1)
user1 = User(username="user1")

some_updated_article.title = "updated title"
article_to_delete.delete()

Я знаю, что в SQLAlchemy вы можете сохранить несколько экземпляров в базе данных за один вызов следующим образом:

db.session.add_all([article1, article2, comment1, user1])
db.session.commit()

Этот подход отправляет все инструкции в базу данных за один раз (пожалуйста, поправьте меня, если я ошибаюсь). db.session.add_all(), за которым следует db.session.commit(), будет работать, и не будет трех отдельных обращений к серверу базы данных.

Я знаю, что в Django я могу использовать bulk_create , bulk_update, для каждой модели:

Article.objects.bulk_create([article1, article2])
Comment.objects.bulk_create([comment1])
User.objects.bulk_create([user1])
Article.objects.bulk_update([some_updated_article], fields=["title"])

Но это отправляет отдельные вызовы на сервер базы данных для каждой модели. Есть ли способ добиться чего-то подобного add_all() от SQLAlchemy, где я могу отправлять все объекты за один раз, независимо от модели?

Я подумывал о том, чтобы использовать transaction.atomic для этого:

with transaction.atomic():
    Article.objects.bulk_create([article1, article2])
    Comment.objects.bulk_create([comment1])
    User.objects.bulk_create([user1])
    Article.objects.bulk_update([some_updated_article], fields=["title"])

Использование transaction.atomic() гарантирует, что все операции будут выполнены успешно или завершатся неудачей как одна атомарная транзакция. Однако в моем случае использования я не хочу, чтобы выполнялся полный откат. Например, если при создании комментариев возникает ошибка, я все равно хочу успешно сохранить статьи и пользователей. Я знаю, что Django предоставляет точек сохранения с помощью transaction.savepoint() и transaction.savepoint_rollback(), но это кажется немного громоздким.

Есть ли лучший способ добиться этого в Django? В идеале я хотел бы:

  1. Избегайте отката всей транзакции в случае сбоя одной операции.

  2. Получать уведомления о том, какой шаг завершился неудачей, сохраняя при этом успешные данные.

  3. Сделайте все за один вызов базы данных (так что, если, например, мой внутренний сервер находится в Австралии, а моя база данных - в Северной Америке, не будет 4 отдельных сетевых запросов)

Каков наилучший подход для обработки таких сценариев? Возможно, есть какая-то причина, по которой вы не можете сделать это легко, о которой я не знаю, и лучше использовать поведение по умолчанию?

Обычные табличные выражения на самом деле не поддерживаются ORM изначально, так что, возможно, вы столкнулись с ситуацией cursor, в которой требуется выполнить простой старый sql (https://docs.djangoproject.com/en/5.1/topics/db/sql/#executing-custom-sql-directly).

Не уверен, используете ли вы postgres или другую реляционную базу данных, но CTE, вероятно, должны быть одинаковыми между ними https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-MODIFYING.

Возможно, в итоге у вас получится что-то вроде:

with connection.cursor() as cursor:
    cursor.execute(
    """
    WITH insertedArticles AS (
        INSERT INTO articles
            (name, subtitle) 
        VALUES
            ('name_value', 'subtitle_value')
        RETURNING article_id
    )
    INSERT INTO comments
        (article_id, comment_text)
    SELECT
        insertedArticles.article_id, $1
    FROM insertedArticles;

    """

Как всегда при использовании необработанных методов sql, а не ORM, убедитесь в параметризации входных данных.

Вернуться на верх