Laden...

Neue DataRow hinzufügen bringt Fehler, da Spalte 'classID' keine 'nulls' zulässt

Erstellt von GeneVorph vor 6 Jahren Letzter Beitrag vor 6 Jahren 2.546 Views
G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 6 Jahren
Neue DataRow hinzufügen bringt Fehler, da Spalte 'classID' keine 'nulls' zulässt

verwendetes Datenbanksystem: SQLite3

Sodale,

ganz kurz zu dem, was ich vorhabe:

  • einem DataTable soll eine neue DataRow hinzugefügt werden. Der DataTable selbst ist eine Kopie des Tables aus der Datenbank.

Ich habe mich dabei besonders auf das Openbook von Rheinwerk gestützt und habe zwei, drei Verständnisfragen, die ihr mir vielleicht erklären könnt.

a) Prinzipiell habe ich demnach drei Möglichkeiten eine neue DataRow hinzuzufügen, wobei ich im Weiteren mich auf .NewRow() beziehen werde.
Mein DataTable enthält eine leere Tabelle mit insgesamt fünf Spalten
1 - classID, Typ Integer, Primary Key
2 - className, Typ Text, NullAllowed
3 - classHead, Typ Text, NullAllowed
4 - partnerClasses, Typ Text, NullAllowed
5 - classShort, Typ Text, NullAllowed

Erste Frage: da mein DataTable eine Kopie aus der Datenbank ist, gehe ich davon aus, dass die Metadaten der Columns übernommen werden (welche Spalte welchen Typs ist, bzw. PrimaryKey). Ist das soweit korrekt?

b) Folgende Aussage finde ich bei Rheinwerk zum Thema "Sonderfall AutoInkrement-Spalten" (s. hier):

Doch welche Werte sollten Sie in der DataTable vergeben? Eigentlich müssen Sie nur sicherstellen, dass neue Schlüsselwerte nicht mit den Schlüsselwerten in Konflikt geraten, die bereits in der DataTable enthalten sind. Sie können auch davon ausgehen, dass negative Werte in der Datenbank nicht verwendet werden. Empfehlenswert ist daher, die beiden Eigenschaften AutoIncrementSeed und AutoIncrementStep auf jeweils –1 festzulegen. Zudem sollten diese Einstellungen erfolgen, ehe das DataSet mit den Daten gefüllt wird.

Ich habe dazu folgenden Code erstellt:


 DataRow newRow = tempDataTable.NewRow();
                    tempDataTable.Columns[0].AutoIncrement = true;
                    tempDataTable.Columns[0].AutoIncrementSeed = -1;
                    tempDataTable.Columns[0].AutoIncrementStep = -1;
                   
                    newRow[dbVariables.ClassName] = txtClassName.Text;
                    newRow[dbVariables.ClassHead] = txtHeadmaster.Text;
                    newRow[dbVariables.PartnerClasses] = txtClassNames.Text;
                    newRow[dbVariables.ShortClassName] = classShort;

                    tempDataTable.Rows.Add(newRow);

                    studentDB.UpdateTable(dbVariables.TableName, tempDataTable);

Wie man sehen kann, werden nur vier Spalten erstellt; die Spalte mit dem Primärschlüssel fehlt - klar, den kenne ich zu diesem Zeitpunkt ja nicht. Ich weiß aber, das Column[0] den Primärschlüssel besitzt; im ersten Block setze ich daher diese Spalte auf AutoIncrement true, und setze Schrittweite und Seed auf -1.

Beim Ausführen des Codes erhalte ich eine Fehlermeldung:

Ein Ausnahmefehler des Typs "System.Data.NoNullAllowedExcewption" ist aufgetreten ... Spalte 'classID' lässt nicht 'nulls' zu.

Genau - das stimmt ja auch. Meine Frage nun dazu: wäre es richtig zu behaupten, dass AutoIncrement, AutoIncrementSeed, etc. sich eher wie ein Vermerk ausnehmen, der später für das Schreiben in die Datenbank wichtig wird? Weniger sich auf mein DataTable bezieht? Oder vereinfacht: mein Table "weiß", dass classID nicht null sein darf, generiert aber durch obigen Code gar keinen neuen Wert?

