4

過去数日間、ODBC プロバイダーを介してドット ネットに接続する MySQL、Oracle、Ibmdb2 などのさまざまなデータベースを操作していました。

例えば:

1)MySQL:

Driver={MySQL ODBC 5.1 Driver};server=**********;uid=**;database=**;port=***;pwd=***;"

2)oracle:

Driver={Microsoft ODBC for Oracle};server=**********;uid=**;database=**;port=***;pwd=***;"

3)Db2:

Driver={IBM DB2 ODBC DRIVER};server=**********;uid=**;database=**;port=***;pwd=***;"

今私の質問は

任意のデータベース プロバイダーのジェネリック クラスを次のように記述することは可能ですか?

Driver={My own driver};server=**********;uid=**;database=**;port=***;pwd=***;"

これは、web.config でドライバー名を変更し、その dll ファイルを公開した Web アプリケーションまたは Web サイト プロジェクトの bin フォルダーに配置するだけで、すべてのデータベースを接続します。

4

1 に答える 1

8

独自のものをロールすることは、それほど大したことではありません。これは、最小限のニーズに合わせて実装する方法の基本的な構造です (もちろん、拡張できます)。

1) まず、基本機能を指定するインターフェースを作成します。

interface IDb
{
    IEnumerable<T> Get<T>(string query, Action<IDbCommand> parameterizer, 
                          Func<IDataRecord, T> selector);

    int Add(string query, Action<IDbCommand> parameterizer);

    int Save(string query, Action<IDbCommand> parameterizer);

    int SaveSafely(string query, Action<IDbCommand> parameterizer);

}

2) インターフェイスを実装するだけでなく、 type で指定する必要がある汎用ヘルパー クラスを作成しますIDbConnection。クラスは、必要な接続文字列を渡してインスタンス化できるように、インスタンス化可能 (静的ではない) である必要があります。

以下は、完全に遅延した実装です。

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;

public class Db<T> : IDb where T : IDbConnection, new()
{
    string connectionString;

    public Db(string connectionString)
    {
        this.connectionString = connectionString;
    }

    IEnumerable<S> Do<R, S>(string query, Action<IDbCommand> parameterizer, 
                            Func<IDbCommand, IEnumerable<R>> actor, Func<R, S> selector)
    {
        using (var conn = new T())
        {
            using (var cmd = conn.CreateCommand())
            {
                if (parameterizer != null)
                    parameterizer(cmd);
                cmd.CommandText = query;
                cmd.Connection.ConnectionString = connectionString;

                cmd.Connection.Open();

                foreach (var item in actor(cmd))
                    yield return selector(item);
            }
        }
    }

    public IEnumerable<S> Get<S>(string query, Action<IDbCommand> parameterizer, Func<IDataRecord, S> selector)
    {
        return Do(query, parameterizer, ExecuteReader, selector);
    }

    static IEnumerable<IDataRecord> ExecuteReader(IDbCommand cmd)
    {
        using (var r = cmd.ExecuteReader(CommandBehavior.CloseConnection))
            while (r.Read())
                yield return r;
    }

    public int Add(string query, Action<IDbCommand> parameterizer)
    {
        return Do(query, parameterizer, ExecuteReader, r => Convert.ToInt32(r[0])).First();
    }

    public int Save(string query, Action<IDbCommand> parameterizer)
    {
        return Do(query, parameterizer, ExecuteNonQuery, noAffected => noAffected).First();
    }

    static IEnumerable<int> ExecuteNonQuery(IDbCommand cmd)
    {
        yield return cmd.ExecuteNonQuery();
    }

    public int SaveSafely(string query, Action<IDbCommand> parameterizer)
    {
        // 'using' clause ensures rollback is called, so no need to explicitly rollback
        return Do(query, parameterizer, cmd => 
        {
            using (cmd.Transaction = cmd.Connection.BeginTransaction())
            {
                var noAffected = ExecuteNonQuery(cmd);
                cmd.Transaction.Commit();
                return noAffected;
            }
        }, noAffected => noAffected).First();
    }
}

これは、基本的な操作ExecuteNonQueryExecuteReader同様の操作、および単純なTransactions のみを実行します。ストアド プロシージャはありません。このAdd関数は、最後に挿入された ID といいね! を挿入および取得するために機能します。Do物事を怠惰にし、コア実行関数(さまざまな db アクションに対して呼び出される) を1 つだけ使用したことは、私の頭がおかしくなりましたDo。そのため、複雑に見えますが、非常に DRY です。別れた方が理想的です。あなたも取り除くことができLinqます。

