PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Eine Datenbank-Klasse in C# - wie geht's am besten?


Expandable
2005-07-27, 01:11:55
Hallo,

wie Ihr ja vielleicht durch meinen letzten Thread noch wisst, beginne ich gerade, mich etwas in C# einzuarbeiten.
Ich bin gerade dabei für mich persönlich ein kleines Kundenverwaltungs-Programm zu schreiben. Folgendes Problem: Es basiert auf MySQL als Datenbank. Ich hätte gerne alle MySQL-Aufrufe in einer Klasse um den Rest des Codes von der Datenbank zu isolieren. Die Klasse sieht bislang so aus (ist noch nicht fertig):


using MySql.Data.MySqlClient;
using System.Windows.Forms;
using System;

namespace ClientManagement
{
class MyDatabase
{
private static MyDatabase myObject = null;

private MySqlConnection dbConn;
private MySqlCommand tmpQuery;
private MySqlDataReader tmpQueryRead = null;

// Implements SingleTon design pattern
public static MyDatabase GetObj()
{
if (myObject == null)
myObject = new MyDatabase();

return myObject;
}

public MyDatabase()
{
string strConnection = "Data Source=localhost;Database=kundenverwaltung;User ID=root;Password=";
dbConn = new MySqlConnection(strConnection);

try
{
dbConn.Open();
}
catch (Exception)
{
MessageBox.Show("Verbindung zur Datenbank fehlgeschlagen!", "Fehler!");
}
}

public void PrepareQuery(string queryStr)
{
if (tmpQueryRead != null)
tmpQueryRead.Close();
tmpQuery = new MySqlCommand(queryStr, dbConn);
}

public void QueryWithoutResults()
{
tmpQuery.ExecuteNonQuery();
}

public MySqlDataReader GetQueryResults()
{
tmpQueryRead = tmpQuery.ExecuteReader();

return tmpQueryRead;
}
}
}


Wenn ich jetzt also Daten aus einer Tabelle laden will, sieht der dazugehörige Code so aus:


MyDatabase.GetObj().PrepareQuery("SELECT " + fields +
" FROM clients " +
" WHERE ID='" + id + "'");
MySqlDataReader tmpInfo = MyDatabase.GetObj().GetQueryResults();

while (tmpInfo.Read())
{
Address = tmpInfo.GetString(0);
Contact = tmpInfo.GetString(1);
ContactMail = tmpInfo.GetString(2);
}


Das ist natürlich extremst unschön, dass ich den Objekt-Typ "MySqlDataReader" hier notieren muss um dann umständlich über eine While-Schleife die Daten zu erhalten. Ginge das noch irgendwie einfacher?

Das Problem existiert ja nicht, wenn Daten nur gelösch/eingefügt/aktualisiert werden sollen, wie z.B. hier:


try
{
MyDatabase.GetObj().PrepareQuery("UPDATE clients " +
"SET organisation='" + organisation + "', " +
"address='" + address + "', " +
"contact='" + contact + "', " +
"contactMail='" + contactMail + "' " +
"WHERE ID='" + id + "'");
MyDatabase.GetObj().QueryWithoutResults();
}
catch (Exception e)
{
MessageBox.Show("Die eingegebenen Daten konnten leider nicht gespeichert werden!\n\n" + e.ToString(), "Fehler");
}


Dieser Code wird von der Datenbank-Klasse komplett versteckt, so hätte ich das eben auch gerne beim Auslesen von Daten.

Irgendwelche Ideen?

Die Frage ist ja auch: Ist es sinnvoll, so wie ich's jetzt lösen musste, vor jedem neuen Query (also Aufruf der Methode PrepareQuery()) die Funktion tmpQueryRead.Close(); aufzurufen? Leider kann ich nicht gleich in der Funktion GetQueryResults() diesen Aufruf machen, das wäre wohl eleganter.

