为任何数据库支持编写驱动程序类generics

在过去的几天里,我正在使用各种数据库,如MySQL,oracle,Ibmdb2等,通过odbc提供商与dot net连接。

例如:

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=***;" 

现在我的问题是

是否可以为任何数据库提供程序编写generics类

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

它只是通过更改web.config中的驱动程序名称并将该dll文件放在我发布的Web应用程序或网站项目的bin文件夹中来连接每个数据库。

推出自己的一个并不是什么大不了的事。 这是我如何实现它以满足最低需求的基本结构(当然你可以扩展它):

1)首先创建一个指定基本function的接口。

 interface IDb { IEnumerable Get(string query, Action parameterizer, Func selector); int Add(string query, Action parameterizer); int Save(string query, Action parameterizer); int SaveSafely(string query, Action parameterizer); } 

2)创建通用助手类,它不仅应该实现接口,还应该由类型IDbConnection指定。 该类应该更好(不一定)可实例化(非静态),以便您可以传递所需的连接字符串来实例化它。

这是一个完全懒惰的实现:

 using System; using System.Data; using System.Collections.Generic; using System.Linq; public class Db : IDb where T : IDbConnection, new() { string connectionString; public Db(string connectionString) { this.connectionString = connectionString; } IEnumerable Do(string query, Action parameterizer, Func> actor, Func 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 Get(string query, Action parameterizer, Func selector) { return Do(query, parameterizer, ExecuteReader, selector); } static IEnumerable ExecuteReader(IDbCommand cmd) { using (var r = cmd.ExecuteReader(CommandBehavior.CloseConnection)) while (r.Read()) yield return r; } public int Add(string query, Action parameterizer) { return Do(query, parameterizer, ExecuteReader, r => Convert.ToInt32(r[0])).First(); } public int Save(string query, Action parameterizer) { return Do(query, parameterizer, ExecuteNonQuery, noAffected => noAffected).First(); } static IEnumerable ExecuteNonQuery(IDbCommand cmd) { yield return cmd.ExecuteNonQuery(); } public int SaveSafely(string query, Action 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操作,以及简单的Transaction 。 没有存储过程。 Add函数用于插入和检索最后插入的id和like。 让我变得懒惰并且只使用一个核心执行函数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(connectionString); else if (driver == MySQL) return new Db(connectionString); else if (driver == JET) return new Db(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 Get(string query, Action parameterizer, Func selector) { return db.Get(query, parameterizer, selector); } public static int Add(string query, Action parameterizer) { return db.Add(query, parameterizer); } public static int Save(string query, Action parameterizer) { return db.Save(query, parameterizer); } public static int SaveSafely(string query, Action parameterizer) { return db.SaveSafely(query, parameterizer); } } 

4)现在我将在某处创建一个额外的静态函数GetDbInstance ,以便它推断出正确的数据库参数,如连接字符串,提供程序类型等。还有一个扩展方法来简化查询的参数化。 我将它们都放在上面的静态Db类中,但这是你的选择(有些人在Db类中编写它但我更喜欢它在外面因为function应该是你的应用程序)。

5)注意对您喜欢的数据库进行中性查询。

要么

您可以使用System.Data.Common下的DbProviderFactory来检测您拥有的DbConnection /提供程序的类型。 你可以只有一个非generics的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 Do(string query, Action parameterizer, Func> actor, Func 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); } 

Pro:您摆脱了if-else编程风格,并且将根据配置文件中的提供程序和连接字符串实例化正确版本的Db类。

Con:您需要在配置文件中指定正确的提供者/驱动程序。


来自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.GetDb.Save等。函数GetDbInstance是这里的关键,它在正确的dll中找到要调用的函数,helper类可以很好地管理资源,同时还可以执行各种任务。数据库操作。 这样的类可以避免打开和关闭连接,释放资源,每次都必须包含数据库DLL命名空间等的麻烦。 这就是所谓的DbAL 。 您还可以使用其他图层来帮助DbAL在各种强类型模型类之间进行通信。 我只是喜欢通过接口和约束的多态性的力量,非常非常OOP! 🙂