3)最後Dbに、インスタンス化可能なクラスの周りに一般的な制約のない静的ラッパーを提供して、dbクエリを実行するたびDbにパラメーターを渡し続ける必要がないようにします。Tたとえば、次のようにします。

public static class Db
{
    static IDb db = GetDbInstance();

    static IDb GetDbInstance()
    {
        // get these two from config file or somewhere
        var connectionString = GetConnectionString();
        var driver = GetDbType();   // your logic to decide which db is being used

        // some sort of estimation of your db
        if (driver == SQLite)
            return new Db<SQLiteConnection>(connectionString);
        else if (driver == MySQL)
            return new Db<MySqlConnection>(connectionString);
        else if (driver == JET)
            return new Db<OleDbConnection>(connectionString);
        //etc

        return null;
    }

    public static void Parameterize(this IDbCommand command, string name, 
                                    object value)
    {
        var parameter = command.CreateParameter();
        parameter.ParameterName = name;
        parameter.Value = value;
        command.Parameters.Add(parameter);
    }

    public static IEnumerable<T> Get<T>(string query, 
                                        Action<IDbCommand> parameterizer, 
                                        Func<IDataRecord, T> selector)
    {
        return db.Get(query, parameterizer, selector);
    }

    public static int Add(string query, Action<IDbCommand> parameterizer)
    {
        return db.Add(query, parameterizer);
    }

    public static int Save(string query, Action<IDbCommand> parameterizer)
    {
        return db.Save(query, parameterizer);
    }

    public static int SaveSafely(string query, Action<IDbCommand> parameterizer)
    {
        return db.SaveSafely(query, parameterizer);
    }
}

GetDbInstance4) 次に、接続文字列、プロバイダーの種類などの適切なデータベース パラメーターを推測できるように、追加の静的関数をどこかに作成します。また、クエリのパラメーター化を容易にする拡張メソッドも用意します。私はそれらの両方を上記の静的Dbクラスに入れましたが、それはあなたの選択です(Dbクラス自体に書く人もいますが、機能はアプリケーションのものであるべきなので、私は外側を好みます)。

5) 好みのデータベースで機能するニュートラルなクエリを使用するように注意してください。

または

DbProviderFactoryを使用して、所有している /providerSystem.Data.Commonのタイプを検出できます。DbConnection非ジェネリックDbクラスを1 つだけ使用して、次のことを行うことができます。

public class Db
{
    string connectionString;
    DbProviderFactory factory;

    public Db(string driver, string connectionString)
    {
        this.factory = DbProviderFactories.GetFactory(driver);
        this.connectionString = connectionString;
    }

    //and your core function would look like
    IEnumerable<S> Do<R, S>(string query, Action<IDbCommand> parameterizer, 
                            Func<IDbCommand, IEnumerable<R>> actor, 
                            Func<R, S> selector)
    {
        using (var conn = factory.CreateConnection())
        {
            // and all the remaining code..
        }
    }
}

メソッドGetDbInstanceは次のようになります。

static IDb GetDbInstance()
{
    string connectionString = GetConnectionString();
    string driver = GetDriver();

    return Db(driver, connectionString);
}

if-else長所:プログラミングのスタイルを取り除き、Db構成ファイル内のプロバイダーと接続文字列に応じて、適切なバージョンのクラスがインスタンス化されます。

短所: 構成ファイルで適切なプロバイダー/ドライバーを指定する必要があります。


C# コードのサンプル クエリは次のようになります。

string query = "SELECT * FROM User WHERE id=@id AND savedStatus=@savedStatus";
var users = Db.Get(sql, cmd =>
{
    cmd.Parameterize("id", 1);
    cmd.Parameterize("savedStatus", true);
}, selector).ToArray();

あなたがしなければならないのは callDb.GetなどですDb.Save。関数GetDbInstanceは、呼び出される適切なdllの関数を見つけるここでの鍵であり、ヘルパークラスは、さまざまなdb操作のタスクをさらに実行しながら、リソースを適切に管理します. このようなクラスは、接続を開いたり閉じたり、リソースを解放したり、毎回データベース dll 名前空間を含めなければならないなどの煩わしさを回避します。これがDbALと呼ばれるものです。DbAL がさまざまな厳密に型指定されたモデル クラス間で通信できるように、追加のレイヤーを用意することもできます。私は単純に、OOP であるインターフェースと制約を介したポリモーフィズムの力が大好きです! :)

于 2012-10-30T06:54:05.617 に答える