要知道的DbProviderFactory


了解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");
SQLite配置

改善自己的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

    }
DbHelp

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM