Laden...

SQLite Update aktualisiert die Datenbank nicht und das Programm friert ein

Erstellt von GeneVorph vor 7 Jahren Letzter Beitrag vor 7 Jahren 3.375 Views
G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 7 Jahren
SQLite Update aktualisiert die Datenbank nicht und das Programm friert ein

Hallo,

ich nutze in Visual Studio 2015 SQLite 3. Meine Datenbank erstelle und bearbeite ich mit dem SQLite Manager unter Mozilla.

Leider habe ich derzeit folgendes Problem und komme echt nicht weiter:

Ich habe eine WinForm-Anwendung erstellt, die u. a. auf eine SQLite Datenbank zugreift. Das Lesen von der Datenbank funktioniert prima: ich fülle DataGridViews und kann einzelne Labels mit Werten aus der Datenbank füllen. Soweit also alles prima.

Problematisch wird es beim Schreiben in die Datenbank, bzw. beim Update. Folgendes Problem: ich möchte in einem Table einen Wert, der zuvor in einer Textbox zu Verfügung gestellt wird, in der Datenbank ablegen. Um das Ganze ein bisschen anschaulicher zu gestalten: stellt euch ein Konto vor. In die Textbox wird ein Betrag x eingetragen, der in einer Methode mit dem alten Betrag verrechnet wird (alt + neu also). Der Einfachheit halber habe ich jetzt auf sämtlichen SchnickSchnack verzichtet (z.B. Übergabe des Textboxinhalts per Variable und dergleichen) und in den CommandText meinen SQL-Befehl ausgeschrieben.

In diesem Fall soll der Wert "100" in den Table "Konten" geschrieben werden, in die Spalte "Kontoname", in die Zeile deren Eintrag "Giro_Konto" zugeordnet ist. Ich habe versucht das so zu lösen:



string myConnectionString = @"Data Source : C:\MyDataBase\Finanzplaner.sqlite; Version = 3";

SQLiteConnection myConnection = new SQLiteConnection(myConnectionString)

SQLiteCommand cmd = new SQLiteCommand();
                    cmd.CommandText = "UPDATE Konten SET Saldo = 100 WHERE Kontoname = 'Giro_Konto'";
                    cmd.Connection = myConnection;
                    myConnection.Open();

Der Code wird zwar durchlaufen, aber der Wert wird in der Datenbank nicht geupdated. Erschwerend kommt hinzu, dass keine Exception ausgelöst wird.

Ich habe dazu verschiedene Codeschnippsel ausprobiert, die alle die Datenbank updaten sollen - obiger ist sozusagen nur der letzte Entwurf^^ Aber ich bin mit meinem Latein gerade echt am Ende.

Ich habe den CommandText im SQLite Manager getestet - dort funktioniert er tadellos.

UPDATE Konten SET Saldo = 500 WHERE Kontoname = 'Giro_Konto'; 

Dort steht dann tatsächlich 500 an der entsprechenden Stelle, wo zuvor der alte Betrag stand.

Heute morgen hatte ich den Code-Block noch um


