Seite 1 von 1

Problem mit Freigabe der Firebird Embedded DB (QSqlDatabase)

Verfasst: 26. August 2008 08:50
von Philipp Endres
Hallo,

vorerst mal: ich bin ziemlich jungfräulich auf dem Gebiet Qt. Ich hoffe meine Frage(n) sind nicht allzu doof.

Ich habe ein Problem bei der Verwendung der Firebird Datenbank (die embedded-Version). Diese Datenbank lockt die gesamte Datenbankdatei exklusiv, sobald ein Prozess bzw. Thread darauf zugreift. Multi-User-Konzept und die Möglichkeit, mit mehreren Prozessen gleichzeitig auf die DB zuzugreifen, existieren bei der eingebetteten Variante nicht.

Also dachte ich mir: Damit andere Prozesse ebenfalls auf die DB zugreifen können, während meine Hauptapplikation läuft, stelle ich einfach immer nur kurz Verbindung zur DB her, um die benötigten Tabellen in Models zu laden, und schließe die Verbindung gleich wieder. So dürften andere Applikationen eigentlich NICHT melden, dass "sie nicht auf die DB zugreifen können, weil sie gerade von einem anderen Prozess verwendet wird". Dazu hab ich mir zwei Methoden geschrieben. Eine zum Öffnen der DB-Verbindung:

Code: Alles auswählen

void DbVerbindung::createDBConnection()
{
	if ( sqlDatabase == 0 )
	{
		sqlDatabase = new QSqlDatabase( QSqlDatabase::addDatabase("QIBASE") );
		sqlDatabase->setDatabaseName("C:\\DBS\\DATENTYPENTEST.FDB");
		sqlDatabase->open();
	}
}

und eine zum Schließen der Verbindung:

Code: Alles auswählen

void DbVerbindung::eraseDBConnection()
{
	if ( sqlDatabase != 0 )
	{
		sqlDatabase->removeDatabase(sqlDatabase->connectionName());
		sqlDatabase->close();
		delete(sqlDatabase);
		sqlDatabase = 0;
	}
}

Die Methoden an sich funktionieren schon. Aber irgendwie wird die Datenbank trotzdem nicht freigegeben, so lange noch QSqlTabelModel-Instanzen existieren, die über die Datenbankverbindung gefüllt wurden. Zumindest melden andere Prozesse, dass die DB noch von meiner Hauptapplikation verwendet wird. Nur wenn ich die Models auch noch lösche mit delete() gibt meine Methode eraseDBConnection() die DB wirklich so frei, dass andere Prozesse darauf können. Aber die Models zu löschen ist ja eigentlich überhaupt nicht mein Wunsch. Im Gegenteil! Die sollen ja im Hauptspeicher erhalten bleiben, so lange bis ich die Änderungen über eine neue Verbindung wieder in die DB zurückschreibe.

Könnt ihr mir sagen, was ich da machen kann? (Btw: die Verwendung der Firebird Embedded Datenbank steht außer Frage. Das ist so fix im Rahmen meiner Diplomarbeit).

Vielen Dank für jegliche Form der Hilfe.

Viele Grüße, Philipp

Verfasst: 26. August 2008 11:37
von CaptnChaos
QSqlTableModel hat ein eigenes Datenbank Member.
Die musst du schliessen.
Doku lesen hilft :wink:

Verfasst: 26. August 2008 13:20
von Philipp Endres
Hi KernelPanic,

danke erstmal für die schnelle Antwort, aber leider hilft das nicht. Das Problem, dass der Prozess die Datenbank für sich auch nach dem close() beansprucht, besteht weiterhin.

Ich habe es gerade eben mit diesen beiden Zeilen probiert:

Code: Alles auswählen

dtModel->database().removeDatabase(dtModel->database().connectionName());
dtModel->database().close();
dtModel ist momentan mein einziges QSqlTableModel in der Applikation. Zudem hatte ich eigentlich erwartet, dass ich über die database()-Methode nur den Pointer auf die QSqlDatabase bekomme, auf die ich auch in meiner eraseDBConnection()-Methode referenziere.

Mein Visual Studio bringt jedes Mal beim Ausführen der Methode removeDatabase() (egal, ob in meiner eraseDBConnection-Methode oder beim Model) die Meldung:

QSqlDatabasePrivate::removeDatabase: connection 'qt_sql_default_connection' is still in use, all queries will cease to work.

Die wird in der invalidateDb()-Methode erzeugt, die von der removeDatabase()-Methode aufgerufen wird. Allerdings bin ich etwas überfragt, was darin im Detail abläuft.

Code: Alles auswählen

void QSqlDatabasePrivate::invalidateDb(const QSqlDatabase &db, const QString &name)
{
    if (db.d->ref != 1) {
        qWarning("QSqlDatabasePrivate::removeDatabase: connection '%s' is still in use, "
                 "all queries will cease to work.", name.toLocal8Bit().constData());
        db.d->disable();
        db.d->connName = QString();
    }
}

Verfasst: 26. August 2008 13:47
von CaptnChaos
Dann versuch mal

void QSqlQueryModel::clear () [virtual]
Clears the model and releases any acquired resource.

