独自のものをロールすることは、それほど大したことではありません。これは、最小限のニーズに合わせて実装する方法の基本的な構造です (もちろん、拡張できます)。
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();
}
}
これは、基本的な操作ExecuteNonQuery
とExecuteReader
同様の操作、および単純なTransaction
s のみを実行します。ストアド プロシージャはありません。この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);
}
}
GetDbInstance
4) 次に、接続文字列、プロバイダーの種類などの適切なデータベース パラメーターを推測できるように、追加の静的関数をどこかに作成します。また、クエリのパラメーター化を容易にする拡張メソッドも用意します。私はそれらの両方を上記の静的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 であるインターフェースと制約を介したポリモーフィズムの力が大好きです! :)