Verständnis: DataSet weiß den nächsten Auto-Increment... woher?

  • C#
  • .NET 4.5

Es gibt 4 Antworten in diesem Thema. Der letzte Beitrag () ist von asuryan.

    Verständnis: DataSet weiß den nächsten Auto-Increment... woher?

    Hallo zusammen!

    Auf meinen Abenteuern in der Welt des DataSets und SQLite bin ich auf folgende Verständnisfrage gestoßen:

    Woher weiß ein DataSet, wenn ich darauf noch keinen Fill mit einem DataAdapter gemacht hab, wie der nächste AutoIncrement-Wert in der DB sein MUSS?

    Der Ablauf ist folgender:

    Ich habe noch keinen Fill auf die Tabelle ausgeführt. Ich füge einer DataSet Tabelle eine neue Row hinzu. Das DataSet vergibt dann theoretisch einen auto increment von 1, weil das DataSet ja die DB dahinter nicht gefragt hat. Jetzt führe ich ein Update auf die Tabelle mit dem Adapter aus: hier sind aber schon 10 Rows in der DB. Dh die ID 1 ist schon vergeben... was macht das DataSet dann?!

    Die gleiche Frage stellt sich mir, wenn ich nur ein Subset der kompletten Tabelle gequeriet hab, wo der höchste AutoIncrement-Wert gar nicht mit in der Ergebnissmenge ist.

    Danke im Voraus
    Spekulatius ohne DB: Das tDS schaut in der Tabelle nach der größten ID und haut noch 1 drauf, also wie manuell: Dim NeueID = Tds.DieZieltabelle.Select(Function(x) x.ID).Max + 1
    Bzgl. Erst ne Row, dann das Fill: Probier es aus. Entweder geht es genauso oder (was ich eher vermute): Crash mit ConstraintException wegen doppelter ID-Vergabe
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Häufig von mir verwendete Abkürzungen: CEs = control elements (Labels, Buttons, DGVs, ...) und tDS (typisiertes DataSet)
    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht in den Spekulatiusmodus gehen.
    Hm okay verstehe.

    Crash mit ConstraintException wegen doppelter ID-Vergabe


    Das würde aber heißen, dass ich das DataSet immer KOMPLETT befüllen müsste. Bei vielen Einträgen macht das aber manchmal keinen Sinn. Sondern da ist es besser ein Subset zu querien zb die letzten 3 Monate oder so. Da ist ja die höchste ID nicht unbedingt dabei.

    Also wird die Zeile als „neu“ markiert evtl.

    Wenn ich jetzt, wie ich es bei SQLite mit Datum-Columns machen muss, manuell, ohne ein DataAdapter.Update, einen SQL command mit INSERT etc. für eine Row ausführe, kann es dann scheinbar sein, dass die Primarykeys nicht mehr eindeutig sind... befürchte ich.

    die nächste id wird zwar in der DB vergeben, aber die DDataTableRow weiß es nicht wie seine ID jetzt sein müsste.



    Also ich konnte das gerade mal testen.

    Ablauf:

    1. Erstellen einer Row im DataSet. Bekommt die ID 1.
    2. Per cmd.ExecuteNonQuery(); folgenden Command ausführen: "INSERT INTO TableTest ("testColumn") VALUES ("test")". (natüröich mit den Werten der neuen Row)
    3. Im SQL Table bekommt die Row die ID 1285, weil die nächst freie ID erst 1285 ist.

    Soweit alles cool (scheinbar).

    4. Ich bearbeite die gerade eingefügte Row (wir erinnern uns: ID 1 im DataSet)
    5. Update per cmd.ExecuteNonQuery(); "UPDATE TestTable SET testColumn='Anderer Text' WHERE id=1" -> Klar... das DataSet hat die Row ja mit ID 1 abgespeichert.
    6. Was passiert -> Natürlich wird die falsche Row geupdatet. Nämlich die mit ID 1... NICHT 1285 wie sie eignetlich in der DB ist.

    Hab ich grundsätzlich einen Fehler in meiner Herangehensweise!?

    Danke!



    Workaround wäre folgender:
    Die letzte ID von der SQLite sqlite_sequence Tabelle holen und dann ins DataSet zurückschreiben.

    C#-Quellcode

    1. public SCSLiteResult insertEntry(DSDataSet.EntryRow entry)
    2. {
    3. String sql = SCSQLiteDB.getTableInsertSQL(entry.Table);
    4. SQLiteCommand command = new SQLiteCommand(sql);
    5. this.addEntryParamters(entry, command);
    6. SCSLiteResult result = this.sqlite.executeCommand(command);
    7. DataTable tb = this.sqlite.executeReadQuery("SELECT seq FROM sqlite_sequence WHERE name='Entry'", null);
    8. accountEntry.id = Convert.ToInt32(tb.Rows[0][0]);
    9. this.db.AcceptChanges();
    10. return result;
    11. }


    Funktioniert. Aber ist das Best Practice?!
    Fühlt sich für mich nicht so an.

    Dreifachpost zusammengefügt. ~Thunderbolt

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

    Jo, das ist leider die Best Practice.
    Zum Konzept gehört übrigens, dass die Ids, die das Dataset vergibt, immer negativ sind, und die DB vergibt positive.
    So sieht man an den Daten, ob ein Datensatz schon in der DB ist oder neu.

    Manche DBs unterstützen das noch bisserl mehr, etwa SqlServer kennt In-Out-Parameter. Damit wird die neue ID mit nur einem Command gleich zurück-transportiert, und ein DataAdapter schreibt das auch gleich in die Insertete DataRow.

    Bei anderen DBProvidern muss man tatsächlich jedem Insert-Command einen Select hinterherschiessen.
    Kann man auch generisch lösen, im RowUpdated-Event, wie in Dataset->Db gezeigt.

    Es ist allerdings kompliziert, aber wenn mans einmal geschafft hat, den Code einzubinden, hat man die Kacke vom Hals.
    Ich danke!

    Okay, dann muss es eben so sein... :D

    Ich hab einfach in meinem SQLite Wrapper jetzt ne Methode die das alles in einem macht und mir ein custom result zurückgibt.
    Wie Du schon sagst; ich hoffe ich hab "die Kacke dann vom Hals". ;)

    C#-Quellcode

    1. public SCSQLiteResult insertQuery(SQLiteCommand cmd, DataTable table)
    2. {
    3. SCSQLiteResult result = new SCSQLiteResult();
    4. this.connection.Open();
    5. cmd.Connection = this.connection;
    6. if(cmd.CommandText.Contains("INSERT INTO"))
    7. {
    8. try
    9. {
    10. this.printDebug("Inserting command '" + cmd.CommandText + "'");
    11. result.rowsAffected = cmd.ExecuteNonQuery();
    12. try
    13. {
    14. var da = new SQLiteDataAdapter();
    15. SQLiteCommand lastInsertIDCommand = new SQLiteCommand("SELECT seq FROM sqlite_sequence WHERE name='" + table.TableName + "'", this.connection);
    16. this.printDebug("Get last insertID: '" + lastInsertIDCommand.CommandText + "'");
    17. da.SelectCommand = lastInsertIDCommand;
    18. DataSet ds = new DataSet();
    19. DataTable tb = ds.Tables.Add("Result");
    20. da.Fill(tb);
    21. result.lastInsertID = Convert.ToInt32(tb.Rows[0][0]);
    22. this.printDebug("Last insertID for table '" + table.TableName + "' : " + result.lastInsertID + "");
    23. } catch (Exception ex)
    24. {
    25. this.printDebug(ex.Message);
    26. result.setError(ex.Message);
    27. }
    28. finally
    29. {
    30. this.connection.Close();
    31. }
    32. }
    33. catch (Exception ex)
    34. {
    35. this.printDebug(ex.Message);
    36. result.setError(ex.Message + ", " + result.message);
    37. }
    38. finally
    39. {
    40. this.connection.Close();
    41. cmd.Dispose();
    42. }
    43. }
    44. else
    45. {
    46. result.setError("Not an insert query: " + cmd.CommandText);
    47. this.printDebug(result.message);
    48. }
    49. return result;
    50. }

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