前言
在工作當中呢,沒怎么用到過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】:最近找工作中,工作地點:深圳,希望大家可以推薦推薦!