Typisiertes SQlite-DataSet und AutoIncrement-Spalte

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 21 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Typisiertes SQlite-DataSet und AutoIncrement-Spalte

    Ich habe eine SQlite-Datenbank erstellt. Darin befindet sich folgende Tabelle:

    SQL-Abfrage

    1. CREATE TABLE IF NOT EXISTS "departments" (
    2. "id" INTEGER,
    3. "department" TEXT NOT NULL UNIQUE,
    4. PRIMARY KEY("id")
    5. ) WITHOUT ROWID;

    Erstelle ich daraus nun ein typisiertes DataSet, so hat die id-Spalte die Eigenschaft AutoIncrement auf False.

    Warum ist das so, also warum wird das nicht übernommen? Oder handelt es sich dabei um einen AutoIncrement, den VB selbst macht, also nicht die DB? In dem Fall wäre wohl False korrekt. Mir ist natürlich klar, dass ich die Eigenschaft mit einem Mausklick auf True setzen könnte.

    Nachtrag: Ich darf die Spalte nicht schon im SQL-Code auf AUTO_INCREMENT setzen, weil es sonst nicht "autoinkrementiert" wird, sagt die SQlite-Doku. Man darf nur PRIMARY_KEY setzen.
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Unter sqlite.org/autoinc.html steht, dass die ID-Spalte kein AUTOINCREMENT haben darf, sondern INTEGER PRIMARY KEY schon den gewünschten Effekt bringt. Setze ich hingegen AUTOINCREMENT, so kann ich scheinbar nicht meine eigene ID-Spalte verwenden, sondern muss die von SQlite vorgegebene ROWID nehmen. Das möchte ich nicht, weil dann kein einfacher Zugriff darauf möglich ist, z. B. für Verknüpfungen zwischen Tabellen.

    Anscheinend wird allerdings bei INTEGER PRIMARY KEY bei mir im Projekt keine ID erzeugt, ich erhalte eine Fehlermeldung, dass das Feld nicht null sein darf.

    Hat irgendjemand hier jemals mit eigenen ID-Spalten in einer SQlite-DB gearbeitet? Wie ist das optimale Vorgehen? Setze ich einfach AutoIncrement im DataSet auf True und gut ist?
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Hallo Marcus,

    ich benutze SQlite-DB für meine Anwendung und definiere meine ID-Spalten ganz normal mit INTEGER PRIMARY KEY AUTOINCREMENT und beim Insert gebe ich dieses ID-Feld nicht an und es wird automatisch erhöht und ich kann auf dieses Feld ganz normal z. B. bei JOINS zugreifen. Ich lese es so, dass das eigene ID Feld auch benutzt werden kann:

    "If a table contains a column of type INTEGER PRIMARY KEY, then that column becomes an alias for the ROWID. You can then access the ROWID using any of four different names, the original three names described above or the name given to the INTEGER PRIMARY KEY column."

    Aus meiner Sicht kann ein ID Column in SQlite so genutz werden, wie in den anderen SQL-Datenbanken.
    Ich werde es morgen testen, aber in der Doku steht auch:

    If the AUTOINCREMENT keyword appears after INTEGER PRIMARY KEY, that changes the automatic ROWID assignment algorithm to prevent the reuse of ROWIDs over the lifetime of the database. In other words, the purpose of AUTOINCREMENT is to prevent the reuse of ROWIDs from previously deleted rows.

    Für mich heißt das, dass das Schlüsselwort AUTOINCREMENT lediglich das Verhalten der ROWID verändert (und somit auch wieder ROWID verwendet wird).
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Ich habe es jetzt nochmal getestet und es klappt nicht so, wie ich mir das vorstelle.

    Was ich möchte:
    Ich möchte die Datenbank so anlegen (extern), dass ich in Visual Studio beim Erstellen eines typisierten Datasets
    a) bei der ID-Spalte die Eigenschaft "AutoIncrement" automatisch auf "True" habe (das ist aber eher unwichtig, da nur ein Mausklick) und
    b) beim Eintragen von Daten in ein DataGridView meine ID-Spalte mit einer ID automatisch gefüllt wird, ich aber nicht zwei verschiedene IDs (meine ID und ROWID) habe.

    Geht das überhaupt?

    Ich kenne mich nur mit Interbase/Firebird, MySQL und MSSQL aus und da ist das alles kein Problem. Wenn ich da meinen Primary-Key auf Autoincrement habe, so wird dieser auch inkrementiert. Eine interne ID gibt es da meines Wissens nach nicht.

    Aktuell sieht meine Tabelle so aus (generiert mit dem "DB Browser for SQlite"):

    SQL-Abfrage

    1. CREATE TABLE IF NOT EXISTS "departments" (
    2. "id" INTEGER PRIMARY KEY AUTOINCREMENT,
    3. "department" TEXT NOT NULL UNIQUE
    4. );
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum

    vb_fan schrieb:

    "If a table contains a column of type INTEGER PRIMARY KEY, then that column becomes an alias for the ROWID. You can then access the ROWID using any of four different names, the original three names described above or the name given to the INTEGER PRIMARY KEY column."
    Also nach dem, was da steht, sollte es deinen Ansprüchen genügen.
    Durch PRIMARY KEY gibst du der ROWID einen zusätzlichen (vierten) Alias-Namen - es entstehen also nicht mehrere ID-Spalten.
    Und AUTOINCREMENT lässt die Spalte halt incrementieren, wie mans kennt.
    Einzig traurig ist, dass der Dataset-Designer das nicht zu schnallen scheint, wenn er die Datenbank-Informationen abruft.

    ErfinderDesRades schrieb:

    Einzig traurig ist, dass der Dataset-Designer das nicht zu schnallen scheint, wenn er die Datenbank-Informationen abruft.

    Ja, genau das! Und ein Setzen der Property "AutoIncrement" auf "True" würde bestimmt nur dafür sorgen, dass VB den Wert setzt, oder?

    @vb_fan Da es bei dir funktioniert: Arbeitest du womöglich nicht mit typisierten Datasets, sondern z. B. einfach mit Insert-Anweisungen im Textformat?
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum

    Marcus Gräfe schrieb:

    Und ein Setzen der Property "AutoIncrement" auf "True" würde bestimmt nur dafür sorgen, dass VB den Wert setzt, oder?

    jaklar.
    Allerdings muss man gucken, wie ein DataAdapter die Inserts ausführt - das kann je nach DatenProvider unterschiedlich sein.
    Beim SqlServer ist die Synchronisation am intelligentesten, da wird beim Insert ein In-Out-Parameter verwendet, der den von der DB vergebenen PK abruft und automatisch ins Dataset einpflegt.
    Bei Access ists nicht so, da muss man jedem Insert-Command einen Select nachschiessen.
    Ist nervtötend und umständlich.
    Habich mit Dataset->Db eine leidlich allgemeingültige Lösung für gebastelt.
    Jetzt dort nochmal geguckt, da steht ja, dass SqLite auch In-Out-Parameter unterstützt - aber weil der DatasetDesigner bei dir da iwie rumzickt würde ich das erstmal testen, ob das wirklich zufriedenstellend funktioniert:
    Richtig funktionieren tut das, wenn dein Dataset negative IDs generiert, und die DB positive. Wenn du dann neu hinzugekommene Datensätze abspeicherst, müssen die nach dem Abspeichern geänderte ID-Werte aufweisen im Dataset.
    Funzt das nicht, ergeben sich früher oder später lausige DatenProbleme, weil die DB annere IDs haben kann als dein Dataset.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    @ErfinderDesRades Ich habe die AI-Property auf True gesetzt und negative IDs generieren lassen. Dann habe ich mit einem externen Tool mir die interne ROWID anzeigen lassen. Die ist dann auch negativ. Insofern wird der interne Autoinkrementierer durch VB ersetzt (damit wird auch nicht die bisher größte ID intern gespeichert, d. h. nach dem Leeren der Tabelle fängt es wieder bei -1 an). Finde ich sehr unsauber, aber mit einem typisierten Dataset und SQlite wohl nicht anders zu realisieren.

    @vb_fan Alles klar, danke, dann ist es logisch, dass es bei dir funktioniert und bei mir nicht.

    EDIT: Nun habe ich noch einen (etwas älteren) Thread in einem anderen Forum gefunden, der sagt, dass mein Vorhaben nicht realisierbar ist (dort hat der TE dasselbe Anliegen wie ich): mycsharp.de/wbb2/thread.php?postid=3500051
    Dort wies ein gewisser @ErfinderDesRades noch darauf hin, dass in einer Mehrbenutzerumgebung die ID-Generierung über das DataSet nicht geht (ist logisch). Daran hatte ich gar nicht gedacht.

    Ich hatte nun erst gehofft, dass ich evtl. einfach nur "AllowDBNull" bei der ID-Spalte im DataSet auf "True" setzen müsste, damit SQlite nach dem Abschicken der Daten im DGV die ID inkrementiert. Leider gibt es trotzdem eine Fehlermeldung, dass die Spalte nicht Null sein darf.

    Somit bleibt wohl nur, das Datenspeichern komplett manuell per Code zu machen.

    EDIT #2:
    Entferne ich die ID-Spalte aus dem Dataset, so geht es! Allerdings ist das auch keine Lösung, weil ich die ID durchaus brauche, z. B. bei Fremdschlüsseln (Stichwort ValueMember).
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Marcus Gräfe“ ()

    Marcus Gräfe schrieb:

    Ich habe die AI-Property auf True gesetzt und negative IDs generieren lassen. Dann habe ich mit einem externen Tool mir die interne ROWID anzeigen lassen. Die ist dann auch negativ.

    Dann funzt der DataAdapter falsch. Ein Dataset mit PrimKey und AutoIncrement schreibt die PK-Werte nicht in die Datenbank. Allenfalls holt es den DB-seitig generierten PK-Wert ab.
    Da guck nochmal , ob dein Dataset richtig eingerichtet ist.
    Und poste mal Code, wie du updatest.
    Wenn du einen generierten DataAdapter verwendest, dann kannste im DatasetDesigner auch dessen Update-Command nachlesen, und die Parameter-Directons kontrollieren.

    Marcus Gräfe schrieb:

    Somit bleibt wohl nur, das Datenspeichern komplett manuell per Code zu machen.
    Najaa - dazu habich ja meine fabelhafte DbPersistance-Klasse erfunden: Dataset->Db
    Die ist zwar kompliziert, kann aber jedes Dataset korrekt wegschreiben.
    Muss man wie gesagt testen, ob SqLite PK-Werte automatisch zurückschreibt.
    Wenn nicht, muss man die DbPersistance eben mit dem nachschiessenden SelectCommand coden.
    Auf jeden Fall nicht(!) beigehen, und für jedes Tabellchen in jedem Progrämmchen nu eigene Methoden frickeln für Select dies, Update das, Inserte jenes, Delete anneres.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    ErfinderDesRades schrieb:

    Wenn du einen generierten DataAdapter verwendest, dann kannste im DatasetDesigner auch dessen Update-Command nachlesen

    Ich verwende den generierten Adapter. Wo finde ich da denn den Update-Befehl? Es gibt dort "Konfigurieren", aber dort sehe ich nur "SELECT" und generell die Möglichkeit, UPDATE und INSERT neu zu generieren. Aber sehen, was drinsteht, geht da nicht. Und selbst eingreifen erst recht nicht.

    Übrigens habe ich hier noch einen Link gefunden, wie es angeblich geht, aber es geht auch nicht: social.msdn.microsoft.com/Foru…n-problem?forum=vbgeneral
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Man muss den TableAdapter selektieren, und dann die Properties angugge

    Achso - es ist nicht eigentlich ein UpdateBefehl, sondern es sind 4 Commands in einem DataAdapter, und wenn DataAdapter.Update() aufgerufen wird, wendet der DataAdapter auf die Datensätze je eines der drei schreibenden Commands (update, insert, delete) entsprechend der Art der Datensatz-Änderung (DataRowState.Changed, .Added, .Deleted) richtig an.

    Aber ich halt nicht viel von die Dinger, weil da muss man immer noch herummurksen, wenn man Änderungen in mehreren Tabellen hat.
    Jo, da gibts einen generierten TableAdapterManager, aber der ist äusserst zickig (wenn er denn ühaupt richtig funktioniert).

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Ach, da! Ich hatte im Dataset-Designer immer den DataTable, aber nicht den TableAdapter angeklickt.

    Allerdings bringt das Entfernen der ID-Spalte aus der Insert-Anweisung (und in meinem Testfall wird ein Insert ausgeführt, kein Update oder Delete) keinen Unterschied.

    Ich erhalte einfach immer eine NoNullAllowedException. Ich verstehe nicht, wer die Meldung zurückgibt. Ich vermute, es ist nicht die Datenbank. Denn warum auch, die generiert ja eine ID. Es muss also VB sein.

    Meine eigentliche Frage in diesem Thread müsste also lauten: Wie verhindere ich, dass VB prüft, ob eine Spalte Null ist und dann einen Fehler zurückgibt?
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Den Fehler reime ich mir so zusammen: Das automatische Abrufen des Db-seitig generierten PKs funktioniert nicht - stattdessen wird da Null rückübertragen.
    Verhindern, dass der PK auf NotNull geprüft wird, ist dann aber sicherlich keine Lösung - ein PK darf numal nicht Null.

    Abhilfe weis ich da nicht, die SqLite-Connectoren-Funktionalität scheint numal buggy und unbrauchbar.
    Ich selbst habe (vor langem) verschiedendlich mit SqLite rumprobiert, und hat nie geklappt.
    Kann auch ein Versions-Debakel sein, sowohl MS als auch SqLite verändern ja gelegentlich ihre Infrastruktur.

    Probiers mal mit meine DbPersistance-Class - die kann an dem Punkt selbst die Kontrolle übernehmen.
    Danke soweit. Ich werde nochmal ein paar Variationen ausprobieren, vielleicht geht es doch irgendwie (also mit einfachen Mitteln).

    Falls noch jemand einen Lösungsansatz hat, immer her damit. ;)
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum
    Ich habe es nun wie folgt einigermaßen hinbekommen:

    Die Tabelle in der Datenbank:

    SQL-Abfrage

    1. CREATE TABLE IF NOT EXISTS "departments" (
    2. "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    3. "department" TEXT NOT NULL UNIQUE
    4. );

    Im generierten DataSet:
    - Im TableAdapter beim InsertCommand und UpdateCommand unter CommandText im Abfrage-Generator jeweils die id-Spalte weggeklickt
    - Im DataTable bei der id-Spalte AutoIncrement auf True

    Nun generiert mir SQlite brav automatisch die IDs, während die von VB generierten IDs verworfen werden (die standardmäßig negativ sind).

    Es ergibt sich im Moment nur ein Problem, wobei ich nicht weiß, ob das ein Problem ist (wird sich noch zeigen):
    Nach dem Speichern wird noch die von VB generierte ID angezeigt. Erst nach dem Neuladen kommt die "echte" ID zum Vorschein.
    Besucht auch mein anderes Forum:
    Das Amateurfilm-Forum

    Marcus Gräfe schrieb:

    Nach dem Speichern wird noch die von VB generierte ID angezeigt. Erst nach dem Neuladen kommt die "echte" ID zum Vorschein.
    Das dürfte Problem machen, wenn du einen Datensatz erstellst, speicherst, ihn dann änderst und dann diese Änderung auch speichern willst.
    Weil beim Speichern der Änderung liegen ja in DB und Dataset verschiedene PKs vor.

    Ich täte auch nicht gut finden, wenn man nach jedem Insert mehr oder weniger grosse Datenmengen neu laden muss.
    Zumal beim NeuLaden die aktuelle Selektion verloren geht, und iwie wiedergesucht werden muss.
    Da habich einfach höhere Ansprüche, was ich von einer Datenbank-Infrastruktur erwarte.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()