c) Wenn meine Vermutung aus b zutrifft, stellt sich die Frage - wie erstelle ich dann eine neue DataRow, wenn die erste Spalte nicht null sein darf, aber die Methode .NewRow auch keinen neuen Index erstellt? Irgendwoher muss der Table ja wissen, dass die erste Spalte nicht null sein darf - OK. Aber dann muss es doch möglich sein einen Wert zu generieren?

Ich habe übrigens einen "Workaround" gefunden, der aber recht, hm, unsexy ist:
ich ermittele die Anzahl der Zeilen in der Tabelle und zähle 1 dazu und übermittele diesen Wert für die erste Spalte...Bitte nicht ausflippen, ich weiß ja, dass das nix ist. Daher ja die ganze Fragerei 🙂

Ist ne Menge Text, aber es würde mir sehr weiterhelfen, dazu ein paar Antworten zu bekommen.
Vielen Dank, viele Grüße
Vorph

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 6 Jahren
[GELÖST] Update DataTable

O M G !

Zuerst möchte ich euch die Lösung posten - dann hätte ich noch ein paar Fragen, vielleicht erbarmt sich ja noch jemand einer Antwort:

Also:

  1. Ich beziehe den Table folgendermaßen:


CLASS classNewOrCreate 

public DataTable GetTable(string tableName)
        {
            string connectionPath = dbVariables.ConnectionString;

            try
            {
                //Connection erstellen
                SQLiteConnection myConnection = new SQLiteConnection(connectionPath);

                //Verbindung zur Datenbank öffnen
                myConnection.Open();

                //SELECT * FROM <Tabellenname>
                string cmdStr = "SELECT * FROM " + tableName;
                
                //DataTable-Objekt erstellen 
                DataTable myTable = new DataTable();

                SQLiteDataAdapter myAdapter = new SQLiteDataAdapter(cmdStr, myConnection);

                // DIESE ZEILE HATTE ICH NICHT
                myAdapter.FillSchema(myTable, SchemaType.Source); 

                myTable.Columns[dbVariables.ClassesID].AutoIncrement = true; 
                myTable.Columns[dbVariables.ClassesID].AutoIncrementSeed = 1; 
                myTable.Columns[dbVariables.ClassesID].AutoIncrementStep = 1; 

                myAdapter.Fill(myTable);

                myConnection.Close();

                return myTable;

            }
            catch (SQLiteException e)
            {
                MessageBox.Show(e.ToString());
                return null;
            }
        }


Die Zeile


 myAdapter.FillSchema(myTable, SchemaType.Source);

war in meinem ursprünglichen Code NICHT enthalten. Diese ist jedoch ganz wesentlich, wie man - ein paar Kapitel vor dem von mir oben verlinkten Artikel - erfahren kann. Eine neue DataRow ohne diese Zeile lässt sich nämlich problemlos erstellen und einfügen - update kann man aber scheinbar knicken. Wenn man dieses nicht weiß, ist man im besten Fall einfach nur gea..... .

Dann musste ich meinen Code von heute mittag (s. oben) noch an zwei Stellen modifizieren:


 DataRow newRow = tempDataTable.NewRow();
                    //tempDataTable.Columns[].AutoIncrement = true;
                    //tempDataTable.Columns[0].AutoIncrementSeed = -1;
                    //tempDataTable.Columns[0].AutoIncrementStep = -1;

                    newRow[dbVariables.ClassName] = txtClassName.Text;
                    newRow[dbVariables.ClassHead] = txtHeadmaster.Text;
                    newRow[dbVariables.PartnerClasses] = txtClassNames.Text;
                    newRow[dbVariables.ShortClassName] = classShort;

                    tempDataTable.Rows.Add(newRow);

                    studentDB.UpdateTable(dbVariables.TableName, tempDataTable);

(Alter Code wurde auskommentiert, zwecks besserem vorher/nachher Vergelich)

Was ich nicht verstehe ist folgendes: bei Rheinwerk finde ich zu lesen:

