一、前言
相信許多人都百度過:“.net 數據庫訪問類”。然后就出來一大堆SqlHelper。我也用過這些SqlHelper,也自己寫過,一堆靜態方法,開始使用起來感覺很不錯,它們也確實在很多時候可以很好的工作。ADO.NET已經封裝很好了,我們很容易就可以實現自己的數據庫訪問類。
很久前,忘記在哪里看到過了,有一個朋友寫了一篇【如何做一個好用的數據庫訪問類】(有興趣的朋友仍然可以搜索到),這篇文章確實寫得很好,作者很詳細的講解了如何設計一個好的數據庫訪問類;所謂“好“是指:輕量、易用、通用、高效。
其實代碼是很久前就實現了,只是現在才總結記錄,希望可以分享一下學習的過程。ok,在開始前先來看幾個ADO.NET常見的面試題:
1. ADO.NET 5個核心對象是哪5個?
2. 與ADO.NET 相關對象中,哪些可以用於數據綁定?
3. DataSet與DataReader有什么區別?分別適用在什么情況?
二、需求
這是一個簡單的、基於ADO.NET的數據庫訪問類,它最起碼要具備以下特點:
1. 支持多種數據庫
搞.net的視乎有一個固定的思維:數據庫就是用sql server。額,只是很多用sql server,但不是全部,也有很多用 my sql 等的。我們並不能限制一定用什么數據庫。
2. 支持多個數據庫
有時候我們的應用程序會用到多個數據庫,並且這些數據庫還不是部署在同一台服務器上的。
3. 簡單
滿足常見的操作。
4. 可擴展
可以隨時增加新的方法;對於具體的數據源,也可以有特有的操作。
三、主要說明
3.1 使用DbProviderFactory
既然要支持多種數據庫,那么我們之前常寫的SqlConnection、SqlCommand 就都不能用了,因為它們是針對sql server 數據源的。如果換成 my sql 就是 MySqlConnection, Oracle 就是 OracleConnection 了。
既然有那么多種Connection,很多朋友可能會想到通過設計模式來處理,例如定義一個父類(或接口),然后各種類型的數據庫繼承它,然后再通過一個工廠,來創建所需要的對象;以后要增加哪種類型的數據庫就再增加一個對應的類就可以了。大概是像下面這樣:
public abstract class DBHelper
{
public abstract void Open(string key){}
public abstract int ExecuteNonQuery() { }
public abstract object ExecuteScalar() { }
public abstract DataSet GetDataSet() { }
}
public class SqlHelper : DBHelper
{
const char _prefix = '@';
//實現抽象方法...
}
public class MySqlHelper : DBHelper
{
const char _prefix = "@";
//實現抽象方法...
}
public class OracleHelper : DBHelper
{
const char _prefix = ":";
//實現抽象方法...
}
public class DBFactory
{
public static DBHelper GetDBHelper()
{
//根據條件返回DBHelper
}
}
這樣實現已經比用SqlXXX好很多了,這也是我之前寫過的一種方式。但它仍然不夠靈活,並且實現起來就會發現很多代碼都是類似的,這就與我們上面的簡單的需求相違背了。
通過上面的分析,我們知道用工廠模式可以解決我們的問題,但這不用我們自己實現,.net 早就提供這樣的工廠:DbProviderFactory。由名稱可以指定DbProviderFactory就是數據源提供程序工廠,負責創建具體的數據源提供程序。它根據 ProviderName就可以創建對應數據源的訪問對象了。這樣我們的實現也由具體變成抽象了,具體的SqlConection變成了抽象的DbConnection。
什么是 ProviderName? 在配置 web.config 的connectionStrings 時,就會有一個 providerNmae 屬性,例如sql server就是 ”System.Data.SqlClient“,這個名稱空間就是對應的數據源提供程序。
3.2 參數問題
不同數據庫參數查詢的格式可能不一樣,例如 sql server/my sql 支持“@變量” 形式,而 oracle 支持“:變量”的形式。像上面的父類的寫法,子類就必須定義自己的參數前綴。但這些用了DbProviderFactory后也不是問題了。
3.3 using 問題
我們都知道using是c#的語法糖,其實編譯后就是 try-finaly;uisng寫起來比較優雅,而且在有異常的時候會自動調用對象的Disponse方法,避免有些人忘記調用。所以嵌套的 using,編譯后就是嵌套的try-finaly,但其實只要我們注意在拋異常的時候釋放資源,一個try-finaly即可。
3.4 DbDataReader 問題
實際項目中,我們更多的是使用DbDataReader而非DataSet/DataTable,而 DbDataReader需要自己逐行讀取,這在每個調用的地方都這樣寫是很麻煩的,怎么解決?委托,又是它!
說到委托還有一個小小的建議,有些人喜歡自己去定義委托,但其實.net已經內置了3種委托:Func、Action、Predicate,並且提供了多個重載版本,應該優先考慮使用這些委托,在不滿足的情況下,再去自定義。
3.5 在分層架構里的角色
為 DAL 層提供數據訪問服務,由 DAL 直接調用;不涉及sql語句拼接、日志記錄等。
四、例子
假設要調用一個 P_GetFriends存儲過程,接收一個id參數,返回一個好友列表。如下:
public List<Friend> GetFriends(int id)
{
try
{
DBHelper helper = new DBHelper("dbConnectionKey");
DbParameter[] parameters = new DbParameter[]
{
helper.CreateDbParameter("id",id)
};
return helper.ExecuteReader(CommandType.StoredProcedure, "P_GetFriends", parameters,
reader =>
{
return new Friend()
{
ID = reader.GetInt32(reader.GetOrdinal("ID")),
Name = reader.GetString(reader.GetOrdinal("Name"))
};
});
}
catch
{
throw;
}
}
附源代碼
public class DBHelper
{
#region 屬性
/// <summary>
/// 鏈接字符串
/// </summary>
private string conStr;
/// <summary>
/// DB工廠
/// </summary>
private DbProviderFactory provider;
#endregion
#region 構造函數
/// <summary>
/// 構造函數
/// </summary>
/// <param name="key">鏈接字符串鍵</param>
public DBHelper(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key");
}
ConnectionStringSettings css = WebConfigurationManager.ConnectionStrings[key];
if (css == null)
{
throw new InvalidOperationException("未找到指定的鏈接字符串!");
}
this.conStr = css.ConnectionString;
this.provider = DbProviderFactories.GetFactory(css.ProviderName);
}
/// <summary>
/// 構造函數
/// </summary>
/// <param name="conStr">鏈接字符串</param>
/// <param name="providerStr">數據源提供程序</param>
public DBHelper(string conStr, string providerStr)
{
if (string.IsNullOrEmpty(conStr))
{
throw new ArgumentNullException("conStr");
}
if (string.IsNullOrEmpty(providerStr))
{
throw new ArgumentNullException("providerStr");
}
this.provider = DbProviderFactories.GetFactory(providerStr);
this.conStr = conStr;
}
#endregion
#region 外部方法
/// <summary>
/// 執行命令,返回受影響行數
/// </summary>
/// <param name="commandType">命令類型</param>
/// <param name="sql">sql語句或存儲過程名稱</param>
/// <param name="parameters">參數數組</param>
/// <returns>受影響行數,失敗返回-1</returns>
public virtual int ExecuteNonQuery(CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, commandType, sqlOrProcName, parameters);
try
{
con.Open();
return cmd.ExecuteNonQuery();
}
finally
{
cmd.Dispose();
con.Dispose();
}
}
/// <summary>
/// 執行命令,返回第一行第一列對象
/// </summary>
/// <param name="commandType">命令類型</param>
/// <param name="sql">sql語句或存儲過程名稱</param>
/// <param name="parameters">參數數組</param>
/// <returns>執行結果</returns>
public virtual object ExecuteScalar(CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, commandType, sqlOrProcName, parameters);
try
{
con.Open();
return cmd.ExecuteScalar();
}
finally
{
cmd.Dispose();
con.Dispose();
}
}
/// <summary>
/// 執行命令返回DataSet
/// </summary>
/// <param name="commandType">命令類型</param>
/// <param name="sql">sql語句或存儲過程名稱</param>
/// <param name="parameters">參數數組</param>
/// <returns>DataSet</returns>
public virtual DataSet GetDataSet(CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, commandType, sqlOrProcName, parameters);
DataSet set = new DataSet();
DbDataAdapter adapter = this.provider.CreateDataAdapter();
try
{
con.Open();
adapter.SelectCommand = cmd;
adapter.Fill(set);
return set;
}
finally
{
adapter.Dispose();
cmd.Dispose();
con.Dispose();
}
}
/// <summary>
/// 執行命令返回DbDataReader
/// </summary>
/// <param name="commandType">命令類型</param>
/// <param name="sql">sql語句或存儲過程名稱</param>
/// <param name="parameters">參數數組</param>
/// <param name="action">委托</param>
/// <returns>對象列表</returns>
public virtual List<T> ExecuteReader<T>(CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters,
Func<DbDataReader, T> action)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, commandType, sqlOrProcName, parameters);
DbDataReader reader = null;
List<T> result = new List<T>();
try
{
con.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
var item = action(reader);
result.Add(item);
}
return result;
}
finally
{
if (reader != null)
{
reader.Dispose();
}
cmd.Dispose();
con.Dispose();
}
}
/// <summary>
/// 批量執行sql語句
/// </summary>
/// <param name="sqlList">sql語句集合</param>
/// <param name="paramList">參數數組集合</param>
/// <returns>執行成功或失敗</returns>
public virtual bool ExecuteSqlBatchByTrans(IEnumerable<string> sqlList, IEnumerable<List<DbParameter>> paramList)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, CommandType.Text);
DbTransaction trans = null;
try
{
con.Open();
trans = con.BeginTransaction();
cmd.Transaction = trans;
int length = sqlList.Count();
IEnumerable<DbParameter> parameters = null;
for (int i = 0; i < length; i++)
{
cmd.CommandText = sqlList.ElementAt<string>(i);
cmd.Parameters.Clear();
parameters = paramList.ElementAt<List<DbParameter>>(i);
foreach (DbParameter pm in parameters)
{
cmd.Parameters.Add(pm);
}
cmd.ExecuteNonQuery();
}
trans.Commit();
return true;
}
catch
{
if (trans != null)
{
trans.Rollback();
}
throw;
}
finally
{
if (trans != null)
{
trans.Dispose();
}
cmd.Dispose();
con.Dispose();
}
}
#endregion
#region CreateDbParameter
public DbParameter CreateDbParameter(string name, object value)
{
DbParameter parameter = this.provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
return parameter;
}
public DbParameter CreateDbParameter(string name, object value, ParameterDirection direction)
{
DbParameter parameter = this.provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.Direction = direction;
return parameter;
}
public DbParameter CreateDbParameter(string name, object value, int size)
{
DbParameter parameter = this.provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.Size = size;
return parameter;
}
public DbParameter CreateDbParameter(string name, object value, int size, DbType type)
{
DbParameter parameter = this.provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.Size = size;
parameter.DbType = type;
return parameter;
}
public DbParameter CreateDbParameter(string name, object value, int size, DbType type, ParameterDirection direction)
{
DbParameter parameter = this.provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.Size = size;
parameter.DbType = type;
parameter.Direction = direction;
return parameter;
}
#endregion
#region 私有方法
/// <summary>
/// 獲取鏈接實例
/// </summary>
/// <returns>鏈接實例</returns>
private DbConnection CreateConnection()
{
DbConnection con = this.provider.CreateConnection();
con.ConnectionString = this.conStr;
return con;
}
/// <summary>
/// 獲取命令實例
/// </summary>
/// <param name="con">鏈接實例</param>
/// <param name="commandType">命令類型</param>
/// <param name="sqlOrProcName">sql語句或存儲過程名稱</param>
/// <returns>命令實例</returns>
private DbCommand CreateCommand(DbConnection con, CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters)
{
DbCommand cmd = InitCommand(con, commandType, parameters);
cmd.CommandText = sqlOrProcName;
return cmd;
}
/// <summary>
/// 獲取命令實例
/// </summary>
/// <param name="con">鏈接實例</param>
/// <param name="commandType">命令類型</param>
/// <returns>命令實例</returns>
private DbCommand CreateCommand(DbConnection con, CommandType commandType)
{
return InitCommand(con, commandType, null);
}
/// <summary>
/// 初始化命令
/// </summary>
/// <param name="commandType">命令類型</param>
/// <param name="parameters">參數集合</param>
/// <returns></returns>
private DbCommand InitCommand(DbConnection con, CommandType commandType, IEnumerable<DbParameter> parameters)
{
DbCommand cmd = con.CreateCommand();
cmd.CommandType = commandType;
if (parameters != null)
{
foreach (DbParameter pm in parameters)
{
cmd.Parameters.Add(pm);
}
}
return cmd;
}
#endregion
}