try
{
    if (myConnection.State == ConnectionState.Open)
     {
       //Messagebox ("OK ")
      }
      else
      {
      //Messagebox ("Fehlschlag")
       }
catch (SQLiteException e)
{
//MessageBox.Show(e.Message);
}

ergänzt. Per Einzelschritt konnte ich lediglich feststellen, dass der Code wie gewünscht durchlaufen wird - nur, dass eben nix in der Datenbank landet.

Jemand eine Idee, was ich (alles) falsch mache?
Gruß
Vorph

4.931 Beiträge seit 2008
vor 7 Jahren

Hallo,

führst du denn auch


/* int count = */ cmd.ExecuteNonQuery();

aus?
Der Rückgabewert gibt dir dann die Anzahl der geänderten Datensätze zurück (sollte also mindestens 1 sein).

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

Hi Th69,

mein Code schaut jetzt so aus (einzige Veränderung zu oben: ich habe int i = cmd.ExecuteNonQuery eingefügt):


string myConnectionString = @"Data Source : C:\MyDataBase\Finanzplaner.sqlite; Version = 3";

SQLiteConnection myConnection = new SQLiteConnection(myConnectionString)

SQLiteCommand cmd = new SQLiteCommand();
                    cmd.CommandText = "UPDATE Konten SET Saldo = 100 WHERE Kontoname = 'Giro_Konto'";
                    cmd.Connection = myConnection;
                    myConnection.Open();

int i = cmd.ExecuteNonQuery();

MessageBox.Show(i.ToString());


Jetzt läuft der Code nicht mehr zum Ende durch - bei der Zeile


int i = cmd.ExecuteNonQuery();

schmiert das Programm ab. Ist mir ein völliges Rätsel.

Nur mal eine doofe Anfängerfrage: ist es denkbar, dass ich mir am System was zerschossen habe? Oder: Mir kommt es fast so vor, als wäre es mir aus Visual Studio raus nur möglich lesend auf die Datenbank zuzugreifen - ist sowas möglich? Irgendwelche Admin-Rechte nötig?

Alles was ich bisher mit der Datenbank gemacht habe, waren einfache Abfragen. Insert und Update funktionieren nur, wenn ich den SQLite Manager ranziehe ("SQL ausführen").

T
314 Beiträge seit 2013
vor 7 Jahren

Und was sagt die Fehlermeldung?

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

Und was sagt die Fehlermeldung?

Tja...das ist mein Problem - gar nichts: das Programm friert einfach ein und ich muss in der Taskleiste VS anklicken, um das Programm händisch abzubrechen (Stopp).

Ich habe in meinen Connection-String jetzt übrigens auch mal Read Only = false eingefügt. Ändert leider auch nix...

T
2.219 Beiträge seit 2008
vor 7 Jahren

@GeneVorph
Dann Debugge dein Program.
Den sonst kann dir auch keiner sagen wo das Problem liegt.
Hier musst du schauen warum er hängt.
Entweder wartet er auf etwas, Timeout, oder es gibt einen anderen Grund.

Ein Program das hängt, wartet meistens auf etwas.
Aber ohne den gesamten Code kann dir dies niemand sagen.
Hier musst du deinen eigenen Code debuggen und schauen was er an dem Break Point macht.
Das gehört zu den Grundaufgaben eines Entwicklers und dies wird auch von dir erwartet.
Gründe für das hängen kann es viele geben und das erraten will heir niemand.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

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

Hey T-Virus,

du wirst wohl leider recht haben. Ich will/wollte aber auch niemanden zum Raten anhalten - ich hatte die leise Hoffnung, bei den Pros würden die Glocken läuten, weil ich vielleicht einen ganz offensichtlichen Fehler gemacht hätte. Dem scheint leider nicht so zu sein.

Dennoch Danke für eure Hilfe.

2.207 Beiträge seit 2011
vor 7 Jahren

Hallo GeneVorph,

fang mal alle Exceptions ab, dann hast du sicher auch eine Fehlermeldung.

Verwende ausserdem usings. Mit dem Debugger und ner Fehlermeldung siehst du sicher, was da vor sich geht. Auch SQL-Parameter sind sicher nicht schlecht anzuschauen.

Weiter gibt es unzählige Beispiele im Netz zu sqlite updates. Hast du dich da an einem orientiert?

Gruss

Coffeebean

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

Hey Coffeebean,

vielen Dank für die Tipps - ich bin Hobby-Coder und habe gerade erst die ersten Gehversuche hinter mich gebracht. Deine Hinweise geben mir jetzt mal eine Richtung, wie ich die Fehlersuche sinnvoll angehen kann, das hilft schon eine Menge, sonst wird es ein kopfloses rumprobieren und zusammenklauben.

Weiter gibt es unzählige Beispiele im Netz zu sqlite updates. Hast du dich da an einem orientiert?

Hier muss ich sagen: leider ja. An mehreren^^ Die SQLite-Statements sind nicht umsonst deklarativ, da gibt's also nicht so viele Fragezeichen. Leider dann aber beim Arbeiten mit denselben in C#. Was viele "Erklärer" leider nicht machen, ist a) ihren Stoff didaktisch sinnvoll strukturieren und b) die Funktionsweisen erläutern. Bei mir ist das z. B. ExecuteNonQuery. Die reine Formulierung würde mich darauf schließen lassen, dass ich bei allem, was keine reine Datenbankabrage ist (Lesen) diese Methode nicht benötige. Seltsamerweise taucht sie dann doch in gut der Hälfte aller Fälle auf, wenn es um INSERT und UPDATE geht.

Konkret orientiert habe ich mich hieran, daran, und dort.

849 Beiträge seit 2006
vor 7 Jahren

Nur die hälfte? Das würde mich wundern.

Inserts und Updates sind "NonQuery´s" da sie keine Ergebnismengen zurück geben, ausser die Anzahl der geänderten Datensätze.

Du musst deinen Code Debuggen dann bekommst Du auch eine Fehlermeldung (Exception Settings: Common Runtime Exceptions -> haken setzen!). ALternativ kannst Du auch ein try & catch mit einer COnsolenausgabe machen.

2.207 Beiträge seit 2011
vor 7 Jahren

Blöde Frage: Du hast die Db schon vorher erstellt, oder?

Schnell ausprobiert, keine Garantie auf Richtigkeit

static void Main(string[] args)
        {
            try
            {
                new Program().Run();
            }
            catch (Exception exception)
            {
                //...
            }

            Console.ReadLine();
        }

        public void Run()
        {
             string databaseName = "MyTestDatabase.sqlite";
            string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
            string databasePath = Path.Combine(baseDirectory, databaseName);
            SQLiteConnection.CreateFile(databasePath);

            Console.WriteLine("File created at {0}", databasePath);

            string connectionString = $"Data Source={databaseName};Version=3;";

            using (SQLiteConnection connection = new SQLiteConnection(connectionString))
            {
                connection.Open();
                string createSqlStatement = "CREATE TABLE Konten (KontoName VARCHAR(20), Saldo INT)";
                ExecuteNonQuery(createSqlStatement, connection);

                string insertSqlStatement = "INSERT INTO Konten (KontoName, Saldo) values ('Girokonto', 1000)";
                ExecuteNonQuery(insertSqlStatement, connection);

                ReadAllValues(connection);

                string updateSqlStatement = "UPDATE Konten SET Saldo = 2000 WHERE KontoName = 'Girokonto'";
                ExecuteNonQuery(updateSqlStatement, connection);

                ReadAllValues(connection);
            }
        }

        public int ExecuteNonQuery(string sqlText, SQLiteConnection connection)
        {
            using (SQLiteCommand command = new SQLiteCommand(sqlText, connection))
            {
                return command.ExecuteNonQuery();
            }
        }

        public void ReadAllValues(SQLiteConnection connection)
        {
            string sql = "SELECT * from Konten";

            using (SQLiteCommand command = new SQLiteCommand(sql, connection))
            {
                SQLiteDataReader reader = command.ExecuteReader();
                while (reader.Read())
                {
                    Console.WriteLine("Name: " + reader["KontoName"] + "Saldo: " + reader["Saldo"]);
                }
            }
        }

Funktioniert bei mir einwandfrei...

File created at c:\...\bin\Debug\MyTestDatabase.sqlite
Name: GirokontoSaldo: 1000
Name: GirokontoSaldo: 2000

Gruss

Coffeebean

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

Blöde Frage: Du hast die Db schon vorher erstellt, oder?

Ja, habe ich 😉

ich konnte deinen Code jetzt noch nicht testen - bin gerade erst heim gekommen. ABER: ich habe mal meinen Code, hm, "zurückgebaut" und festgestellt, dass u. a. folgender Codeblock Probleme macht, und zwar:

public object LoadDataGridView(string tableName)
        {
            try
            {
                //Connection erstellen --> der connectString gibt dabei den Pfad an.
                SQLiteConnection myConnection = new SQLiteConnection(connectStringPath);

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

                //Einen Befehls-String erstellen, der den gewünschten SQLite-Befehl enthält (i.d.F.: SELECT *)
                string myCommandString = "SELECT * FROM @tableName";

                //Die Variable @tableName mittels SQLite-Befehl an einen SQLite-Parameter übergeben
                SQLiteCommand myCommand = new SQLiteCommand(myCommandString, myConnection);
                myCommand.Parameters.AddWithValue("@tableName", tableName);

                myCommand.ExecuteNonQuery();
               
                //DataAdapter erzeugen, der die Daten für das DataGridView "adaptiert" und einen DataTable der die Adapterdaten entgegennimmt
                SQLiteDataAdapter myAdapter = new SQLiteDataAdapter(myCommandString, myConnection);
                DataTable myTable = new DataTable();

                //Table füllen, Verbindung schließen und das DataTAble-Object mit Hilfe der object-Variable an die aufrufende Methode zurückgeben
                myAdapter.Fill(myTable);
                myConnection.Close();

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

Das ist Code, der obigem Code zuspielt - die Problemstelle ist auch vollkommen klar, sie liegt hier:

myCommand.Parameters.AddWithValue("@tableName", tableName);

                myCommand.ExecuteNonQuery();
               

Wenn ich nämlich diese Zeilen auskommentiere und statt dem @tableName das Ganze so mache,

string myCommandString = "SELECT * FROM '" + tableName + "';

dann funktioniert es. Ich wollte mit @tableName eigenlich SQL-Injections vorbeugen. Ich hatte ähnlichen Code schon mal im Projekt verwendet, und da funktioniert es so:


SQLiteConnection myConnection = new SQLiteConnection(myConnectionString);

//Verbindung öffnen
myConnection.Open();

//String mit Variablen
string comString = "INSERT INTO Konten (id, Name, Nummer) VALUES (@id, @Name, @Nummer);

SQLiteCommand myCommand = new SQLiteCommand(comString, myConnection);

//Die Variable @tableName mittels SQLite-Befehl an einen SQLite-Parameter übergeben
myCommand.Parameters.AddWithValue("@id", id);
myCommand.Parameters.AddWithValue("@Name", Name);
myCommand.Parameters.AddWithValue("@Nummer", Nummer);

//Ausführen
myCommand.ExecuteNonQuery();

myConnection.Close();

Wie gesagt, die Chose funktioniert. ABER: ich vermute mal stark, dass ein konzeptionelles Missverständnis vorliegt - womöglich seht ihr das an meinen Kommentaren. Nach meinem Verständnis hätte es funktionieren müssen; ich weiß jetzt wie es funktioniert (s. CodeBlocks 3+4) bin aber unglücklich von dem @ abrücken zu müssen, weil man allenthalben liest, dass man so effizient Injections verhindert 😦 Sorry, wenn das sehr noobish rüwwerkommt, aber man kann sich noch so viel einlesen: vor Fehlannahmen ist man nicht gefeit. Vielleicht könnt ihr mir aber sagen, wie ich es hätte machen sollen damit es funktioniert. Meine Lösung kann nicht der einzige (richtige?) Wes sein, oder?

Danke für eure Inputs - ich werde alles sorgfältig prüfen, bzw. bin mitten im Debugging!

B
110 Beiträge seit 2008
vor 7 Jahren

Da liegt wirklich ein Missverständnis vor: Tabellen- und Feldnamen einerseits, Feldwerte andererseits sind zwei Paar Stiefel und können nicht gleich behandelt werden. Tabellen- und Feldnamen sind im Normalfall ja Konstanten und können einfach in den SQL-Text geschrieben werden. Sind es doch Variable, hilft nur erst ordentlich überprüfen (falls vom Benutzer gekommen) und dann in den Text einbauen. Bei letzterem würde ich eher zu string.format("xxx {0}", variable) tendieren, aber das geht jetzt in die Geschmacksfragen.

Mit SQL-Parametern kann und soll man Feldwerte übergeben, sonst nichts. In Deinem funktionierenden Beispiel wird ja auch genau so verfahren.

Hinweis von Abt vor 7 Jahren

Nochmals: bitte keine Full Quotes!
[Hinweis] Wie poste ich richtig?

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

Danke für deine Antwort. OK, ich will jetzt nicht die Geschmacksfrage stellen, muss dennoch kurz darauf zu sprechen kommen:

In Deinem funktionierenden Beispiel wird ja auch genau so verfahren.
das vielleicht schon, aber die ursprüngliche Absicht mich vor SQL-Injections abzusichern - die bleibt bei diesem Vorgehen ja außen vor. Wie würde denn ein kurzer Schnippsel Code aussehen, der das berücksichtigt?

16.807 Beiträge seit 2008
vor 7 Jahren

SqlParameter sind - wie der Name sagt - für die Parameter, also die Werte da; nicht für Bezeichnungen des Schemas, wie Spalten- oder Tabellennamen.
Bei Table Names sollte empfiehlt es sich diese über Whitelists verfügbar zu machen; nicht vollständig variabel!

Alternativ könntest Du es über den SqlCommandBuilder und QuoteIdentifier machen.

string safeTableName = sqlCmdBuilder.QuoteIdentifier(tableName);
var query = $"SELECT * FROM {safeTableName}"

QuoteIdentifier ist für Felder und den Tabellennamen da, sodass diese Werte dynamisch sicher sind.

2.207 Beiträge seit 2011
vor 7 Jahren

Hallo GeneVorph,

Wie würde denn ein kurzer Schnippsel Code aussehen, der das berücksichtigt?

meinst du sowas:

command.Parameters.AddWithValue("@Saldo", 1000);

?

Gruss

Coffeebean

5.299 Beiträge seit 2008
vor 7 Jahren

... ich bin Hobby-Coder und habe gerade erst die ersten Gehversuche hinter mich gebracht.... Dann würde ich erstmal ganz pauschal von Anwendungen mit Datenbank-Zugriffen abraten.

Dahinter stecken jede Menge Grundlagen.
Hinzu kommen jede Menge Tücken.
Und überhaupt muss man dafür die abstrakten Konzepte eines relationalen Datenbank-Modells verstehen und befolgen.
Hinzu kommen Pattern, die ebenfalls zu befolgen sind - etwa dass man im Client ebenfalls ein Datenmodell unterhalten sollte, also wer ein DatagridView aus einer Datenbank befüllt steht schon mit beiden Beinen inne Hölle, denn man sollte immer sein Datenmodell befüllen, keine Controls!

Bei Interesse an Tutorials from Scratch sag bescheid - sind allerdings am Beispiel vb erläutert.

Der frühe Apfel fängt den Wurm.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 7 Jahren
[gelÖst]

Kurze Rückmeldung: ich konnte das Problem lösen! Vielen Dank an alle Helfer!

Das Problem war - wie hier schon richtig vermutet - das Warten auf ein Ereignis, das schlicht nicht eintreten konnte. Zwei Fehler gingen dem Voraus: meine Unerfahrenheit (vergessen eine Connection zu schließen) und dazwischen eine Methode, die auf einen Zugriff auf die Datenbank wartete, der aber so nicht eintreten konnte.

Ich habe zwar jetzt viel Zeit ins Debugging investiert, aber es hat sich sehr gelohnt. Aus Fehlern lernt man tatsächlich 😃

Dankeschön @ all!