Laden...

Providerunabhängige Datenzugriffskomponente

Erstellt von Rainbird vor 16 Jahren Letzter Beitrag vor 16 Jahren 13.985 Views
Rainbird Themenstarter:in
3.728 Beiträge seit 2005
vor 16 Jahren
Providerunabhängige Datenzugriffskomponente

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

3.825 Beiträge seit 2006
vor 16 Jahren

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

1.134 Beiträge seit 2004
vor 16 Jahren

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)

Rainbird Themenstarter:in
3.728 Beiträge seit 2005
vor 16 Jahren
Anbieter

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.

3.825 Beiträge seit 2006
vor 16 Jahren

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

F
1 Beiträge seit 2007
vor 16 Jahren

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???

1.274 Beiträge seit 2005
vor 16 Jahren

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

Rainbird Themenstarter:in
3.728 Beiträge seit 2005
vor 16 Jahren
DbCommandBuilder

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.

1.274 Beiträge seit 2005
vor 16 Jahren

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