Die Werte, die ADO.NET erzeugt, müssen Sie als Platzhalter verstehen. Sie werden später bei der Aktualisierung der Originaldatenbank nicht mit zurückgeschrieben. Die tatsächlichen Schlüsselwerte erzeugt die Datenbank selbst.

Doch welche Werte sollten Sie in der DataTable vergeben? Eigentlich müssen Sie nur sicherstellen, dass neue Schlüsselwerte nicht mit den Schlüsselwerten in Konflikt geraten, die bereits in der DataTable enthalten sind. Sie können auch davon ausgehen, dass negative Werte in der Datenbank nicht verwendet werden. Empfehlenswert ist daher, die beiden Eigenschaften AutoIncrementSeed und AutoIncrementStep auf jeweils –1 festzulegen. Zudem sollten diese Einstellungen erfolgen, ehe das DataSet mit den Daten gefüllt wird.

Das ist eigentlich genau das, was ich getan habe, oder? Nur, dass in meinem Table unter SQLite dann negative Indices eingetragen wurden. Jetzt habe ich die negativen Werte einfach in positive abgeändert - und es funzt.

Falls mir hier jemand mit Erläuterungen weiterhelfen könnte, würde mir das schon sehr helfen!

Gruß
Vorph

F
10.010 Beiträge seit 2004
vor 6 Jahren

Das erste Problem ist, das die Junks von Rheinwerk bei ADO.NET meist ziemlichen Mist schreiben und die hälfte weglassen.

  1. Das einlesen der Kompletten Datenbank ist sehr selten richtig. Wenn man die DB nicht als solche benutzen möchte, dann Benutze gleich Serialisierung.

  2. Die Junx benutzen den MS Sql Server und Du SQLite, das sind 2 verschiedene Paar Schuhe.
    MS Sql erlaubt das setzen von Identity Spalten nur auf Umwegen, bei SQLite kannst du da reinschreiben was du willst.

  3. UpdateTable ( was ist das?? ) muss ja irgendwoher die entsprechenden SQL-Anweisungen generieren, wenn du da die falschen nimmst, kann es nicht funktionieren.
    Wie sieht denn das UpdateTable aus?

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 6 Jahren

Hallo FZelle,

vielen Dank für deinen post!
Bevor ich dazu noch drauf eingehe, hier mal der Code meiner UpdateTable-Methode:


public void UpdateTable(string tableName, DataTable sourceTable, bool newOrEdit)
        {          
            try
            {
                string connectionPath = dbVariables.ConnectionString;
                //Connection erstellen --> der connectString gibt dabei den Pfad an.
                SQLiteConnection myConnection = new SQLiteConnection(connectionPath);

                myConnection.Open();
                                
                string myUpdateString = "SELECT * FROM " + tableName + "";
                
                SQLiteDataAdapter myAdapter = new SQLiteDataAdapter(myUpdateString, myConnection);

                SQLiteCommandBuilder comBuild = new SQLiteCommandBuilder(myAdapter);

                myAdapter.DeleteCommand = comBuild.GetDeleteCommand(true);
                myAdapter.UpdateCommand = comBuild.GetUpdateCommand(true);
                myAdapter.InsertCommand = comBuild.GetInsertCommand(true);
                

                myAdapter.Update(sourceTable);

                myConnection.Close();

                if (newOrEdit == true)
                {
                    MessageBox.Show("Klasse erstellt!");
                }
                else
                {
                    MessageBox.Show("Klasse aktualisiert!");
                }

            }
            catch (SQLiteException e)
            {
                MessageBox.Show(e.ToString());
            }

        }

Mit der Methode UpdateTable kann ich derzeit neue Einträge anlegen, bzw. angelegte Einträge bearbeiten. (Table wird ausgelesen - Eintrag dazu/bearbeitet - Table in der Datenbank geupdated) [EDIT] quatsch - ich wollte damit sagen: ich habe meinen DataTable, trage dort neue DatarRows ein oder bearbeite bestehende DataRows und schicke den so bearbeiteten DataTable an obige Methode, wo die Änderungen dann wirksam werden.

