了解DbProviderFactory
前不久想使用下EF的通用單表增刪改的特性,當時使用控制台做測試,怎么弄都沒成功,原因出在app.config中,反而在mvc項目中,就沒有任何問題。在反復的更改配置的過程中,發現了DbProviderFactory這個類,好奇打開看看其定義。
// 摘要: // 表示一組方法,這些方法用於創建提供程序對數據源類的實現的實例。 public abstract class DbProviderFactory { // 摘要: // 初始化 System.Data.Common.DbProviderFactory 類的新實例。 protected DbProviderFactory(); // 摘要: // 指定特定的 System.Data.Common.DbProviderFactory 是否支持 System.Data.Common.DbDataSourceEnumerator // 類。 // // 返回結果: // 如果 System.Data.Common.DbProviderFactory 的實例支持 System.Data.Common.DbDataSourceEnumerator // 類,則為 true;否則為 false。 public virtual bool CanCreateDataSourceEnumerator { get; } // 摘要: // 返回實現 System.Data.Common.DbCommand 類的提供程序的類的一個新實例。 // // 返回結果: // System.Data.Common.DbCommand 的新實例。 public virtual DbCommand CreateCommand(); // // 摘要: // 返回實現 System.Data.Common.DbCommandBuilder 類的提供程序的類的一個新實例。 // // 返回結果: // System.Data.Common.DbCommandBuilder 的新實例。 public virtual DbCommandBuilder CreateCommandBuilder(); // // 摘要: // 返回實現 System.Data.Common.DbConnection 類的提供程序的類的一個新實例。 // // 返回結果: // System.Data.Common.DbConnection 的新實例。 public virtual DbConnection CreateConnection(); // // 摘要: // 返回實現 System.Data.Common.DbConnectionStringBuilder 類的提供程序的類的一個新實例。 // // 返回結果: // System.Data.Common.DbConnectionStringBuilder 的新實例。 public virtual DbConnectionStringBuilder CreateConnectionStringBuilder(); // // 摘要: // 返回實現 System.Data.Common.DbDataAdapter 類的提供程序的類的一個新實例。 // // 返回結果: // System.Data.Common.DbDataAdapter 的新實例。 public virtual DbDataAdapter CreateDataAdapter(); // // 摘要: // 返回實現 System.Data.Common.DbDataSourceEnumerator 類的提供程序的類的一個新實例。 // // 返回結果: // System.Data.Common.DbDataSourceEnumerator 的新實例。 public virtual DbDataSourceEnumerator CreateDataSourceEnumerator(); // // 摘要: // 返回實現 System.Data.Common.DbParameter 類的提供程序的類的一個新實例。 // // 返回結果: // System.Data.Common.DbParameter 的新實例。 public virtual DbParameter CreateParameter(); // // 摘要: // 返回提供程序的類的新實例,該實例可實現提供程序的 System.Security.CodeAccessPermission 類的版本。 // // 參數: // state: // System.Security.Permissions.PermissionState 值之一。 // // 返回結果: // 指定 System.Security.Permissions.PermissionState 的 System.Security.CodeAccessPermission // 對象。 public virtual CodeAccessPermission CreatePermission(PermissionState state); }
數據庫5大對象全了。我想是不是可以通過它來構建出更抽象的數據庫訪問。后來查詢相關資料,並閱讀了 PetaPoco 這個微型Orm的源代碼,發現其也是使用DbProviderFactory 來創建數據庫連接等對象的。
反觀自己在最初學習SQLServer,Oracle,Sqlite時,分別封裝了SqlHelp,OracleHelp,SqliteHelp等簡化數據庫的操作,當時還洋洋自得,現在看來很是無語,代碼幾乎是一致的,無非是使用的Connection、Command、Adapter、Parameter 等對象的不同,如果使用DbProviderFactory來隔絕具體的實例,是不是可以把這3個數據庫Help類封裝成一個通用的DbHelp呢?
DbProviderFactory 導入外部的Provider
初步測試是可以的,不過在通過config文件載入DbProviderFactory時遇到點問題,花費了好一會。過程就不說了,如使用Oracle官網的dll,正確的配置是這樣:
<system.data>
<DbProviderFactories>
<add name="OracleClientFactory" invariant="OracleClientFactory" description="Oracle.ManagedDataAccess.Client.OracleClientFactory" type="Oracle.ManagedDataAccess.Client.OracleClientFactory,Oracle.ManagedDataAccess, Version=4.121.1.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
</DbProviderFactories>
</system.data>
其中要注意的,是 invariant 和 type 屬性。
invariant: 可以通過這個屬性值獲取到對應DbProviderFactory類型。如,DbProviderFactories.GetFactory("OracleClientFactory") ;
type:類名(含命名空間),程序集名稱,程序集信息。
我們經常可以在web.config文件中看到類似的type信息,但如果是自己拓展,或者剛剛從Oracle官網上下載的組件呢,該如何得到這些信息。使用Reflector就可以輕松看到,以前一直用Reflector,但從沒關注過這一塊。

