在过去的几天里,我与各种数据库工作,如MySQL和甲骨文,IBMDB2等它通过ODBC提供与点网连接。
例如:
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应用程序或网站项目的bin文件夹中的每个数据库连接。
推出自己的一个不是什么大不了的事。 下面是我将如何实现它的最低限度的需求(你当然可以展开它)的基本结构:
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
秒。 没有存储过程。 在Add
功能适用于插入和检索最后插入的ID和喜欢。 这是疯了我已经把事情懒惰,已经习惯了只有一个核心执行功能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)小心有你喜欢的工作,数据库查询中性。
要么
您可以利用DbProviderFactory下System.Data.Common
检测的类型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约束爱多态性的力量! :)