So, jetzt noch kurz eine Erläuterung, bzw. Fragen:

a)

2. Die Junx benutzen den MS Sql Server und Du SQLite, das sind 2 verschiedene Paar Schuhe.
MS Sql erlaubt das setzen von Identity Spalten nur auf Umwegen, bei SQLite kannst du da reinschreiben was du willst.

OK, das Erste verstehe ich - mit Identity-Spalten meinst du PrimaryKey, bzw. AutoIncrement usw.?

b)

  1. Das einlesen der Kompletten Datenbank ist sehr selten richtig. Wenn man die DB nicht als solche benutzen möchte, dann Benutze gleich Serialisierung.

Mein Gedanke war, dass es nicht, hm, "guter Stil" sein kann, ständig Datenbankzugriffe zu verursachen. Bspl: Für einen neuen Eintrag musste ich vorher erstmal eine Query feuern, die schaut OB überhaupt Einträge in der Datenbank sind, dann ein Query, dass die Namen der vorhandenen Einträge einer Liste übergibt, dann ein Query.... --> das führte zu dem Gedanken, mit einer Kopie der Tabelle aus der Datenbank zu arbeiten, diese nach Einträgen, Bearbeitungen und Löschungen zu updaten und wieder der Datenbank zu übergeben. Wenn ich das hier richtige verstanden habe, geht Serialisierung genau in diese Richtung?

Andererseits würde ich gerne lernen mit Datenbanken zu arbeiten und nutze meine Projekte immer als "Übungsaufgaben".

Dabei sind wir einem weiteren wichtigen Punkt: das Internet ist voll von Tutorials, Dokumentationen und YouTube zum Thema - vielleicht lehne ich mich ja weit aus dem Fenster, aber ich habe manchmal das Gefühl, man lernt mehr "falsch", wenn man sonst keine Referenzen hat, als dass man direkt Experten fragt. Der Laie sucht immer den Weg, der als erstes funktioniert - das muss ja nicht zwangsläufig ein guter Weg sein 😉

s. dazu folgendes: nachdem ich mit obiger Methode nun also Einträge hinzufügen und Editieren kann, klappt Löschen überhaupt nicht. Hier mein Code dazu:


 if (myResult == DialogResult.Yes)
            {                
                DataRow[] dr = killTable.Select("" + cmVariables.ClassName + " = '" + cmbClasses.Text + "'");                
                //Index als String übergeben - dr ist ein Array; da ein gesuchter Eintrag nur 1x vorkommen kann, muss das Array an Index 0 (erster EIntrag) abgegriffen werden.
                string indexName = (killTable.Rows.IndexOf(dr[0])).ToString();

                //String in int parsen - dadurch erhält man den Index der DataRow, die mit den Werten gefüllt ist, die für die Löschung benötigt werden
                int i = Int32.Parse(indexName);

                DataRow modifiedRow = killTable.Rows[i];

                modifiedRow.BeginEdit();
                                          
                killTable.Rows[i].Delete();

                //MessageBox.Show("hier: " + killTable.Rows[i].RowState);

                modifiedRow.EndEdit();

                killTable.AcceptChanges();                

                killClass_Execution(killTable, cmbClasses.Text, ShortClassNm);
                
            }

Ich habe mir in der - jetzt auskommentierten - MessageBox den RowState ausgeben lassen - dieser ist als Deleted markiert; warum AcceptChanges keine Wirkung zeigt - keine Ahnung. Mit meinem Code oben klappt es leider nicht...

Zu einer Anmerkung von dir noch eine kleine Anmerkung von mir:

Das erste Problem ist, das die Junks von Rheinwerk bei ADO.NET meist ziemlichen Mist schreiben und die hälfte weglassen.

