qt несколько QSqlTableModels, отредактированных вместе в одной транзакции

У меня есть окно в приложении Qt с использованием базы данных PostgreSQL 9.3. Окно представляет собой форму, используемую для отображения, редактирования и вставки новых данных. Это выглядит так:

У меня есть данные из 3 таблиц sql в этом представлении. таблицы связаны с внешними ключами:

  • подрядчики (основная таблица) - сопоставлены с разделом "персональные данные"
  • контакты (имеет внешний ключ к контрагентам.ID)
  • адреса (имеет внешний ключ к контрагентам.ID)

Итак, в классе моего окна у меня есть 3 основные модели (+ 2 модели прокси для транспонирования таблиц в разделах "персональные данные" и "адресные данные"). я использую QSqlTableModel для этих сессий, и QSqlRelationalTableModel для раздела contactData. при открытии этого окна "нормально" (для просмотра какого-либо подрядчика) я просто передаю идентификатор подрядчика конструктору и сохраняю его в соответствующей переменной. Также я называю QSqlTableModel::​setFilter(const QString & filter) метод для каждой из моделей, и установить правильную фильтрацию. При открытии этого окна в режиме "добавления нового" я ​​просто передаю значение "-1" или "0" в переменную ID, чтобы данные не загружались в модель. Все 3 модели имеют QSqlTableModel::OnManualSubmit editStrategy. При сохранении данных (вызванных нажатием соответствующей кнопки) я запускаю транзакцию. А потом я представляю модели по одному. personalData Модель отправляется первой, так как мне нужно получить ее PK после вставки (для установки в полях FK в других моделях). Когда не удается отправить модель, я показываю messageBox с содержимым QSqlError, откатываю транзакцию и возвращаюсь из метода. Когда у меня появляется ошибка на первой обрабатываемой модели - нет проблем, так как ничего не было вставлено. Но когда первая модель сохраняется, а вторая или третья выходит из строя - возникает небольшая проблема. Поэтому я откатываю транзакцию, как и раньше, и возвращаюсь из функции. Но после исправления данных и повторной отправки - первая модель не пытается отправить - поскольку она не знает, что произошел откат, и данные необходимо вставить снова. Что было бы хорошим способом заметить такую ​​модель, которую нужно отправить еще раз? На данный момент я закончил с чем-то вроде этого:

void kontrahenciSubWin::on_btnContractorAdd_clicked() {
    //QStringList errorList; // when error occurs in one model - whole transacion gets broken, so no need for a list
    QString error;
    QSqlDatabase db = QSqlDatabase::database();

    //backup the data - in case something fails and we have to rollback the transaction
    QSqlRecord personalDataModelrec = personalDataModel->record(0); // always one row. will get erased by SubmitAll, as no filter is set, because I don't have its ID.

    QList<QSqlRecord> contactDataModelRecList;
    for (int i = 0 ; i< contactDataModel->rowCount(); i++) {
        contactDataModelRecList.append( contactDataModel->record(i) );
    }

    QList<QSqlRecord> addressDataModelRecList;
    for (int i = 0 ; i< addressDataModel->rowCount(); i++) {
        addressDataModelRecList.append( addressDataModel->record(i) );
    }

    db.transaction();
    if ( personalDataModel->isDirty() && error.isEmpty() ) {
        if (!personalDataModel->submitAll()) //submitAll calls select() on the model, which destroys the data as the filter is invalid ("where ID = -1")
            //errorList.append( personalDataModel->lastError().databaseText() );
            error = personalDataModel->lastError().databaseText(); 
        else {
            kontrahentid = personalDataModel->query().lastInsertId().toInt(); //only here can I fetch ID
            setFilter(ALL); //and pass it to the models
        }
    }

    if ( contactDataModel->isDirty() && error.isEmpty() ) 
        if (!contactDataModel->submitAll()) //slot on_contactDataModel_beforeInsert() sets FK field
            //errorList.append( contactDataModel->lastError().databaseText() );
            error = contactDataModel->lastError().databaseText();

    if ( addressDataModel->isDirty() && error.isEmpty() )
        if (!addressDataModel->submitAll()) //slot on_addressDataModel_beforeInsert() sets FK field
            //errorList.append( addressDataModel->lastError().databaseText() );
            error = addressDataModel->lastError().databaseText();

    //if (!errorList.isEmpty()) {
    //  QMessageBox::critical(this, tr("Data was not saved!"), tr("The following errors occured:") + " \n" + errorList.join("\n"));
    if (!error.isEmpty()) {
        QMessageBox::critical(this, tr("Data was not saved!"), tr("The following errors occured:") + " \n" + error);

        db.rollback();
        personalDataModel->clear();
        contactDataModel->clear();
        addressDataModel->clear();
        initModel(ALL); //re-init models: set table and so on.

        //re-add data to the models - backup comes handy
        personalDataModel->insertRecord(-1, personalDataModelrec);

        for (QList<QSqlRecord>::iterator it = contactDataModelRecList.begin(); it != contactDataModelRecList.end(); it++) {
            contactDataModel->insertRecord(-1, *it);
        }

        for (QList<QSqlRecord>::iterator it = addressDataModelRecList.begin(); it != addressDataModelRecList.end(); it++) {
            addressDataModel->insertRecord(-1, *it);
        }

        return;
    }
    db.commit();
    isInEditMode = false;
    handleGUIOnEditModeChange();
}

У кого-нибудь есть идея получше? Я сомневаюсь, что можно пропустить резервное копирование записей, прежде чем пытаться их вставить. Но, может быть, есть лучший способ "заново" добавить их в модель? Я пытался использовать "setRecord", а также "remoweRows" & "insertRecord" комбо, но не повезло. Сброс всей модели кажется самым простым (мне нужно только повторно запустить ее, так как при очистке она теряет таблицу, фильтр, сортировку и все остальное)

1 ответ

Я предлагаю вам использовать функцию, написанную на языке PLPGSQL. У него есть одна транзакция между BEGIN и END. Если в какой-то момент кода это пойдет не так, то все данные будут откатаны без ошибок.

То, что вы делаете сейчас, не является хорошим дизайном, потому что вы управляете определенным функционалом (откатом) внешней системы в отношении отката (это происходит в базе данных). Внешняя система не предназначена для этого, в то время как база данных, напротив, создана и предназначена для работы с откатами и транзакциями. Это очень хорошо в этом. Восстановление и переосмысление этой функциональности, которая является довольно сложной, вне базы данных, создает много проблем. Вы никогда не получите такую ​​же безупречную обработку отката, как при использовании функций в базе данных.

Пусть каждая система делает то, что умеет лучше всего.

Я уже встречался с вашей проблемой и придерживался той же мысли, чтобы решить эту проблему с помощью Hibernate в моем случае. Пока я не отступил от своих усилий и не переоценил ситуацию. Над механизмом отката базы данных работают три команды: 1. мужчины и женщины, которые пишут исходный код самой базы данных, 2. мужчины и женщины, которые пишут код Hibernate, и 3. я. Первая команда посвящена созданию хорошего механизма отката. Если они терпят неудачу, у них есть плохой продукт. Им это удалось. Вторая команда посвящена созданию хорошего механизма отката. Их продукт не выходит из строя, когда он не работает в очень сложных ситуациях. Последняя команда, я, не посвящена этой проблеме. Кто я такой, чтобы писать лучшее решение, чем люди из команды 2 или команды 1, основанные на работе команды 2, которые не смогли получить его до уровня команды 1? Именно тогда я решил вместо этого использовать функции базы данных.

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