另外通過 DbProviderFactories.GetFactoryClasses(); 可以查看到所有被安裝的Provider 哦,通過配置文件導入的也會在其中顯示。
<system.data> <DbProviderFactories> <add name="SQLiteFactory" invariant="SQLiteFactory" description="xxxxxxx" type="System.Data.SQLite.SQLiteFactory,System.Data.SQLite" /> </DbProviderFactories> </system.data> 檢測配置是否成功 var sqlite = DbProviderFactories.GetFactory("SQLiteFactory");
改善自己的DbHelp類
數據庫操作無非是執行查詢與非查詢,在與之相關的方法上,封裝的沒有任何問題。但是在原來的Oracle和Sqlite封裝類中,支持分頁。如傳遞普通的查詢sql語句會自動生成分頁sql呢。其中Oracle 是使用ROWNUM這個來分頁,並沒有使用ROW_NUMBER()這個分析函數,而Sqlite 則使用limit 關鍵字,分頁更簡單。唯獨SQLServer,從2000 到 2012,有3種分頁方式,這個處理起來有些麻煩。
在借鑒了Patacoco通過正則拆分SQL,最終拼接出分頁的SQL語句的相關代碼。具體生成分頁語句的思路,則請看 模仿Orm生成分頁SQL 。
代碼有點長,展開請注意。
public partial class DbHelp { enum DBType { SqlServer2000, SqlServer, Oracle, SQLite } #region 成員定義 private DbProviderFactory dbProvider;//數據庫Provider private DBType dbType;//數據庫類型 private char pSymbol = '@';//參數符號 private DbConnection conn;//連接對象 private string connectionString;//連接字符串 private DbTransaction tran;//事務對象 private IList parameterList = new List<DbParameter>();//過程參數列表 private bool hasOutput = false;//是否包含輸出參數 private Dictionary<string, object> dicPara = new Dictionary<string, object>();//輸出參數列表 #endregion #region 構造方法,實例化連接字符串 /// <summary> /// 讀取WebConfig鏈接字符串 /// </summary> /// <param name="connectionName">ConnectionString配置名</param> public DbHelp(string connectionName = "") { //默認使用ConnectionString第一項 var config = string.IsNullOrEmpty(connectionName) ? ConfigurationManager.ConnectionStrings[0] : ConfigurationManager.ConnectionStrings[connectionName]; dbProvider = DbProviderFactories.GetFactory(config.ProviderName); connectionString = config.ConnectionString; CommonConstruct(config.ProviderName); } /// <summary> /// 有參構造,實例化連接字符串 /// </summary> /// <param name="provider">DbProvider</param> /// <param name="connectionString">連接字符串</param> public DbHelp(DbProviderFactory provider, string connectionString) { this.dbProvider = provider; this.connectionString = connectionString; CommonConstruct(provider.GetType().Name); } private void CommonConstruct(string _dbtype = "") { // Try using type name first (more reliable) if (_dbtype.StartsWith("Oracle")) dbType = DBType.Oracle; else if (_dbtype.StartsWith("SQLite")) dbType = DBType.SQLite; else if (_dbtype.StartsWith("System.Data.SqlClient")) dbType = DBType.SqlServer; // else try with provider name else if (_dbtype.IndexOf("Oracle", StringComparison.InvariantCultureIgnoreCase) >= 0) dbType = DBType.Oracle; else if (_dbtype.IndexOf("SQLite", StringComparison.InvariantCultureIgnoreCase) >= 0) dbType = DBType.SQLite; if (dbType == DBType.Oracle) pSymbol = ':'; else pSymbol = '@'; } #endregion #region 實現接口IDisposable /// <釋放資源接口> /// 實現接口IDisposable /// </釋放資源接口> public void Dispose() { if (conn != null) { if (conn.State == ConnectionState.Open)//判斷數據庫連接池是否打開 { conn.Close(); } if (parameterList.Count > 0)//判斷參數列表是否清空 { parameterList.Clear(); } conn.Dispose();//釋放連接池資源 GC.SuppressFinalize(this);//垃圾回收 } } #endregion #region 執行SQL或存儲過程 並返回影響的行數 /// <summary> /// 執行SQL,並返回影響的行數 /// </summary> /// <param name="sql">SQL語句</param> /// <returns></returns> public int ExecuteNonQuery(string sql) { using (var cmd = CreateCommand(sql)) { return ExecuteNonQuery(cmd); } } /// <summary> /// 執行存儲過程,並返回影響的行數 /// </summary> /// <param name="storeProcedureName">存儲過程名</param> /// <returns></returns> public int ExecuteProceudre(string storeProcedureName) { using (var cmd = CreateCommand(storeProcedureName, CommandType.StoredProcedure)) { return ExecuteNonQuery(cmd); } } #endregion #region 執行SQL或者存儲過程,並返回DataTable /// <summary> /// 執行SQL語句並返回DataTable /// </summary> /// <param name="sql">SQL語句</param> /// <returns></returns> public DataTable ExecuteSql(string sql) { using (var cmd = CreateCommand(sql)) { return Execute(cmd); } } /// <summary> /// 執行存儲過程並返回DataTable /// </summary> /// <param name="storeProcedureName">存儲過程名</param> /// <returns></returns> public DataTable ExecuteProc(string storeProcedureName) { using (var cmd = CreateCommand(storeProcedureName, CommandType.StoredProcedure)) { return Execute(cmd); } } #endregion #region 執行SQL或存儲過程並返回DbDataReader /// <summary> /// 執行SQL語句並返回DbDataReader /// </summary> /// <param name="sql">SQL語句</param> /// <returns>返回DbDataReader</returns> public DbDataReader ExecuteReader(string sql) { using (var cmd = CreateCommand(sql)) { return ExecuteReader(cmd); } } /// <summary> /// 執行存儲過程並返回DbDataReader /// </summary> /// <param name="storeProcedureName">存儲過程名</param> /// <returns>返回DbDataReader</returns> public DbDataReader ExecuteProcReader(string storeProcedureName) { using (var cmd = CreateCommand(storeProcedureName, CommandType.StoredProcedure)) { return ExecuteReader(cmd); } } #endregion #region 執行統計 /// <summary> /// 執行SQL語句 返回首行首列的值,一般用於統計 /// </summary> /// <param name="sql">SQL語句</param> /// <returns>查詢結果首行首列的值轉換為整形,轉換失敗則返回-1</returns> public int Count(string sql) { using (var cmd = CreateCommand(sql)) { return ExecuteScalar(cmd); } } #endregion #region 測試連接是否成功 /// <summary> /// 測試連接是否成功 /// </summary> /// <returns></returns> public bool HasConnection { get { try { conn = new SqlConnection(connectionString); conn.Open(); return true; } catch { return false; } finally { conn.Close(); } } } #endregion #region 索引器訪問 public object this[string name] { set { this[name, DbType.Object, ParameterDirection.Input] = value; } get { object obj; if (dicPara.TryGetValue(name, out obj)) { return obj; } return null; } } public object this[string name, DbType dbtype] { set { this[name, dbtype, ParameterDirection.Input] = value; } } public object this[string name, DbType dbType, ParameterDirection direction] { set { if (name[0] != pSymbol) name = pSymbol + name; var para = dbProvider.CreateParameter(); if (dbType != DbType.Object) para.DbType = dbType; para.ParameterName = name; para.Value = value == null ? DBNull.Value : value; parameterList.Add(para); } } #endregion #region 命令相關處理 /// <summary> /// 創建DbCommand /// </summary> /// <param name="cmdText">命名文本</param> /// <param name="cmdType">命名類型</param> /// <returns></returns> private DbCommand CreateCommand(string cmdText, CommandType cmdType = CommandType.Text) { //創建數據庫連接對象 if (conn == null || conn.State != ConnectionState.Open) { conn = dbProvider.CreateConnection(); conn.ConnectionString = connectionString; conn.Open();//打開數據庫連接池 } //創建Command命令 var cmd = conn.CreateCommand(); cmd.Connection = conn; cmd.CommandType = cmdType; if (!string.IsNullOrEmpty(cmdText)) cmd.CommandText = cmdText; if (tran != null) cmd.Transaction = tran; cmd.CommandTimeout = 0; //加載過程參數 LoadParamter(cmd); return cmd; } /// <summary> /// 創建過程參數 /// </summary> /// <param name="name">參數名</param> /// <param name="value">參數值</param> /// <param name="t">參數值類型</param> /// <param name="pDirection">參數類型</param> /// <returns></returns> private DbParameter CreateParameter(string name, object value, DbType t = DbType.Object, ParameterDirection pDirection = ParameterDirection.Input) { var para = dbProvider.CreateParameter(); if (t != DbType.Object) para.DbType = t; para.Direction = pDirection; if (name[0] == pSymbol) { para.ParameterName = name; } else { para.ParameterName = pSymbol + name; } para.Value = value; return para; } /// <summary> /// 執行Command 並返回影響的行數 /// </summary> /// <param name="cmd">命令</param> /// <returns></returns> private int ExecuteNonQuery(DbCommand cmd) { try { return cmd.ExecuteNonQuery(); } catch (Exception) { conn.Close(); throw; } finally { if (tran == null) Dispose(); } } /// <summary> /// 執行Command 並返回影響的行數 /// </summary> /// <param name="cmd">命令</param> /// <returns></returns> private int ExecuteScalar(DbCommand cmd) { try { var ret = cmd.ExecuteScalar().ToString(); int i; if (!int.TryParse(ret, out i)) { throw new Exception("can't parse it to int,the value is " + ret); } return i; } catch (Exception) { conn.Close(); throw; } finally { if (tran == null) Dispose(); } } /// <summary> /// 執行Command 並返回DataTable /// </summary> /// <param name="cmd">命令</param> /// <returns></returns> private DataTable Execute(DbCommand cmd) { try { using (var adapter = dbProvider.CreateDataAdapter())//創建適配器 { adapter.SelectCommand = cmd; adapter.SelectCommand.CommandTimeout = 0; var dt = new DataTable(); adapter.Fill(dt); return dt;//返回結果集 } } catch (Exception) { conn.Close(); throw; } finally { if (tran == null) Dispose(); } } /// <summary> /// 執行Command 並返回DbDataReader /// </summary> /// <param name="cmd">命令</param> /// <returns></returns> private DbDataReader ExecuteReader(DbCommand cmd) { try { return cmd.ExecuteReader(CommandBehavior.CloseConnection); } catch (Exception) { conn.Close(); throw; } finally { if (tran == null) Dispose(); } } /// <summary> /// 加載輸出參數至字典中 僅當執行非查詢時才調用 /// </summary> /// <param name="Parameters"></param> private void InitDic(DbParameterCollection Parameters) { if (hasOutput) { dicPara.Clear(); foreach (DbParameter Para in Parameters) { if (Para.Direction != ParameterDirection.Input) { dicPara.Add(Para.ParameterName, Para.Value); } } hasOutput = false; } } /// <summary> /// 加載過程參數輸入至Commond中 /// </summary> /// <param name="cmd"></param> private void LoadParamter(DbCommand cmd) { if (parameterList.Count != 0) { foreach (DbParameter Para in parameterList) { if (!hasOutput && Para.Direction != ParameterDirection.Input) { hasOutput = true; } cmd.Parameters.Add(Para); } parameterList.Clear(); } } /// <summary> /// 將參數化Sql轉換成純Sql,便於調試 /// </summary> /// <param name="strSql">SQL語句</param> /// <returns></returns> private string getSqlOnly(DbCommand cmd) { var sql = cmd.CommandText; foreach (DbParameter para in cmd.Parameters) { if (para.DbType == DbType.Int16 || para.DbType == DbType.Int32 || para.DbType == DbType.Int64 || para.DbType == DbType.UInt16 || para.DbType == DbType.UInt32 || para.DbType == DbType.UInt64 || para.DbType == DbType.Decimal || para.DbType == DbType.Double || para.DbType == DbType.Single) { sql = sql.Replace(para.ParameterName, para.Value.ToString()); } else if (pSymbol == '@' || para.DbType == DbType.AnsiString || para.DbType == DbType.String || para.DbType == DbType.StringFixedLength) { sql = sql.Replace(para.ParameterName, "'" + para.Value.ToString() + "'"); } else if (pSymbol == ':' || para.DbType == DbType.DateTime || para.DbType == DbType.DateTime2 || para.DbType == DbType.DateTimeOffset) { //排除未知的時間類型 DateTime time; if (DateTime.TryParse(para.Value.ToString(), out time)) { sql = sql.Replace(para.ParameterName, "to_date('" + time.ToString() + "','yyyy-MM-dd hh24:mi:ss')"); } } } return sql; } #endregion #region 分頁查詢 /// <summary> /// 對指定Sql語句查詢的結果集進行分頁 /// </summary> /// <param name="sql">sql語句</param> /// <param name="start">結果集起始行號,不包括此行</param> /// <param name="limit">取出的行數</param> /// <returns></returns> public DataTable ExecuteSql(string sql, int start, int limit) { string[] sqls; var pageParms = CreatePageSql(sql, out sqls, start, limit); using (var cmd = CreateCommand(sqls[1])) { cmd.Parameters.AddRange(pageParms); return Execute(cmd); } } /// <summary> /// 對指定Sql語句查詢的結果集進行分頁 /// </summary> /// <param name="sql">sql語句</param> /// <param name="start">結果集起始行號,不包括此行</param> /// <param name="limit">取出的行數</param> /// <returns></returns> public DbDataReader ExecuteReader(string sql, int start, int limit) { string[] sqls; var pageParms = CreatePageSql(sql, out sqls, start, limit); using (var cmd = CreateCommand(sqls[1])) { cmd.Parameters.AddRange(pageParms); return ExecuteReader(cmd); } } /// <summary> /// 對指定Sql語句查詢的結果集進行分頁 /// </summary> /// <param name="sql">sql語句</param> /// <param name="start">結果集起始行號,不包括此行</param> /// <param name="limit">取出的行數</param> /// <param name="count">輸出總行數</param> /// <returns></returns> public DataTable ExecuteSql(string sql, int start, int limit, out int count) { //查看是否已經使用事務,若沒有使用事務,這里必須使用事務執行 var alreadyUserTran = tran != null; if (!alreadyUserTran) BeginTransation(); string[] sqls; var pageParms = CreatePageSql(sql, out sqls, start, limit, true); using (var cmd = CreateCommand(sqls[0])) { count = ExecuteScalar(cmd); //加載逆序分頁 並返回過程參數 var pageReverse = CreatePageSqlReverse(sql, ref sqls, start, limit, count); if (pageReverse != null) cmd.Parameters.AddRange(pageParms); else cmd.Parameters.AddRange(pageParms); cmd.CommandText = sqls[1]; var dt = Execute(cmd); //如果事先已開啟事務,則不在此處提交事務,應由用戶調用時手動提交,否則自動提交方法 if (!alreadyUserTran) tran.Commit(); return dt; } } /// <summary> /// 對指定Sql語句查詢的結果集進行分頁 /// </summary> /// <param name="sql">sql語句</param> /// <param name="start">結果集起始行號,不包括此行</param> /// <param name="limit">取出的行數</param> /// <param name="count">輸出總行數</param> /// <returns></returns> public DbDataReader ExecuteReader(string sql, int start, int limit, out int count) { //查看是否已經使用事務,若沒有使用事務,這里必須使用事務執行 var alreadyUserTran = tran != null; if (!alreadyUserTran) BeginTransation(); string[] sqls; var pageParms = CreatePageSql(sql, out sqls, start, limit, true); using (var cmd = CreateCommand(sqls[0])) { count = ExecuteScalar(cmd); //加載逆序分頁 並返回過程參數 var pageReverse = CreatePageSqlReverse(sql, ref sqls, start, limit, count); if (pageReverse != null) cmd.Parameters.AddRange(pageParms); else cmd.Parameters.AddRange(pageParms); cmd.CommandText = sqls[1]; return ExecuteReader(cmd); } } #endregion #region 開啟事務 /// <summary> /// 開啟事務 /// </summary> /// <returns></returns> public void BeginTransation() { //創建數據庫連接對象 if (conn == null || conn.State != ConnectionState.Open) { conn = dbProvider.CreateConnection(); conn.ConnectionString = connectionString; conn.Open();//打開數據庫連接池 } tran = conn.BeginTransaction(); } /// <summary> /// 提交事務 /// </summary> public void Commit() { if (tran != null) { tran.Commit(); tran.Dispose(); tran = null; Dispose(); } } /// <summary> /// 回滾事務 /// </summary> public void Rollback() { if (tran != null) { tran.Rollback(); tran.Dispose(); tran = null; Dispose(); } } #endregion #region 生成 分頁SQL語句 /// <summary> /// 匹配移除Select后的sql /// </summary> private Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); /// <summary> /// 匹配SQL語句中Order By字段 /// </summary> private Regex rxOrderBy = new Regex(@"\b(?<ordersql>ORDER\s+BY\s+(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+)(?:\s+(?<order>ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); /// <summary> /// 匹配SQL語句中Distinct /// </summary> private Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); /// <summary> /// 分析Sql語句 輸出分析數組 信息依次為: /// 0.countsql /// 1.pageSql(保留位置此處不做分析) /// 2.移除了select的sql /// 3.order by 字段 desc /// 4.order by 字段 /// 5.desc /// </summary> /// <param name="sql"></param> /// <returns></returns> private string[] SplitSqlForPaging(string sql) { var sqlInfo = new string[6]; // Extract the columns from "SELECT <whatever> FROM" var m = rxColumns.Match(sql); if (!m.Success) return null; // Save column list and replace with COUNT(*) Group g = m.Groups[1]; sqlInfo[2] = sql.Substring(g.Index); if (rxDistinct.IsMatch(sqlInfo[2])) sqlInfo[0] = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length); else sqlInfo[0] = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length); // Look for an "ORDER BY <whatever>" clause m = rxOrderBy.Match(sqlInfo[0]); if (!m.Success) { sqlInfo[3] = null; } else { g = m.Groups[0]; sqlInfo[3] = g.ToString(); //統計的SQL 移除order sqlInfo[0] = sqlInfo[0].Substring(0, g.Index) + sqlInfo[0].Substring(g.Index + g.Length); //存儲排序信息 sqlInfo[4] = m.Groups["ordersql"].Value;//order by xxx sqlInfo[5] = m.Groups["order"].Value;//desc //select部分 移除order sqlInfo[2] = sqlInfo[2].Replace(sqlInfo[3], string.Empty); } return sqlInfo; } /// <summary> /// 生成逆序分頁Sql語句 /// </summary> /// <param name="sql"></param> /// <param name="sqls"></param> /// <param name="start"></param> /// <param name="limit"></param> /// <param name="total"></param> private DbParameter[] CreatePageSqlReverse(string sql, ref string[] sqls, int start, int limit, int total = 0) { //如果總行數不多或分頁的條數位於前半部分,沒必要逆序分頁 if (total < 100 || start <= total / 2) { return null; } //sql正則分析過后的數組有5個值,若未分析,此處分析 if (sqls == null || sqls.Length == 6) { sqls = SplitSqlForPaging(sql); if (sqls == null) { //無法解析的SQL語句 throw new Exception("can't parse sql to pagesql ,the sql is " + sql); } } //如果未定義排序規則,則無需做逆序分頁計算 if (string.IsNullOrEmpty(sqls[5])) { return null; } //逆序分頁檢查 string sqlOrder = sqls[3]; int end = start + limit; //獲取逆序排序的sql string sqlOrderChange = string.Compare(sqls[5], "desc", true) == 0 ? string.Format("{0} ASC ", sqls[4]) : string.Format("{0} DESC ", sqls[4]); /*理論 * total:10000 start:9980 limit:10 * 則 end:9990 分頁條件為 RN >= 9980+1 and RN <= 9990 * 逆序調整后 * start = total - start = 20 * end = total - end + 1 = 11 * 交換start和end,分頁條件為 RN >= 11 and RN<= 20 */ //重新計算start和end start = total - start; end = total - end + 1; //交換start end start = start + end; end = start - end; start = start - end; //定義分頁SQL var pageSql = new StringBuilder(); if (dbType == DBType.SqlServer2000) { pageSql.AppendFormat("SELECT TOP @PageLimit * FROM ( SELECT TOP @PageEnd {0} {1} ) ", sqls[2], sqlOrderChange); } else if (dbType == DBType.SqlServer) { //組織分頁SQL語句 pageSql.AppendFormat("SELECT PageTab.* FROM ( SELECT TOP @PageEnd ROW_NUMBER() over ({0}) RN , {1} ) PageTab ", sqlOrderChange, sqls[2]); //如果查詢不是第一頁,則需要判斷起始行號 if (start > 1) { pageSql.Append("Where RN >= :PageStart "); } } else if (dbType == DBType.Oracle) { pageSql.AppendFormat("SELECT ROWNUM RN, PageTab.* FROM ( Select {0} {1} ) PageTab where ROWNUM <= :PageEnd ", sqls[2], sqlOrderChange); //如果查詢不是第一頁,則需要判斷起始行號 if (start > 1) { pageSql.Insert(0, "SELECT * FROM ( "); pageSql.Append(" ) "); pageSql.Append(" WHERE RN>= :PageStart "); } } else if (dbType == DBType.SQLite) { pageSql.AppendFormat("SELECT * FROM ( SELECT {0} {1} limit @PageStart,@PageLimit ) PageTab ", sqls[2], sqlOrderChange); } //恢復排序 pageSql.Append(sqlOrder); //存儲生成的分頁SQL語句 sqls[1] = pageSql.ToString(); //臨時測試 //sqls[1] = sqls[1].Replace("@", "").Replace(":", "").Replace("PageStart", start + "").Replace("PageEnd", end + "").Replace("PageLimit", limit + ""); //組織過程參數 DbParameter[] paras = null; if (dbType == DBType.SqlServer2000 || dbType == DBType.SQLite) { paras = new DbParameter[2]; paras[0] = CreateParameter("@PageLimit", limit); paras[1] = CreateParameter("@PageEnd", end); } else if (start > 1) { paras = new DbParameter[2]; paras[0] = CreateParameter("@PageStart", start); paras[1] = CreateParameter("@PageEnd", end); } else { paras = new DbParameter[] { CreateParameter("@PageEnd", end) }; } return paras; } /// <summary> /// 生成常規Sql語句 /// </summary> /// <param name="sql"></param> /// <param name="sqls"></param> /// <param name="start"></param> /// <param name="limit"></param> /// <param name="createCount"></param> private DbParameter[] CreatePageSql(string sql, out string[] sqls, int start, int limit, bool createCount = false) { //需要輸出的sql數組 sqls = null; //生成count的SQL語句 SqlServer生成分頁,必須通過正則拆分 if (createCount || dbType == DBType.SqlServer || dbType == DBType.SqlServer2000) { sqls = SplitSqlForPaging(sql); if (sqls == null) { //無法解析的SQL語句 throw new Exception("can't parse sql to pagesql ,the sql is " + sql); } } else { sqls = new string[2]; } //組織分頁SQL語句 var pageSql = new StringBuilder(); //構建分頁參數 var end = start + limit; start++; if (dbType == DBType.SqlServer2000) { pageSql.AppendFormat("SELECT TOP @PageEnd {0} {1}", sqls[2], sqls[3]); if (start > 1) { var orderChange = string.IsNullOrEmpty(sqls[5]) ? null : string.Compare(sqls[5], "desc", true) == 0 ? string.Format("{0} ASC ", sqls[4]) : string.Format("{0} DESC ", sqls[4]); pageSql.Insert(0, "SELECT TOP 100 PERCENT * FROM (SELECT TOP @PageLimit * FROM ( "); pageSql.AppendFormat(" ) PageTab {0} ) PageTab2 {1}", orderChange, sqls[3]); } } else if (dbType == DBType.SqlServer) { pageSql.AppendFormat(" Select top (@PageEnd) ROW_NUMBER() over ({0}) RN , {1}", string.IsNullOrEmpty(sqls[3]) ? "ORDER BY (SELECT NULL)" : sqls[3], sqls[2]); //如果查詢不是第一頁,則需要判斷起始行號 if (start > 1) { pageSql.Insert(0, "Select PageTab.* from ( "); pageSql.Append(" ) PageTab Where RN >= @PageStart"); } } else if (dbType == DBType.Oracle) { pageSql.Append("select ROWNUM RN, PageTab.* from "); pageSql.AppendFormat(" ( {0} ) PageTab ", sql); pageSql.Append(" where ROWNUM <= :PageEnd "); //如果查詢不是第一頁,則需要判斷起始行號 if (start > 1) { pageSql.Insert(0, "select * from ( "); pageSql.Append(" ) Where RN>= :PageStart "); } } else if (dbType == DBType.SQLite) { pageSql.AppendFormat("{0} limit @PageStart,@PageLimit", sql, start, limit); } //存儲生成的分頁SQL語句 sqls[1] = pageSql.ToString(); //臨時測試 //sqls[1] = sqls[1].Replace("@", "").Replace(":", "").Replace("PageStart", start + "").Replace("PageEnd", end + "").Replace("PageLimit", limit + ""); //組織過程參數 DbParameter[] paras; if (dbType == DBType.SqlServer2000 || dbType == DBType.SQLite) { paras = new DbParameter[2]; paras[0] = CreateParameter("@PageLimit", limit); paras[1] = CreateParameter("@PageEnd", end); } else if (start > 1) { paras = new DbParameter[2]; paras[0] = CreateParameter("@PageStart", start); paras[1] = CreateParameter("@PageEnd", end); } else { paras = new DbParameter[] { CreateParameter("@PageEnd", end) }; } return paras; } #endregion }
