Autor |
Beitrag |
Bergmann89
      
Beiträge: 1742
Erhaltene Danke: 72
Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
|
Verfasst: Fr 24.02.12 19:18
Hey,
ich hab gestern damit begonnen eine lokale Datenbank in das Projekt zu integerieren, das ich gerade entwickel. Leider musste ich feststellen, das die Datenbank mehr als langsam ist. Ich brauch für 30k Datensätze (verteilt auf mehrere Tabellen) knapp 5min. Die Zeit die er dabei für ein Objekt benötigt steigt mit der größe der Datenbank. Wenn ich das ganze einfach über einen Stream auf die Festplatte auslager, dann dauert das ca. 1 sec. Ist SQL Compact da so schwach oder hab ich vlt. was falsch gemacht?! Hier ma noch n bisl Code:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97:
| public static class TQuestions { public static string TABLE_NAME = "Questions", ID = "ID", C_ID = "C_ID", SUBJECT = "Subject", TYPE = "Type", TEXT = "Text"; }
if (!Directory.Exists(Path.GetDirectoryName(Filename))) Directory.CreateDirectory(Path.GetDirectoryName(Filename));
using (SqlCeEngine engine = new SqlCeEngine()) { engine.LocalConnectionString = "Data Source='" + Filename + "'; LCID=1033;" + "Password='" + DB_PASSWORD + "'; Encrypt = TRUE; Case Sensitive = TRUE;"; engine.CreateDatabase(); }
tableExists = TableExists(connection, TQuestions.TABLE_NAME); if (DropIfExists && tableExists) DropTable(connection, TQuestions.TABLE_NAME); if (!tableExists || DropIfExists) { cmd.CommandText = "CREATE TABLE " + TQuestions.TABLE_NAME + " (" + TQuestions.ID + " INT NOT NULL PRIMARY KEY IDENTITY, " + TQuestions.C_ID + " INT, " + TQuestions.SUBJECT + " INT, " + TQuestions.TYPE + " INT NOT NULL, " + TQuestions.TEXT + " NVARCHAR (2000) NOT NULL)"; cmd.ExecuteNonQuery(); } if (_owner == null || _owner.ID <= 0) throw new Exception("CQuestion.SaveToDatabase - invalid owner or owner id!"); _comment.SaveToDatabase(connection); SqlCeCommand cmd = connection.CreateCommand();
bool insert = true; if (_ID > 0) { cmd.CommandText = "UPDATE " + TQuestions.TABLE_NAME + " SET " + TQuestions.C_ID + " = " + (_comment.ID > 0 ? _comment.ID.ToString() : "NULL") + ", " + TQuestions.SUBJECT + " = " + _subjectID + ", " + TQuestions.TYPE + " = " + ((int)_type) + ", " + TQuestions.TEXT + " = '" + _text + "' " + "WHERE " + TQuestions.ID + " = " + _ID; insert = (cmd.ExecuteNonQuery() == 0); }
if (insert) { cmd.CommandText = "INSERT INTO " + TQuestions.TABLE_NAME + " (" + TQuestions.C_ID + ", " + TQuestions.SUBJECT + ", " + TQuestions.TYPE + ", " + TQuestions.TEXT + ") " + "VALUES (" + (_comment.ID > 0 ? _comment.ID.ToString() : "NULL") + ", " + _subjectID + ", " + ((int)_type) + ", " + "'" + _text + "')"; cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT * FROM " + TQuestions.TABLE_NAME + " WHERE " + TQuestions.ID + " = @@IDENTITY;"; using (SqlCeDataReader reader = cmd.ExecuteReader()) { if (reader.Read()) _ID = (int)reader[TQuestions.ID]; } }
cmd.CommandText = "SELECT COUNT(*) FROM " + TCorrPagesQuestions.TABLE_NAME + " WHERE " + TCorrPagesQuestions.P_ID + " = " + _owner.ID + " AND " + TCorrPagesQuestions.Q_ID + " = " + _ID; using (SqlCeDataReader reader = cmd.ExecuteReader()) { insert = true; if (reader.Read()) insert = ((int)reader[0] == 0); } if (insert) { cmd.CommandText = "INSERT INTO " + TCorrPagesQuestions.TABLE_NAME + " (" + TCorrPagesQuestions.P_ID + ", " + TCorrPagesQuestions.Q_ID + ") " + "VALUES (" + _owner.ID + ", " + _ID + ")"; if (cmd.ExecuteNonQuery() <= 0) throw new Exception("CQuestion.SaveToDatabase - failed to insert values to table " + TCorrPagesQuestions.TABLE_NAME); }
_answers.SaveToDatabase(connection); _statements.SaveToDatabase(connection); |
MfG & Thx Bergmann.
_________________ Ich weiß nicht viel, lern aber dafür umso schneller^^
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Fr 24.02.12 19:42
Das bestimmen der Identity nach einem Insert ist ein wenig krudde oder ich habe etwas übersehen
Muster sollte eher sein
SQL-Anweisung 1: 2: 3: 4: 5:
| command.CommandText = @"INSERT INTO MeinTabelle(MeinFeld) VALUES(MeineDaten); SELECT @Id = @@IDENTITY;" SqlCeParameter param = command.Parameters.Add("@Id", SqlDbType.Int); param.Direction = ParameterDirection.Output; command.ExecuteNonQuery(); _ID = param.value; |
Typbezeichnungen für CE sind geraten mußt du wahrscheinlich noch anpassen.
Das Konzept mit gewürfelten Tabellenamen und daraus folgendem dynamischen Sql ist auch unglücklich. Wenn irgend möglich benutze fixe Tabellenbezeichnung und echte Parameter für Werte. Dynamisches Sql ist immer problematisch und langsam oft dazu.
|
|
Bergmann89 
      
Beiträge: 1742
Erhaltene Danke: 72
Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
|
Verfasst: Fr 24.02.12 20:09
Hey,
das mit der ID hab ich so gemacht, weil ich gestern in irgendeinem Topic gelesen hab, das man bei CE kein INSERT und SELECT zusammen packen kann. Da wurde das aber auch ohne den Parameter gemacht. Ich probier das dann mal aus und guck ob das so funktioniert.
Wie meinst du das mit dynamischem SQL?! Die Tabellenbezeichnungen und -werte sind doch fest Werte. Ich hab mir die nur zur Übersichtlichkeit als Konstanten in einer Klasse pro Tabelle abgelegt. Und dann bau ich nur noch den String für den Command zusammen. So kann ich die Werte global für alle Befehle ändern und muss nich jeden Command neu schreiben, wenn ich was ändern will.
MfG & Thx Bergmann.
_________________ Ich weiß nicht viel, lern aber dafür umso schneller^^
|
|
Th69
      

Beiträge: 4799
Erhaltene Danke: 1059
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: Fr 24.02.12 20:28
Hallo Bergmann89,
mit "dynamisch" meint Ralf das Zusammensetzen des SQL Befehls mittels String-Verkettung. Besser sind hier die erwähnten SQL-Parameter, da dann die DB (bzw. der DB-Treiber) besser cachen kann und einen vorberechneten Ausführungsplan benutzen kann (bei SQL Compact bin ich mir jedoch nicht sicher, ob dem wirklich so 100-prozentig ist).
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Fr 24.02.12 20:40
Zitat: | das man bei CE kein INSERT und SELECT zusammen packen kann |
Möglicherweise einer der wenigen syntaktischen Unterschiede zwischen CE und dem ausgewachsenen SqlServer.
Dann müsstest du 2 Executes machen. Halte ich immer noch für zumindest übersichtlicher als deine Methode mit einem DataReader.
SQL-Anweisung 1: 2: 3: 4:
| command.CommandText = @"INSERT INTO MeinTabelle(MeinFeld) VALUES (MeineDaten)" command.ExecuteNonQuery(); command.CommandText = "SELECT @@IDENTITY" _ID = Convert.ToInt32(command.ExecuteScalar()); |
Zitat: | (bei SQL Compact bin ich mir jedoch nicht sicher, ob dem wirklich so 100-prozentig ist). |
Si. Bei einer Embedded Datenbank möglicherweise kein ausschlaggebender Faktor. Aber man bewegt sich dann zumindest im Rahmen von Best Practises die vielleicht nicht groß helfen aber bestimmt auch nicht schaden.
|
|
Bergmann89 
      
Beiträge: 1742
Erhaltene Danke: 72
Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
|
Verfasst: Mo 27.02.12 19:55
Hey,
ich hab heut mein Projekt nach euren Vorschlägen umgebaut. Bei den SQL-Commands hab ich jetzt die Parameter eingebaut. Außerdem benutz ich jetzt DataSet, da sich das etwas einfacher verwalten lässt. Das ganze sieht jetzt ungefähr so aus:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
| SqlCeCommand cmd = connection.CreateCommand(); cmd.CommandText = "SELECT * FROM QuestionIndex WHERE QuestionIndex.ID = @ID"; cmd.Parameters.Add("ID", _ID); using (SqlCeDataAdapter adp = new SqlCeDataAdapter(cmd)) { SqlCeCommandBuilder cmdBuilder = new SqlCeCommandBuilder(adp); adp.DeleteCommand = cmdBuilder.GetDeleteCommand(); adp.UpdateCommand = cmdBuilder.GetUpdateCommand(); adp.InsertCommand = cmdBuilder.GetInsertCommand();
DataSet data = new DataSet(); adp.Fill(data);
DataTable table = data.Tables[0]; if (table == null) throw new Exception("CQuestionIndex.SaveToDatabase - table not available: QuestionIndex");
DataRow row; if (table.Rows.Count > 0) row = table.Rows[0]; else { row = table.NewRow(); table.Rows.Add(row); }
row["Date"] = _date.Ticks; row["Type"] = (int)_type; adp.Update(data);
_pages.SaveToDatabase(connection); } |
Jetzt hab ich aber noch folgendes Problem. Und zwar kann ich im DataSet nicht nach dem Tabellennamen suchen. Die Tabelle wird immer "Table" genannt. Ich kann nur über die ID der Collection zugreifen (siehe Z15). Ich brauch aber eine Möglichkeit die Tabelle anhand ihres Namens zu bestimmen. Ich bin mir relativ sicher, das der Name bei SQL Server 2008 mit ausgelesen wurde. Kann es sein, das es bei SQL Compact nicht unterstützt wird?
MfG & Thx Bergmann.
_________________ Ich weiß nicht viel, lern aber dafür umso schneller^^
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Mo 27.02.12 20:45
Zitat: | Kann es sein, das es bei SQL Compact nicht unterstützt wird? |
Das ist das Standardverhalten eines DbDataAdapters. Wenn die nicht überschrieben wird heißt die Tabelle "Table". Weder der SqlDataAdapter noch der SqlCeDataAdapter überschreiben Fill. Aber wieso benutzt du überhaupt ein DataSet? Dein Code sieht für mich so aus als würde eine DataTable reichen. Da brauchts dann auch keinen Namen.
Ansonsten kannst du beim Fill auch einfach deinen gewünschten Namen mitgeben. Für den gezeigten Code empfinde ich selbst den SqlCeDataAdapter überzogen. Da würde ich glaube ich eher einfach das entsprechende SqlCeCommand direkt feuern.
|
|
Bergmann89 
      
Beiträge: 1742
Erhaltene Danke: 72
Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
|
Verfasst: Di 28.02.12 06:20
Hey,
für den Code wie er da ist brauch ich das nicht, da hast du Recht. Aber ich will das ganze noch etwas umbauen. Und zwar will ich der Laderoutine dann nur ein DataSet übergeben, wo das Objekt seine Daten einträgt. So kann ich mehrere Objekte oder ggf. den kompletten Objektbaum erstmal in dem DataSet ablegen und dann in einem Schwung in die Datenbank schieben. Und wenn die Routine nur ein DataSet bekommt, dann muss diese die Tabelle anhand ihres Names aus dem Set auslesen können.
€: Ich hab's jetzt doch nur mit Parametern und ohne das DataSet gemacht. Passt doch irgendwie besser ins Konzept. Jetzt braucht er für alle Datensätze ca. 5sec, also eine deutliche Verbesserung. Nochma Danke für die Hilfe.
MfG Bergmann.
_________________ Ich weiß nicht viel, lern aber dafür umso schneller^^
|
|
|