Beschreibung:
Ich arbeite in meinen Projekten viel mit ADO.NET. Allerdings finde ich, dass man bei ADO.NET viel zu viel Code hinschreiben muss. Das ist bei Anwendungen, die ständig irgendwelche Datenbankabfragen ausführen lästig.
Deshalb habe ich mir eine Klasse geschrieben, die häufig benötigte Datenbankoperationen kapselt. Im Zeichen von ADO.NET 2.0 natürlich providerneutral. Deshalb kann meine Database-Klasse in verbindung mit allen Datenbanksystemen verwendet werden, für die ein Managed Provider zur Verfügung steht.
Die Anwendung ist sehr einfach. Alle Methoden der Database-Klasse sind statisch. Folgende Methoden sind vorhanden:*Query (Führt eine SQL SELECT-Anweisung aus und gibt eine DataTable zurück) *Update (Überträgt Änderungen an einer DataTable auf die Datenbank) *CreateParameter (Erzeugt einen Parameter für parametrisierte Abfragen) *GetTableNameFromQuery (Ermittelt den Tabellennamen aus einer SELECT-Anweisung)
Beispiel für die Verwendung der Query-Methode:
// using System.Data.Common; NICHT VERGESSEN!
// Anbieter-Fabrik für SQL Server erzeugen
DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
// Verbindungszeichenfolge
string connectionString="Data Source=SERVER;Inital Catalog=Test;Integrated Security=true";
// Parameterliste erstellen
IList<DbParameter> parameters=new List<DbParameter>;
parameters.Add(Database.CreateParameter(factory,"@categoryID",DbType.Int32,ParameterDirection.Input,32));
parameters.Add(Database.CreateParameter(factory,"@discontinued",DbType.Boolean,ParameterDirection.Input,false));
// Parametrisierte Abfrage ausführen
DataTable result=Database.Query(factory,connectionString,"SELECT * FROM Articles WHERE CategoryID=@categoryID AND Discontinued=@discontinued",parameters);
Beispiel für die Verwendung der Update-Methode:
// using System.Data.Common; NICHT VERGESSEN!
// Anbieter-Fabrik für SQL Server erzeugen
DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
// Verbindungszeichenfolge
string connectionString="Data Source=SERVER;Inital Catalog=Test;Integrated Security=true";
// Änderungen an der DataTable in die Datenbank schreiben
Database.Update(factory, connectionString, result);
Transaktionen sind absichtlich nicht integriert, da ich es für sinnvoller halte, Transaktionen mit System.Transactions zu machen. Hier ein Beispiel eines Updates von zwei Tabellen unter Verwendung einer System.Transactions-Transaktion:
// using System.Data.Common; NICHT VERGESSEN!
// using System.Transactions; NICHT VERGESSEN!
// Anbieter-Fabrik für SQL Server erzeugen
DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
// Verbindungszeichenfolge
string connectionString="Data Source=SERVER;Inital Catalog=Test;Integrated Security=true";
// Transaktionsklammer setzen
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
// Änderungen an Tabelle 1 übernehmen
Database.Update(factory, connectionString, table1);
// Änderungen an Tabelle 1 übernehmen
Database.Update(factory, connectionString, table2);
// Transaktion abschließen (commit)
scope.Complete();
}
**
Hier ist der komplette Quellcode, der Datenzugriffsklasse:**
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
namespace Rainbird.Tools.DataAccess
{
/// <summary>
/// Implementiert einfachen Datenzugriff auf Relationale Datenbanken.
/// </summary>
public class Database
{
/// <summary>
/// Speichert Änderungen an einer Tabelle in der Datenbank.
/// </summary>
/// <param name="factory">Fabrik des Datenanbieters</param>
/// <param name="connectionString">Verbindungszeichenfolge</param>
/// <param name="table">Tabelle mit Änderungen</param>
public static void Update(DbProviderFactory factory, string connectionString, DataTable table)
{
// Datenbankverbindung erzeugen
using (DbConnection connection = factory.CreateConnection())
{
// Verbindungszeichenfolge übernehmen
connection.ConnectionString = connectionString;
// SELECT-Befehl zusammensetzen
StringBuilder statement = new StringBuilder();
statement.Append("SELECT * FROM ");
statement.Append(table.TableName);
statement.Append(" WHERE 0=1");
// Datenbankbefehl erzeugen
DbCommand selectCommand = factory.CreateCommand();
selectCommand.Connection = connection;
selectCommand.CommandType = CommandType.Text;
selectCommand.CommandText = statement.ToString();
// Datenadapter erzuegen
DbDataAdapter adapter = factory.CreateDataAdapter();
adapter.SelectCommand = selectCommand;
adapter.AcceptChangesDuringUpdate = true;
// Befehlsgenerator erzeugen
DbCommandBuilder builder = factory.CreateCommandBuilder();
builder.ConflictOption = ConflictOption.CompareAllSearchableValues;
builder.DataAdapter = adapter;
// Änderungen übernehmen
adapter.Update(table);
}
}
/// <summary>
/// Führt eine Datenbankabfrage aus.
/// </summary>
/// <param name="factory">Fabrik des Datenbanbieters</param>
/// <param name="connectionString">Verbindungszeichenfolge zur Datenbank</param>
/// <param name="query">SQL-SELECT-Abfrage</param>
/// <param name="parameters">Paramaterliste (Optional)</param>
/// <returns>Tabelle mit Abfrageergebnissen</returns>
public static DataTable Query(DbProviderFactory factory, string connectionString, string query, IList<DbParameter> parameters)
{
// Tabellenname ermitteln
string tableName = GetTableNameFromQuery(query);
// Tabelle erzeugen
DataTable table = new DataTable(tableName);
// Datenbankverbindung erzeugen
using (DbConnection connection = factory.CreateConnection())
{
// Verbindungszeichenfolge übernehmen
connection.ConnectionString = connectionString;
// Datenbankbefehl erzeugen
DbCommand command = factory.CreateCommand();
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = query;
// Wenn Parameter angegeben wurden ...
if (parameters != null)
{
// Alle Parameter durchlaufen
foreach (DbParameter parameter in parameters)
{
// Parameter zufügen
command.Parameters.Add(parameter);
}
}
// Datenadapter erzeugen
DbDataAdapter adapter = factory.CreateDataAdapter();
adapter.SelectCommand = command;
// Abfrage ausführen
adapter.Fill(table);
}
// Tabelle zurückgeben
return table;
}
/// <summary>
/// Ermittelt den Tabellennamen für eine DataTable aus einer SQL-Abfrage.
/// </summary>
/// <param name="query">SQL-Abfrage</param>
/// <returns>Tabellenname</returns>
public static string GetTableNameFromQuery(string query)
{
// SQL-Abfrage zerlegen
string[] parts = query.Split(' ');
// Alle Teile durchlaufen
for (int i = 0; i < parts.Length; i++)
{
// Wenn der aktuelle Teil "FROM" ist ...
if (parts[i].ToLower() == "from")
{
// Tabellenname lesen und zurückgeben
return parts[i + 1].Split(',')[0];
}
}
// "Table" zurückgeben
return "Table";
}
/// <summary>
/// Erzeugt einen Parameter für eine Datenbankabfrage.
/// </summary>
/// <param name="factory">Fabrik des Datenbanbieters</param>
/// <param name="name">Name des Parameters</param>
/// <param name="type">Datentyp</param>
/// <param name="size">Größe</param>
/// <param name="direction">Richtung (Eingabe- oder Ausgabeparameter)</param>
/// <param name="value">Wert</param>
/// <returns>Fertig konfigurierter Parameter</returns>
public static DbParameter CreateParameter(DbProviderFactory factory, string name,DbType type,int size,ParameterDirection direction,object value)
{
// Neuen Parameter erzeugen
DbParameter parameter=factory.CreateParameter();
parameter.ParameterName = name;
parameter.DbType = type;
parameter.Size = size;
parameter.Direction = direction;
parameter.Value = value;
// Parameter zurückgeben
return parameter;
}
/// <summary>
/// Erzeugt einen Parameter für eine Datenbankabfrage.
/// </summary>
/// <param name="factory">Fabrik des Datenbanbieters</param>
/// <param name="name">Name des Parameters</param>
/// <param name="type">Datentyp</param>
/// <param name="direction">Richtung (Eingabe- oder Ausgabeparameter)</param>
/// <param name="value">Wert</param>
/// <returns>Fertig konfigurierter Parameter</returns>
public static DbParameter CreateParameter(DbProviderFactory factory, string name, DbType type, ParameterDirection direction, object value)
{
// An andere Überladung delegieren
return CreateParameter(factory, name, type, 0, direction, value);
}
/// <summary>
/// Erzeugt einen Parameter für eine Datenbankabfrage.
/// </summary>
/// <param name="factory">Fabrik des Datenbanbieters</param>
/// <param name="name">Name des Parameters</param>
/// <param name="type">Datentyp</param>
/// <param name="direction">Richtung (Eingabe- oder Ausgabeparameter)</param>
/// <returns>Fertig konfigurierter Parameter</returns>
public static DbParameter CreateParameter(DbProviderFactory factory, string name, DbType type, ParameterDirection direction)
{
// An andere Überladung delegieren
return CreateParameter(factory, name, type, 0, direction, DBNull.Value);
}
/// <summary>
/// Erzeugt einen Parameter für eine Datenbankabfrage.
/// </summary>
/// <param name="factory">Fabrik des Datenbanbieters</param>
/// <param name="name">Name des Parameters</param>
/// <param name="type">Datentyp</param>
/// <param name="size">Größe</param>
/// <param name="direction">Richtung (Eingabe- oder Ausgabeparameter)</param>
/// <returns>Fertig konfigurierter Parameter</returns>
public static DbParameter CreateParameter(DbProviderFactory factory, string name, DbType type, int size, ParameterDirection direction)
{
// An andere Überladung delegieren
return CreateParameter(factory, name, type, size, direction, DBNull.Value);
}
}
}
Außerdem befindet sich im Anhang eine fertige Visual Studio 2005 Projektmappe zum download.
Schlagwörter: ADO.NET, Provider, DataTable, DataSet, DataAdapter, CommandBuilder, Datenzugriff, DAL
Hallo Rainbird,
ich benutze auch so eine Klasse.
Da ich aber nicht zu jedem Servertyp eine Factory erzeugen kann habe ich das gekapselt und eine eigene Enumeration für alle unterstützten Servertypen erstellt.
Wie heisst der DBFactory-Typ von MySQL ? Wie heisst der DBFactory-Typ von SQL Server Compact ?
Nur so eine kleine Idee.
Ausserdem setze ich die SQL-Kommandos in den entsprechenden SQL-Dialekt um (top 10 -> limit 10, getdate -> datepart). Das ist aber ziemlich viel Arbeit 😉
Grüße Bernd
Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3
Hi
nutze ebenfalls eine eigene Datenkomponente dieser Art.
Eine Anregung habe ich ncoh die ich bei mir umgesetzt habe.
Die häufigsten Probleme sind ja das Formatieren von Werten oder das aufrufen von SQL Server spezifischen funktionen.
Deswegen hat bei mir jedes Behavior objekt methoden die diese Werte / SQL Server Methoden einkapseln. z.bsp so :
string whereClause = DefaultDataLayer.EncapsulateField("tablefield",SQLFunctions.ToInteger) + "<" + DefaultDataLayer.FormatValue( 100,customTypes.Integer)
so kann dann später ohne probleme eine weitere Datenbank angehängt werden.
Feedback erwünscht 🙂
Mein Stackoverflow Profil
Skype Name : Boas.Enkler (bitte einen hinweis in der Kontaktanfrage damit ich euch vom Spam unterscheiden kann)
Original von BerndFfm
Wie heisst der DBFactory-Typ von MySQL ? Wie heisst der DBFactory-Typ von SQL Server Compact ?
Über DbProviderFactories.GetFactoryClasses() kann man die installierten Anbieter abfragen. Provider von Drittherstellern sollten sich eigentlich (genau wie die vorinstallierten) in die machine.config eintagen. Dann können sie auch über den dort hinterlegten Namen von der Factory erzeugt werden.
Wenn der Provider alle nötigen Schnittstellen implementiert, sich aber nicht einträgt, kann man das auch von Hand machen. Wenn der Provider nur pro Anwendung konfiguriert werden soll, ist der Eintrag statt in der machine.config in der app.config vorzunehmen.
MySQL und SQL Compact tragen sich weder in die machine.config ein noch erscheinen sie bei DbProviderFactories.GetFactoryClasses().
Da ich die Namen nicht weiss kann ich sie auch nicht manuell eintragen.
Geht aber auch ohne :
DbConnection conn = null;
if (dbprovider == Parameter.DatenbankProvider.SQLServerCompactEdition)
{
conn = new SqlCeConnection();
}
if (dbprovider == Parameter.DatenbankProvider.FirebirdSQLServer)
{
factory = DbProviderFactories.GetFactory("FirebirdSql.Data.FirebirdClient");
conn = factory.CreateConnection();
}
if (dbprovider == Parameter.DatenbankProvider.MySQL)
{
conn = new MySqlConnection();
}
Grüße Bernd
Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3
Hallo,
benutze ebenfalls die ProviderFactory und nutze als DB-Provider Npgsql.Net.
Alles klappt wunderbar, nur mit dem DBCommandBuilder habe ich Probleme:
habe alles identisch wie in deinem SourceCode oben:
// create Database command
DbCommand command = dbconf.ProviderFactory.CreateCommand();
command.Connection = CurrentConnection;
command.CommandType = CommandType.Text;
command.CommandText = query.ToString();
// create Dataadapter
DbDataAdapter adapter = dbconf.ProviderFactory.CreateDataAdapter();
adapter.SelectCommand = command;
adapter.AcceptChangesDuringUpdate = true;
// create Commandbuilder
//DbCommandBuilder builder = dbconf.ProviderFactory.CreateCommandBuilder();
//builder.ConflictOption = ConflictOption.CompareAllSearchableValues;
//builder.DataAdapter = adapter;
Npgsql.NpgsqlCommandBuilder builder = new Npgsql.NpgsqlCommandBuilder();
builder.ConflictOption = ConflictOption.CompareAllSearchableValues;
builder.DataAdapter = adapter as Npgsql.NpgsqlDataAdapter;
// update Database
adapter.Update( a_DataTable );
Ich bekomme aber immer die Fehlermeldung, wenn ich den DbCommandBuilder benutze: "Aktualisieren erfordert einen gültigen InsertCommand, wenn eine DataRow-Auflistung mit neuen Zeilen weitergegeben wird."
Wenn den NpgsqlCommandBuilder benutze, läuft es.
DbCommandBuilder ist auch von Typ NpgsqlCommandBuilder. Könnt ihr mir helfen???
Hallo Rainbird,
wie funktioniert dein Update, im der Variable Statement schreibst du
statement.Append(" WHERE 0=1");
wie kann da die Datenbank die Zuordnung machen, für rows die ein Update haben und keine Insert?
LG
LastGentleman
"Das Problem kennen ist wichtiger, als die Lösung zu finden, denn die genaue Darstellung des Problems führt automatisch zur richtigen Lösung." Albert Einstein
Das macht der CommandBuilder automatisch für mich. Er bezieht die nötigen Informationen aus gesammelten Metadaten und erzeugt entsprechend des RowState jeder einzelnen Zeile der DataTable INSERTs, UPDATEs und DELETES. Schau Dir doch mal die Online-Hilfe bzw. MSDN zum CommandBuilder an. Da steht das alles grottenbreit erklärt.
Das ist doch immer dasselbe. Warum sollte ich das immer alles hinschreiben. Wenn ich eine Tabelle mit 200 Feldern habe, brauche ich ja sonst einen halben Tag, um den Persistenzcode hinzuschreiben (Leicht übertrieben).
Das "WHERE 0=1" kann man auch weglassen. Ist nur so ne Angewohnheit.
Danke Rainbird,
dass hätte ich nicht raus gelesen, aber im nachhinein betrachtet ist das auch logisch.
auszug aus der MSDN
um Generieren von INSERT-Anweisungen, UPDATE-Anweisungen und DELETE-Anweisungen verwendet der SqlCommandBuilder die SelectCommand-Eigenschaft, um einen erforderlichen Satz von Metadaten automatisch abzurufen.
Danke für die Info, damit kann ich mir einige Datenbankzugriffe sparen.
Liebe Grüße aus dem verschneiten Österreich
LastGentleman
"Das Problem kennen ist wichtiger, als die Lösung zu finden, denn die genaue Darstellung des Problems führt automatisch zur richtigen Lösung." Albert Einstein