疑難雜症——關於EntityFramework的SqlQuery方法的執行效率差別的探討

前言:最近項目上面遇到一個問題,在Code First模式裏面使用EntityFramework的SqlQuery()方法查詢很是慢,一條數據查詢出來須要10秒以上的時間,但是將sql語句放在plsql裏面執行,查詢時間基本能夠忽略不計。折騰了半天時間,仍然找不到緣由。最後經過對比和原始Ado的查詢方式的差別找到緣由,今天將此記錄下。html

本文原創地址:http://www.cnblogs.com/landeanfen/p/8392498.htmlsql

1、問題描述

其實問題很簡單,上面前言已經描述過。就是一個多表的聯合查詢,沒有什麼特殊的語法。大體代碼以下:(PS:不要問爲何用了EF還要使用sql語句,你就當這是個梗!)ide

通用的sql語句查詢方法函數

     public IEnumerable<TEntity> SqlQuery<TEntity>(string sql, params object[] parameters)
        {
            return this._dbContext.Database.SqlQuery<TEntity>(sql, parameters).ToArray();
        }

而後在外層調用這個方法測試

 var sql = @"select * from (
                select ax.*,rownum as rnum from (
                select o.* from order o
                left join ordertt a on o.id=a.orderid
                left join orderdd d on o.id=d.orderid
                left join orderstation ts on d.station = ts.id
                left join ordermodel m on o.materialid=m.id
                where 1=1  and o.status = 30
                order by ts.no,tw.seq
                ) ax ) t 
                 where t.rnum>0 and t.rnum <=15";

                var result = SqlQuery(sql);

而後查詢,每次獲得的結果都是一條記錄,可是卻須要10秒以上,可能你不相信,但這是真的!this

問題就這麼個問題,沒辦法,出現了問題就要想辦法解決。spa

2、找到解決方案

咱們將SqlQuery<T>()方法轉到定義,最終發現它是EntityFramework.dll裏面的方法。pwa

既然轉到定義已經找不到任何突破口,那咱們常規的作法就只有對比了。而後博主在本地定義了一個對比的例子。code

原始組:EF的SqlQuery()方法

     //EF查詢方法
        private static IEnumerable<TestModel> EFSqlQuery(DbContext db, string sql)
        {
            return db.Database.SqlQuery<TestModel>(sql).ToArray();
        }

對比照:Ado的查詢方法

     //ADO的查詢方法
        private static IEnumerable<TestModel> AdoSqlQuery(DbContext db, string sql)
        {
            DataTable dt = ExecuteDataTable(db, sql);
            var ms = GetListByDataTable<TestModel>(dt);
            return ms;
        }
//DataTable轉List<T>公共的方法
        private static DataTable ExecuteDataTable(DbContext db, string sql, params System.Data.Common.DbParameter[] parameters)
        {
            DataTable dt = new DataTable();

            var conn = db.Database.Connection;

            conn.Open();
            using (var cmd = db.Database.Connection.CreateCommand())
            {
                foreach (var p in parameters) cmd.Parameters.Add(p);

                cmd.CommandText = sql;
                dt.Load(cmd.ExecuteReader());
            }
            conn.Close();

            return dt;
        }

        private static List<T> GetListByDataTable<T>(DataTable dt) where T : class, new()//這個意思是一個約束,約定泛型T必需要有一個無參數的構造函數
        {
            List<T> lstResult = new List<T>();
            if (dt.Rows.Count <= 0 || dt == null)
            {
                return lstResult;
            }
            //反射這個類獲得類的類型。
            Type t = typeof(T);
            //聲明一個泛型類對象
            T oT;
            //獲得這個類的全部的屬性
            PropertyInfo[] lstPropertyInfo = t.GetProperties();
            foreach (DataRow dr in dt.Rows)
            {
                //一個DataRow對應一個泛型對象
                oT = new T();
                //遍歷全部的屬性
                for (var i = 0; i < lstPropertyInfo.Length; i++)
                {
                    //獲得屬性名
                    string strPropertyValue = lstPropertyInfo[i].Name;
                    //只有表格的列包含對應的屬性
                    if (dt.Columns.Contains(strPropertyValue))
                    {
                        object oValue = Convert.IsDBNull(dr[strPropertyValue]) ? default(T) : GetTypeDefaultValue(lstPropertyInfo[i].PropertyType.ToString(), dr[strPropertyValue]);
                        //給對應的屬性賦值
                        lstPropertyInfo[i].SetValue(oT, oValue, null);
                    }
                }
                lstResult.Add(oT);
            }
            return lstResult;
        }

        private static object GetTypeDefaultValue(string strTypeName, object oValue)
        {
            switch (strTypeName)
            {
                case "System.String":
                    return oValue.ToString();
                case "System.Int16":
                case "System.Nullable`1[System.Int16]":
                    return Convert.ToInt16(oValue);
                case "System.UInt16":
                case "System.Nullable`1[System.UInt16]":
                    return Convert.ToUInt16(oValue);
                case "System.Int32":
                case "System.Nullable`1[System.Int32]":
                    return Convert.ToInt32(oValue);
                case "System.UInt32":
                case "System.Nullable`1[System.UInt32]":
                    return Convert.ToUInt32(oValue);
                case "System.Int64":
                case "System.Nullable`1[System.Int64]":
                    return Convert.ToInt64(oValue);
                case "System.UInt64":
                case "System.Nullable`1[System.UInt64]":
                    return Convert.ToUInt64(oValue);
                case "System.Single":
                case "System.Nullable`1[System.Single]":
                    return Convert.ToSingle(oValue);
                case "System.Decimal":
                case "System.Nullable`1[System.Decimal]":
                    return Convert.ToDecimal(oValue);
                case "System.Double":
                case "System.Nullable`1[System.Double]":
                    return Convert.ToDouble(oValue);
                case "System.Boolean":
                case "System.Nullable`1[System.Boolean]":
                    return Convert.ToBoolean(oValue);
                case "System.DateTime":
                case "System.Nullable`1[System.DateTime]":
                    return Convert.ToDateTime(oValue);
                case "System.SByte":
                case "System.Nullable`1[System.SByte]":
                    return Convert.ToSByte(oValue);
                case "System.Byte":
                case "System.Nullable`1[System.Byte]":
                    return Convert.ToByte(oValue);
                case "System.Char":
                case "System.Nullable`1[System.Char]":
                    return Convert.ToChar(oValue);
                case "System.Object":
                case "System.Nullable`1[System.Object]":
                    return oValue;
                case "System.Array":
                    return oValue as Array;

                default:
                    return "";
            }
        }