steht auch in der doku.

Verfasst: 26. August 2008 14:15
von RHBaum
Fragen:

lohnt es sich bei deinem fall ueberhaupt, alles ueber die SQL Modells laufen zu lassen ?
Wie kompliziert/umfangreich waere es, eine eigene datenstruktur im speicher zu halten, und nur beim Laden einmalig auf die DB zuzugreifen ?

Schreiben deine nutzer auch in die DB ? wenn ja, wie willst da das kollisionsmanagment betreiben ?

vielleicht waer eine private temporaere DB fuer jeden client, die beim starten einfach aktualisiert wird, ne abwaegbare loesung ....

Ciao ...

Verfasst: 26. August 2008 14:39
von Philipp Endres
Die clear()-Methode erlaubt mir zwar die Datenbank so wieder zu schließen, dass ich sie mit anderen Prozessen benutzen kann, jedoch gehen dadurch ja auch meine Daten im Model verloren, was ich ja gerade vermeiden will. Die Daten sollen ins Model geladen werden während DB-Verbindung da ist (das geht ja ganz schnell) und danach sollen sie vom Benutzer über die Oberfläche geändert werden können. Und die Änderungen sollen so lange im am Model im Hauptspeicher erfolgen, bis der Benutzer eine Speichern-Schaltfläche oder ähnliches klickt. Dann soll wieder DB-Verbindung aufgebaut werden und die (evtl modifizierten) Daten sollen wieder zurück in die DB geschrieben werden.


zu den anderen Fragen:
lohnt es sich bei deinem fall ueberhaupt, alles ueber die SQL Modells laufen zu lassen ?
Insgesamt wird die Applikation/der Editor ziemlich groß mit einem ziemlich großen Baum links und über das Anklicken einer Zeile im Baum wird rechts daneben in einer Benutzereingabemaske dem Benutzer die Möglicheit gegeben zum angeklickten Thema die Daten zu verwalten. So lassen sie sämtliche Relationen der DB bzw. die Daten verändern. Die Anzahl Relationen, Daten und Benutzermasken ist ziemlich groß. Viele Tabellen und Listen etc. Da glaube ich nicht um die SQLModels drum herum zu kommen, zumal deren Verwendung in unserer Abteilung Standard ist. Die Daten anders im Speicher zu halten (z.B. über Container und Template-Klassen) würde zwar wahrscheinlich gehen, aber dann wäre ja die komplette Model-View-Controller-Funktionalität ungenutzt, was ebenfalls unerwünscht ist.

Die Applikation wird immer nur von einem Benutzer verwendet. Drum auch die eingebettete, lokale DB. Wenn da nur einer dran arbeitet, sollte es eigentlich keine Kollisionen geben. Allerdings ruft meine Hauptapplikation diverse externe exe-Dateien auf bei bestimmten Funktionalitäten, die der Benutzer aus der Hauptapplikation heraus aufrufen kann. (dazu gehört beispielsweise ein Modul für den Konsistenzcheck; insgesamt sind es ca. 7 weitere Programme/Prozesse, die der Benutzer aufrufen kann, und die dann auch die Datenbank lesen, teils auch beschreiben müssen).


Wenn alle anderen Module nur die DB lesen würden, wäre es eine Lösung für die Prozesse einfach die Datenbank(-Datei) zu kopieren und sie auf der Kopie arbeiten zu lassen. Aber einige Module schreiben wie gesagt auch und dann hab ich ein Konsistenzproblem.

Verfasst: 26. August 2008 16:41
von CaptnChaos
Dann ist das Qt-Sql-Model das falsche für dich. Das ruft nämlich kontinuirlich die Datenbank ab. Wenn du die Daten einmal lesen und anzeigen willst, musst du dir selbst was basteln.

Verfasst: 3. September 2008 11:11
von Philipp Endres
Dann ist das Qt-Sql-Model das falsche für dich. Das ruft nämlich kontinuirlich die Datenbank ab. Wenn du die Daten einmal lesen und anzeigen willst, musst du dir selbst was basteln.
Ja, das hat Trolltech mittlerweile auch noch mal bestätigt.

Hab mir jetzt ne abgeleitete Klasse "OfflineModel" von QSqlTableModel geschrieben, die die Daten intern in ne verschachtelte QList speichert und sich auch nur dieser bedient. Hauptsächlich hab ich existierende Methoden überschrieben. Neu dazugekommen sind nur zwei Methoden für den Im- und Export:

Code: Alles auswählen

bool importDataFromSqlTableModel(QSqlTableModel* sourceModel);
bool exportDataToSqlTableModel(QSqlTableModel *sourceModel);
Das funktioniert soweit ganz gut. Habe aber noch ein ProxyModel hinter meine OfflineModel-Klasse(n) gepackt, damit nicht beim importieren einzelner Daten jedes Mal die Signale durch die Gegend fliegen.

Wer den ausführlichen Code mal sehen will oder möchte dass ich ihn poste, der soll sich noch mal bei mir melden oder das hier posten.

@KernelPanic und RHBaum: Danke für Eure Hilfe.

Greetz, Philipp