Außerdem ist die Frage: Wann soll ich die Datenbank-Verbindung wieder schließen? Das geht ja überhaupt nicht, oder? In dem Fall ist es kein großes Problem, da die Datenbank nur von mir als einzigem Benutzer beansprucht wird, aber falls ich mal größere Anwendungen schreibe, würde ich die Verbindung gerne schnellstmöglich wieder schließen. Aber das müsste natürlich auch innerhalb der Datenbank-Klasse geschehen, ohne irgendwelche Aufrufe "von außen". Würde mich auch hierzu über Ideen freuen!

PS: Bitte zereißt mich nicht falls die Fragen irgendwie lächerlich sind. Wie gesagt, bin erst seit kurzem dabei in C# und der Windows-Programmierung!

DANKE!

grakaman
2005-07-27, 08:42:22
Außerdem ist die Frage: Wann soll ich die Datenbank-Verbindung wieder schließen?


Sofort nachdem du sie nicht mehr brauchst. Ich weiß jetzt allerdings nicht, wie das mit dem MySql .NET Provider aussieht, aber beim Sql Server Provider wird automatisch connection pooling genommen (kann man noch genauer über Connection String spezifizieren), deswegen sollte man die Connection mit Close() auch sofort wieder in den Pool zurücklegen, wenn sie nicht mehr benötigt wird.


...würde ich die Verbindung gerne schnellstmöglich wieder schließen.


wäre sinnvoll


Aber das müsste natürlich auch innerhalb der Datenbank-Klasse geschehen, ohne irgendwelche Aufrufe "von außen". Würde mich auch hierzu über Ideen freuen!


Du kannst z.B. beim Sql .NET Server Provider der ExecuteReader Methode die Enumeration CommandBehavior.CloseConnection übergeben. Wenn du dann den Reader nach Benutzung schließt, wird auch implizit das Connection Objekt geschlossen. Müsste eigentlich beim MySql Provider genau so sein.

Im Prinzip würd ich mit dem DataAdapter und DataSets arbeiten.
Implizit arbeiten zwar die auch alle mit dem DataReader, aber der DataAdapter übernimmt das DataBinding ans DataSet automatisch. Außerdem öffnet/schließt der DataAdapter auch automatisch implizit die Connection, wenn du ihm ein Command Objekt übergibst.
Da kannst du entweder das generische DataSet nehmen oder ein typisiertes. Beim typisierten wird quasi eine eigene Klasse mit der DataSet Klasse als Basisklasse genommen. Der Vorteil, du kannst das typisierte DataSet benutzerfreundlicher ansprechen und die Eigenschaften greifen auf die Ordinalnummern der Spalten zu. Das kannst du recht leicht per WYSIWYG in VS.NET erstellen oder direkt mit xsd.exe im Framework (musst dann aber logischerweise das Shema von Hand erstellen). Oder du nimmst eben bloß das generische DataSet, ist aber nicht so elegant anzusprechen und wenn du dann auf die Spalten zugreifst und dort den Spaltennamen übergibst ist das nicht so schnell, als wenn du über die Ordinalnummer zugreifst. Da bietet das typisierte DataSet eine gute Kombination aus beidem.

edit:

Achja, auf jeden Fall solltest du für deine Parameter im Sql Statement Parameter Objekte verwenden, denn so kann man ja bekannterweise recht leicht Sql Injections machen.

Expandable
2005-07-28, 16:32:19
Erstmal danke für Deine Hilfe!!

Ich weiß zwar nicht, ob das sinnvoll war, aber jetzt habe ich mir eine eigene Klasse geschrieben, die die Daten aus dem Reader auslist und selbst speichert. Mit der Klasse kann ich dann "vernünftig" arbeiten und kann die Datenbank komplett verstecken.

Bzgl. der SQL Injections: Bei der Anwendung wär das nicht das Problem, da nur ich damit arbeite ;) Aber wie funktioniert das generell, als Parameter Objekte zu verwenden? Ich muss da noch 'nen String reinschreiben??

Danke!