Ich bin - leider - nicht in der Lage, das zu beurteilen. Aber es spricht mir doch irgendwie aus der Seele. Denn das ist das erste, aber nicht einzige Problem^^ Die Sache ist die: ich progammiere zum Spaß und weil ich mir hin und wieder mit kleinen selbstgeschriebenen Progrämmchen auch schon Arbeit gespart habe. Ich habe leider nie einen Kurs besucht (wie auch? Such mal C# an 'ner VHS...), kein Vorwissen und bin beruflich in gaaanz anderen Gefilden unterwegs: kurzum - alles ist zusammengelesen, erfragt oder aus Tutorials. "Visual C# - das umfassende Training" war mein Einstieg ins Programmieren überhaupt. Das Wörtchen "umfassend" (das habe ich dann schnell gemerkt) ist dabei noch sehr geschönt. Fachwörter mit Fachwörtern zu erklären - ganz toll. Ich habe jetzt Serialisierung gegoogelt und nach gut 30 Minuten eine ungefähre Vorstellung davon. Habe dazu ausnahmsweise in der Doku was gefunden, was in (verständlichem) deutsch geschrieben stand und zumindest nach mehrmaligem Begriffezusammensuchen dann auch Sinn ergab. Wer sich mal in den typischen Programmierlaien reinversetzen möchte, kann ja mal das das durchlesen - ist für mich Klartext, aber so wie hier bei einem Aussenstehenden erstmal 60% vorbeirauschen, geht's mir bei der Doku - didaktischer Alptraum.

beste Grüße
Vorph

F
10.010 Beiträge seit 2004
vor 6 Jahren

Das Du dich darauf verlassen musst das ein "Lehrbuch" auch das richtige lehrt ist schon klar, und das es am Anfang holpert auch.
Nur hast du in deinem Fachgebiet den Vorteil, das da Fachbücher vor dem Veröffentlichen von Profis korrigiert werden.
In unserer Profession werden Bücher meist von Leute geschrieben die sich berufen fühlen und das Korrekturlesen in den Verlagen erfolgt meist nicht auf fachlicher Basis, sondern nur auf Stil, Durchgängigkeit und Rechtschreibfehler.
Gerade Einsteigerbücher strotzen nur so von Fehlern und falschen Herangehensweisen.

Das allerwichtigste was Du lernen musst ist das die Doku von MS immer mehr recht hat als irgendwelche Bücher und deshalb auch zwingend gelesen werden sollte.

Schau doch mal was AcceptChanges macht.
https://msdn.microsoft.com/de-de/library/system.data.datatable.acceptchanges(v=vs.110).aspx
Wenn du das also aufrufst, wird der RowState auf unchanged gesetzt, und der DataAdapter weis nicht was er tun soll.

Zu Datenbanken:
Das suchen in Datenbanken ist (normalerweise ) so optimiert das es schneller ist als z.b. in der Datatable im Speicher.
Guter Stil ist es das man benutzt was man hat.
Wir haben teilweise Datenbanken mit 1 Mrd Datensätze, die in den Speicher zu lesen wäre nicht mal ansatzweise möglich.
Natürlich werden Listen geladen, aber selten komplett, auch abfragen ( Count(ID) from ) ob überhaupt etwas in einer DB steht sind sehr schnell.

Und zu deinem UpdateTable, schau mal sqlitecommandbuilder, wie umsetzen? an.

Wenn du "nur" ein bisschen Programmieren willst ist das ja nicht schlecht, aber wenn es irgendwann dann für den Beruf sein soll, ist ein echtes Training wichtig.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 6 Jahren

Ahoi FZelle,

vielen Dank für die und die Links! Ich hab's bei mir jetzt hinbekommen, sowohl das Speichern von Daten, Löschen und Bearbeiten. Nur halt wohl nicht der elegante Weg. Wenn meine App - ist ja nur für mich - Praxistauglichkeit erreicht, werden die Einträge so gering sein, dass sich wohl eine DB gar nicht erst gelohnt hätte; wie schon gesagt, mir geht es da um die Übung.

Womit wir beim Stichwort währen - wie man es jetzt nicht macht, weiß ich 😃 Ich hab da nette Sachen gebookmarkt zum Thema DataBinding, Datenbankstrukturen und dergleichen. Alles von myCSharp.de - hier hat's ja zum Glück viele Profis, die Korrekturlesen 😃

Nochmals vielen Dank,
Gruß
Vorph