GetListByDataTable

而後在控制檯裏面分別調用。htm

 private static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            using (MesDbContext db = new MesDbContext())
            {
                var sql = @"select * from (
                select ax.*,rownum as rnum from (
                select o.* from order o
                left join ordertt a on o.id=a.orderid
                left join orderdd d on o.id=d.orderid
                left join orderstation ts on d.station = ts.id
                left join ordermodel m on o.materialid=m.id
                where 1=1  and o.status = 30
                order by ts.no,tw.seq
                ) ax ) t 
                 where t.rnum>0 and t.rnum <=15";
                //「預熱」
                EFSqlQuery(db, sql);

                sw.Start();
                var result = EFSqlQuery(db, sql);
                sw.Stop();
                Console.WriteLine("使用EF裏面的SqlQuery方法查詢獲得" + result.Count() + "條記錄。總耗時" + sw.Elapsed);

                //一樣也預熱一下
                AdoSqlQuery(db, sql);
                sw.Restart();
                var result2 = AdoSqlQuery(db, sql);
                sw.Stop();
                Console.WriteLine("使用原始的Ado查詢獲得" + result.Count() + "條記錄。總耗時" + sw.Elapsed);

            }
        }

獲得結果:

差異有多大你們能夠自行腦補。

緣由分析

既然結果差異這麼大,而sql語句在plsql裏面執行又如此快,那麼問題天然而然轉到了對象序列化的身上了。也就是說這個SqlQuery()方法實際上能夠分爲兩個步驟:第一步是查詢獲得DataTable之類的對象,而後第二步是將DataTable之類的對象轉換爲List<T>,既然咱們第一步沒有任何效率問題,那麼問題確定就在第二步上面了。

解決方案

既然初步判斷是對象轉換的問題,將TestModel這個對象轉到定義一看,我地個乖乖,一百來個屬性,因而更加堅信本身的分析是正確的。接下來,博主將這一百個字段減小到50個,再次執行發現效率提升了很多,基本在3秒左右,再減小到只剩20個字段,查詢時間基本在毫秒級別。原來真是反射賦值的效率問題啊。至於項目爲何會有100個字段的對象,博主也沒有想明白,或許真的須要吧!可是由此咱們能夠得出以下經驗:

一、查詢的sql語句裏面儘可能不要用select * 這種語法,尤爲是連表的時候,若是你用了select * ,那有時真的就傷不起了!

二、若是肯定實體裏面真的有那麼多字段有用(極端的狀況),就不要用SqlQuery()了,而改用原生的Ado+DT轉List<T>的方式。

3、意外的「環境問題」

原本覺得找到了緣由了,正要下結論的時候。聽到另外一個同事傳來了不一樣的聲音:騙紙,你的這個測試程序在我這裏跑這麼快,哪裏有你說的10秒以上!去他那邊瞅了一眼,嚇我一跳,哪裏有什麼效率問題,簡直快得不要不要的!這就尷尬了,一樣的程序,在我這邊怎麼這麼慢呢?

最後對比一圈下來,發現他那邊用的Visual Studio的版本是2017,而我這邊是2015。難道真是IDE的環境問題?博主不甘心,在本機裝了一個2017,運行程序,依然快得嚇人。而關掉2017,再用2015跑,一樣須要10秒左右,並且找到其餘裝了VS 2013的電腦,用2013運行,依然須要10秒左右。好像2017如下的版本速度都會有一些影響。對比各個主要dll的版本,最終找不到任何不一樣。到這裏,博主也沒有辦法了。若是哪位有解,感謝賜教!

那就用2017唄,這是最後得出的結論!!!

4、總結

以上針對最近遇到的一個問題作了一些記錄。若是你們有不一樣的意見,歡迎交流和斧正!

本文原創出處:http://www.cnblogs.com/landeanfen/

歡迎各位轉載,可是未經做者本人贊成,轉載文章以後必須在文章頁面明顯位置給出做者和原文鏈接,不然保留追究法律責任的權利

相關文章
相關標籤/搜索