非EF环境下多数据提供者抽象方案优化及重复实现咨询
你当前的目标是搭建一个可扩展的异构数据库访问框架,基于System.Data.Common抽象类封装通用逻辑,同时支持专用数据库对象(而非仅依赖OleDb)。先来看你现有的实现代码:
现有基类实现
public abstract class DataProvider<TConn, TCmd, TParam> : IDisposable where TConn : DbConnection, new() where TCmd : DbCommand, new() { public DataProvider(string connstr) { ConnectionString = connstr; } public TConn Connection { get { return new TConn(); } } public TCmd Command { get { return new TCmd(); } } public string ConnectionString { get; private set; } protected DbDataReader ExecuteReader(string text, CommandType type, params TParam[] parms) { try { using (var conn = Connection) { conn.Open(); using (var cmd = Command) { cmd.CommandText = text; cmd.CommandType = type; cmd.Parameters.AddRange(parms); return cmd.ExecuteReader(); } } } catch (Exception ex) { Log.Exception(ex, GetType().Name); throw ex; } } }
现有实现类
SqlDataProvider
public class SqlDataProvider : DataProvider<SqlConnection, SqlCommand, SqlParameter> { public SqlDataProvider(string connstr) : base(connstr) { } public DataTable ExecuteStoredProcedure(string text, params SqlParameter[] parms) { var dt = new DataTable(); dt.Load(ExecuteReader(text, CommandType.StoredProcedure, parms)); return dt; } public DataTable ExecuteStatement(string text, params SqlParameter[] parms) { var dt = new DataTable(); dt.Load(ExecuteReader(text, CommandType.Text, parms)); return dt; } }
OleDataProvider
public class OleDataProvider : DataProvider<OleDbConnection, OleDbCommand, OleDbParameter> { public OleDataProvider(string connstr) : base(connstr) { } public DataTable ExecuteStoredProcedure(string text, params OleDbParameter[] parms) { var dt = new DataTable(); dt.Load(ExecuteReader(text, CommandType.StoredProcedure, parms)); return dt; } public DataTable ExecuteStatement(string text, params OleDbParameter[] parms) { var dt = new DataTable(); dt.Load(ExecuteReader(text, CommandType.Text, parms)); return dt; } }
你的疑问解答
1. 你是否重复了已有工作?
是的,其实ADO.NET原生就提供了DbProviderFactory机制,专门解决异构数据库的统一访问问题,完全可以替代你当前的泛型基类实现。你现在的方案虽然能运行,但每次新增数据库(比如Oracle、Sybase)都需要手动创建新的继承类,而DbProviderFactory可以通过配置驱动的方式动态创建对应的DbConnection、DbCommand、DbParameter等对象,不需要重复编写相似的实现类。
2. 除EF外的更优实现方案
推荐以下两种方向,都比你当前的泛型基类更灵活、更符合ADO.NET的设计规范:
方案一:基于DbProviderFactory封装通用数据访问层
DbProviderFactory是ADO.NET的原生抽象,它可以通过数据库驱动的名称(比如System.Data.SqlClient、Oracle.DataAccess.Client)动态创建所有Db*系列对象。你可以封装一个通用的DataAccess类,所有操作都基于抽象类而非具体实现:
public class GenericDataAccess : IDisposable { private readonly DbProviderFactory _factory; private readonly string _connectionString; public GenericDataAccess(string providerName, string connectionString) { _factory = DbProviderFactories.GetFactory(providerName); _connectionString = connectionString; } public DataTable ExecuteDataTable(string commandText, CommandType commandType, params DbParameter[] parameters) { var dt = new DataTable(); using (var conn = _factory.CreateConnection()) { conn.ConnectionString = _connectionString; conn.Open(); using (var cmd = _factory.CreateCommand()) { cmd.Connection = conn; cmd.CommandText = commandText; cmd.CommandType = commandType; if (parameters != null) { cmd.Parameters.AddRange(parameters); } using (var reader = cmd.ExecuteReader()) { dt.Load(reader); } } } return dt; } // 可以扩展更多通用方法:ExecuteScalar、ExecuteNonQuery等 public void Dispose() { // 清理资源(如果需要) } }
使用时只需要传入对应的驱动名称和连接字符串:
// SQL Server var sqlAccess = new GenericDataAccess("System.Data.SqlClient", "your-sql-connection-string"); var sqlDt = sqlAccess.ExecuteDataTable("GetUser", CommandType.StoredProcedure, new SqlParameter("@UserId", 1)); // Oracle var oracleAccess = new GenericDataAccess("Oracle.DataAccess.Client", "your-oracle-connection-string"); var oracleDt = oracleAccess.ExecuteDataTable("GET_USER", CommandType.StoredProcedure, new OracleParameter("P_USER_ID", 1));
这种方案的优势:
- 新增数据库只需添加对应的驱动包(比如Oracle.DataAccess、Sybase.Data.AseClient),并在配置文件中注册驱动(或直接传入providerName),无需编写新的实现类
- 所有数据库操作逻辑复用同一套代码,避免重复
- 完全基于ADO.NET原生抽象,稳定性和兼容性更好
方案二:结合轻量ORM(如Dapper)
如果你不想自己封装底层的Db*操作,可以使用Dapper——这是一个轻量级ORM,基于ADO.NET实现,性能接近原生ADO.NET,同时提供了对象映射能力。Dapper完全支持DbConnection抽象,所以可以和DbProviderFactory结合使用,快速实现通用数据访问:
public class DapperDataAccess : IDisposable { private readonly DbProviderFactory _factory; private readonly string _connectionString; public DapperDataAccess(string providerName, string connectionString) { _factory = DbProviderFactories.GetFactory(providerName); _connectionString = connectionString; } public IEnumerable<T> Query<T>(string sql, object parameters = null, CommandType commandType = CommandType.Text) { using (var conn = _factory.CreateConnection()) { conn.ConnectionString = _connectionString; conn.Open(); return conn.Query<T>(sql, parameters, commandType: commandType); } } // 扩展更多Dapper方法:Execute、QueryFirstOrDefault等 }
Dapper的优势是减少了手动处理DataTable和对象映射的代码,同时保持了原生ADO.NET的性能,适合需要快速开发又不想引入EF这类重型ORM的场景。
总结
- 你当前的泛型基类方案存在重复造轮子的问题,ADO.NET原生的
DbProviderFactory已经完美解决了异构数据库的统一访问需求 - 优先推荐基于
DbProviderFactory封装通用数据访问层,或者结合Dapper这类轻量ORM,两者都比你当前的方案更灵活、更易扩展 - 对于你提到的MS-SQL、Sybase、Informatica、Oracle,都有对应的官方或第三方
DbProviderFactory驱动支持,无需依赖OleDb
内容的提问来源于stack exchange,提问作者GoldBishop