grakaman
2005-07-28, 22:04:25
Ich weiß zwar nicht, ob das sinnvoll war, aber jetzt habe ich mir eine eigene Klasse geschrieben, die die Daten aus dem Reader auslist und selbst speichert. Mit der Klasse kann ich dann "vernünftig" arbeiten und kann die Datenbank komplett verstecken.


Wie schon gesagt, schau dir mal den DataAdapter und DataSets an.


Bzgl. der SQL Injections: Bei der Anwendung wär das nicht das Problem, da nur ich damit arbeite ;) Aber wie funktioniert das generell, als Parameter Objekte zu verwenden? Ich muss da noch 'nen String reinschreiben??


Das Problem ist, wenn du das ohne Parameter machst, könnte jemand in den Parametern Sql Statements einschleusen und alles vorhergehende z.B. auskommentieren. Das geht aber nur, weil so das Statement auf dem Datenbankserver neu geparst wird und ein neuer Ausführungsplan erstellt wird.
Wenn du aber Parameter verwendest, werden die Daten in den Parameter nachträglich in den Ausführungsplan eingefügt. Zum Bsp.:


command.CommandText = "SELECT * FROM Table WHERE ID = @ID";
SqlParameter parameter = new SqlParameter("@ID", SqlDbType.Int, 4);
parameter.Value = id;
command.Parameters.Add(parameter);


Bei Sprocs kannst du dann z.B. auch noch angeben, ob es ein Output Parameter ist etc.

Expandable
2005-07-30, 18:00:22
Danke, hab das jetzt mal mit dem DataSet realisiert. Ist doch irgendwie besser als meine selbstgeschriebene Lösung - obwohl die (bis auf die genaue Syntax) genau so funktioniert hat ;)

Bzgl. der Command Parameter: Wenn ich z.B. "große Updates" mache, also sagen wir 20 Spalten und 200 Zeilen aktualisieren, müsste sich das doch relativ negativ auf die Performance auswirken (verglichen mit der "direct-string" Methode)???

grakaman
2005-07-30, 18:21:10
Bzgl. der Command Parameter: Wenn ich z.B. "große Updates" mache, also sagen wir 20 Spalten und 200 Zeilen aktualisieren, müsste sich das doch relativ negativ auf die Performance auswirken (verglichen mit der "direct-string" Methode)???

Wie meinen???

Expandable
2005-07-30, 23:06:45
Das da


command.CommandText = "SELECT * FROM Table WHERE ID = @ID";
SqlParameter parameter = new SqlParameter("@ID", SqlDbType.Int, 4);
parameter.Value = id;
command.Parameters.Add(parameter);


verglichen mit dem da

command.CommandText = "SELECT * FROM Table WHERE ID='" + id + "'";


wenn ich viele Updates mit vielen Spalten (also Parametern) mache, dann müsste die "Sql Injection"-sichere Methode doch merkbar langsamer sein, oder nicht? Ich weiß nicht, ich komme aus der PHP-Entwicklung, und da hieß es immer: So wenig Objekte wie möglich, denn die Objekt-Initialisierung drückt die Performance enorm ;) Ist das in C# nicht so schlimm?

grakaman
2005-07-30, 23:11:57
wenn ich viele Updates mit vielen Spalten (also Parametern) mache, dann müsste die "Sql Injection"-sichere Methode doch merkbar langsamer sein, oder nicht? Ich weiß nicht, ich komme aus der PHP-Entwicklung, und da hieß es immer: So wenig Objekte wie möglich, denn die Objekt-Initialisierung drückt die Performance enorm ;) Ist das in C# nicht so schlimm?

Das ist ziemlicher Unsinn.

Expandable
2005-07-31, 01:02:02
Also ist es nur umständlicher zu schreiben, aber gleich performant? Gut zu wissen...

grakaman
2005-07-31, 16:37:56
Also ist es nur umständlicher zu schreiben, aber gleich performant? Gut zu wissen...

Um es kurz zu machen: richtig