自己滚一个没什么大不了的。这是我将如何实现它以满足最低需求的基本结构(您当然可以扩展它):
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
like操作,和simple Transaction
s。没有存储过程。该Add
函数用于插入和检索最后插入的 id 和 likes。让事情变得懒惰并且只使用一个核心执行函数Do
(被各种数据库操作调用)真是太疯狂了,这就是为什么Do
看起来很复杂,但它非常干燥。理想情况下最好分开。你也可以摆脱Linq
。
3)最后提供静态包装器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);
}
}
4) 现在我将在某处创建一个额外的静态函数GetDbInstance
,以便它推断出正确的数据库参数,如连接字符串、提供程序类型等。还有一个扩展方法来简化查询的参数化。我将它们都放在上面的静态Db
类中,但这是你的选择(有些人将它写在 Db 类本身中,但我更喜欢它在外面,因为功能应该是你的应用程序的)。
5)注意在您喜欢的数据库上使用中性查询。
或者
您可以利用DbProviderFactorySystem.Data.Common
来检测您拥有的 /provider 的类型DbConnection
。您可以只拥有一个非泛型Db
类并执行以下操作:
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();
你所要做的就是调用Db.Get
等Db.Save
。函数GetDbInstance
是这里的关键,它在正确的 dll 中找到要调用的函数,并且帮助类在很好地管理资源的同时还完成了各种数据库操作的任务。这样的类将避免每次打开和关闭连接、释放资源、必须包含数据库 dll 命名空间等的麻烦。这就是所谓的DbAL。您还可以有一个附加层来帮助 DbAL 在各种强类型模型类之间进行通信。我只是喜欢通过接口和约束实现的多态性的强大功能,这非常非常 OOP!:)