EF循環迭代導致如此嚴重的性能丟失,你知道?


前言

在工作當中呢,沒怎么用到過EF,所以為了遺忘這一部分知識,偶爾會去寫寫小的demo,偶然機會在EF循環迭代中發現居然影響性能這么嚴重,當我們在用時或許大概也許可能都曾這樣寫過,但是你注意到了嗎,你懷疑過嗎?這就是本節所要討論的話題。若有錯誤,請批評指出。

話題

關於基礎知識我們就不廢話了哈,我們假設這樣一個場景(不一定嚴謹,只是為了引出話題):當在下單中,如果有多個人下單,此時我們需要通過訂單Id去得到客戶Id。在這一場景中我們給出一個訂單類以及訂單處理類。如下:

     //訂單類
     public class Order
    {
        public int Id { get; set; }
        public int OrderId { get; set; }
        public int CustomerId { get; set; }
        public string Filed1 { get; set; }
        public string Filed2 { get; set; }
        public string Filed3 { get; set; }
        public string Filed4 { get; set; }
        public string Filed5 { get; set; }
    }
    //訂單處理類
    public class OrderProcess
    {
        public int OrderId { get; set; }
        public int CustomerId { get; set; }
    }

訂單類是poco類存於數據庫中,而訂單處理類為將訂單類進行DTO的類,我們將訂單Id傳到訂單處理類中,通過訂單類來獲取到客戶Id並賦給訂單處理類中的客戶Id。

為了大家可能方便測試,將其余說了很多次的映射類以及上下文都給出來。

    //訂單映射類
    public class OrderMap : EntityTypeConfiguration<Order>
    {
        public OrderMap()
        {
            HasKey(k => k.Id);
            Property(p => p.OrderId);
            Property(p => p.CustomerId);
            Property(p => p.Filed1);
            Property(p => p.Filed2);
            Property(p => p.Filed3);
            Property(p => p.Filed4);
            Property(p => p.Filed5);
        }
    }
    //EF上下文以及預熱
    public class EFDbContext : DbContext
    {
        public EFDbContext()
            : base("name=EFIteration")
        {
            var objectContext = ((IObjectContextAdapter)this).ObjectContext;
            var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace);
            mappingCollection.GenerateViews(new List<EdmSchemaError>());
        }


        /// <summary>
        /// 通過反射一次性將表進行映射
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {

            var typesRegister = Assembly.GetExecutingAssembly().GetTypes()
                .Where(type => !(string.IsNullOrEmpty(type.Namespace))).Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
            foreach (var type in typesRegister)
            {
                dynamic configurationInstance = Activator.CreateInstance(type);
                modelBuilder.Configurations.Add(configurationInstance);
            }

        }
    }

在數據庫中對訂單表生成了20萬條數據。我們在訂單處理類中隨機插入100條訂單Id,如下:

            var orderProcessList = new List<OrderProcess>();
            for (int i = 0; i < 100; i++)
            {
                var random = new Random();
                var number = random.Next(1, 200000);
                var orderProcess = new OrderProcess() { OrderId = number };
                orderProcessList.Add(orderProcess);
            }

為了將訂單類中客戶Id賦給訂單處理類中客戶Id,你是不是會像如下這樣操作呢?

            var context = new EFDbContext();
            foreach (var op in orderProcessList)
            {
                var order = context.Set<Order>().FirstOrDefault(o => o.OrderId == op.OrderId);
                op.CustomerId = order.CustomerId;
            }

此時我們來測試下耗費的時間。

結果是3.2秒,看到這里有人就說了,而且大家都看到過都知道,在查詢時為了性能我們可以關閉EF的變更追蹤,於是乎就有了下面的代碼:

改良一

 var order = context.Set<Order>().FirstOrDefault(o => o.OrderId == op.OrderId);

//替換成

 var order = context.Set<Order>().AsNoTracking().FirstOrDefault(o => o.OrderId == op.OrderId);

此時看看演示結果:

此時耗費的時間為1.7秒,當然有時會更短,反正比上述加了變更追蹤情況的耗費時間要少。

到了這里你是不是就覺得已經很滿足了呢?是不是覺得已經得到了很大的改善了呢?要是這樣關閉變更追蹤就可以解決了問題,那我這篇文章你說還有什么意義呢?好了廢話不多說。本文討論的話題就在於EF循環迭代問題,難道僅僅只有上述一種方式能達到我們的需求嗎,上述我們將EF迭代放在了遍歷訂單處理類中,我們難道就不能事先得到符合條件的訂單Id,然后直接取到對應的客戶Id呢?思路來了,說完就開始干。

改良二

(1)篩選出符合訂單處理類中的訂單。

            var orderToProcessIds = orderProcessList.Select(o => o.OrderId).ToList();
            var allOrders = context.Set<Order>().AsNoTracking().Where(o => orderToProcessIds.Contains(o.OrderId));

(2)將符合條件的訂單轉換成以訂單Id為鍵的字典。

            var allOrdersDictionary = allOrders.ToDictionary(o => o.OrderId);
            foreach (var op in orderProcessList)
            {
                var order = allOrdersDictionary[op.OrderId];
                op.CustomerId = order.CustomerId;
            }

這樣不就解決了每次都要去迭代訂單嗎。接下來我們來測試比較改良一和改良二中耗費的時間。

 改良一中的耗時為3.4秒,改良二中耗時為0.3秒,通過小小的改善是不是又將性能提升了10倍呢!不必多說,你懂的!

總結

本節我們驗證了在EF循環迭代中會導致性能的丟失,我們不經意間的操作就導致性能的丟失,有時候轉換思維很重要,不僅僅只局限於固定思維,從上面可以看出:能夠避免的應該盡量避免在遍歷數據時去進行EF的循環迭代。好了,本來打算早點睡覺的,偶然發現這樣也會導致性能的丟失,於是馬不停蹄導致本文的產生。

【Advertisement】:最近找工作中,工作地點:深圳,希望大家可以推薦推薦!

 


免責聲明!

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



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