〇、目錄
一、前言
二、設計思路
三、核心代碼分析
(一) 在項目中建立專屬文件夾
(二) 在文件夾內實現數據操作的基礎准備
(四) 添加測試調用入口
(五) 運行測試
五、寫在后面
六、源碼下載
一、前言
首先,鑒於本文所展現的ORM耗時測試已成為了博友的吐嘈點,我想我有必要聲明一點:我發布這個測試框架,相當於一個活動,目的是收集各種數據訪問解決方案的實現示例,並在性能,易用性,代碼量上做一個綜合的對比,讓大家更好的了解各個解決方案的優缺點,選擇的時候更明確。時間的對比只是其中一個方面,並不是對比的全部。
最近又掀起了數據訪問組件&ORM的性能比拼了,但基本都是各說各的好,沒有一個統一的標准與平台來進行對比。
郭某不才,花了點時間寫了個數據訪問性能測試框架,主要是想各個ORM在一個統一的環境下完成相同的事,來進行一下公平的對比,看看哪家的性能更好、更易用、完成相同的業務代碼量更少。
特別說明,本框架測試環境如下:VS2010+SP1或以上,SQL2005或以上,.NET 4.0,能運行以上環境的操作系統
本框架的代碼已發布到 codeplex 上,同學們可以通過VS自帶的團隊項目管理功能(TFS)或SVN 隨時獲取最新代碼。
項目地址:https://datatester.codeplex.com
TFS獲取地址:https://tfs.codeplex.com:443/tfs/TFS17,(似乎只有團隊成員可以用TFS方式)需要登錄,並在項目的 SOURCE CODE 標簽的 Connect 連接處獲取VS端的登錄用戶名,密碼為自己的賬號密碼。
SVN獲取地址:https://datatester.svn.codeplex.com/svn
二、設計思路
這個測試框架主要是設計一個通用的測試基類,把要做的事規定好,並把實現細節開放出來供給各家ORM自己去實現,為公平起見,規定如下:
- 所有數據訪問操作都從一個數據庫連接字符串開始。
- 要實現的業務最終結果必須相同,比如有些同學給出的ado.net與ORM的對比測試,ORM查詢出來的是一個實體,而ado.net去只是執行一條相應的sql語句,顯然是不公平的,ado.net也必須構造出一個實體,才算把業務實現了。
- 統一的調用方案,所有的測試方法的調用入口相同。
根據這個設計思想,設計了單個實體與多個實體的添加,查詢,修改,刪除操作,為了體現各個ORM的易用性,還添加了一個比較復雜的查詢操作。以上這些操作都是在基類中定義了對應的 protected abstract 的方法,需要在具體的實現類中進行實現。並且唯一的前提條件只有基類中的的一個只讀的數據庫連接字符串ConnectionString,如果該連接串不滿足要求,也可以在實現類中進行重寫。
本次測試使用到的數據庫結構如下(偷下懶,直接上EF4.0的反向數據庫功能生成的 edmx 圖上)
由圖可以看出,實體關系如下:
- 分類:一個產品分類可以對應多個產品
- 客戶:一個客戶可以對應多個訂單
- 產品:一個產品必定對應一個分類,且可以對應多個訂單明細
- 訂單:一個訂單必定對應一個客戶,且可以對應多個訂單明細
- 訂單明細:一個訂單明細必定對應一個產品與一個訂單
在本框架中,規定的要實現的業務如下:
- 單個操作
- 添加一個客戶信息(客戶名稱做有唯一處理)
- 根據客戶名稱獲取步驟1中添加的客戶信息,並返回
- 將步驟2中獲取的客戶信息的 PostalCode 與 Tel 信息更新為指定字符串
- 刪除這個客戶信息
- 批量操作
- 批量添加指定數量的客戶信息(客戶名稱做有唯一處理)
- 查詢出剛添加的指定數量的客戶信息,並返回客戶信息的集合
- 將步驟2獲取的客戶信息的 Address 信息更新為 Address + CustomerName
- 刪除批量操作所添加的客戶信息
- 復雜查詢
復雜查詢將查詢已完成的訂單信息的集合,並以如下視圖模型作為結果裝載
視圖模型定義如下:
1 namespace DataTestFramework.ViewModels 2 { 3 /// <summary> 4 /// 視圖模型——訂單信息 5 /// </summary> 6 public class OrderView 7 { 8 public int OrderId { get; set; } 9 10 public DateTime OrderDate { get; set; } 11 12 public decimal SumMoney { get; set; } 13 14 public bool Finished { get; set; } 15 16 /// <summary> 17 /// 訂單關聯的客戶名稱 18 /// </summary> 19 public string CustomerName { get; set; } 20 21 /// <summary> 22 /// 當前訂單的所有訂單明細所關聯的產品名稱,多個以逗號分隔 23 /// </summary> 24 public string ProductNames { get; set; } 25 } 26 }
三、核心代碼分析
借助VS2012更新了Update1后的新功能 Code Map,測試基類的結構展現如下:
上圖展示了測試基類TesterBase的調用結構:
- Work方法是公共方法,測試的運行由這個方法開始。
- SingleCrudTest、MultipleCrudTest、RetrieveComplex三個方法是私有方法,分別負責單個實體,多個實體,復雜查詢的功能調用、計時與輸出功能。
- 再下一級的方法是受保護的抽象方法,負責各個功能的業務實現,需要具體的實現類中進行實現。
測試基類 TesterBase 的具體代碼如下:
1 namespace DataTestFramework.Infrastructure 2 { 3 /// <summary> 4 /// 測試類基類 5 /// </summary> 6 public abstract class TesterBase 7 { 8 private const string _connectionString = "Data Source=.; Integrated Security=True;" + 9 " Initial Catalog=DataTestFramework; Pooling=True; MultipleActiveResultSets=True;"; 10 11 /// <summary> 12 /// 獲取 數據庫連接字符串 13 /// </summary> 14 protected virtual string ConnectionString 15 { 16 get { return _connectionString; } 17 } 18 19 #region 受保護方法 20 21 #region 單個操作 22 23 /// <summary> 24 /// 添加單個客戶信息 25 /// </summary> 26 /// <param name="customer">待添加的客戶信息</param> 27 protected abstract void CreateCustomer(Customer customer); 28 29 /// <summary> 30 /// 獲取指定名稱的客戶信息 31 /// </summary> 32 /// <param name="customerName">帶Token的客戶名稱</param> 33 /// <returns>指定名稱的客戶信息,不存在時返回null</returns> 34 protected abstract Customer RetrieveCustomer(string customerName); 35 36 /// <summary> 37 /// 更新指定客戶信息的 PostalCode="100000",Tel="13800138000" 38 /// </summary> 39 /// <param name="customer">待更新的客戶信息</param> 40 protected abstract void UpdateCustomer(Customer customer); 41 42 /// <summary> 43 /// 刪除指定客戶信息 44 /// </summary> 45 /// <param name="customerId">客戶編號</param> 46 protected abstract void DeleteCustomer(int customerId); 47 48 #endregion 49 50 #region 批量操作 51 52 /// <summary> 53 /// 批量添加客戶信息 54 /// </summary> 55 /// <param name="customers">待添加的客戶信息集合</param> 56 protected abstract void CreateCustomers(IEnumerable<Customer> customers); 57 58 /// <summary> 59 /// 反向(先倒序排序)獲取指定數量的客戶信息 60 /// </summary> 61 /// <param name="count">要獲取的數量</param> 62 /// <returns>獲取的客戶信息集合</returns> 63 protected abstract IEnumerable<Customer> RetrieveCustomers(int count); 64 65 /// <summary> 66 /// 更新指定的多個客戶信息,在Address后面加上當前客戶的CustomerName信息 67 /// </summary> 68 /// <param name="customers">待更新的客戶信息</param> 69 protected abstract void UpdateCustomers(IEnumerable<Customer> customers); 70 71 /// <summary> 72 /// 批量刪除指定編號的客戶信息 73 /// </summary> 74 /// <param name="customerIds">待刪除的客戶編號集合</param> 75 protected abstract void DeleteCustomers(IEnumerable<int> customerIds); 76 77 #endregion 78 79 #region 復雜查詢 80 81 /// <summary> 82 /// 查詢所有已完成(Finished == true)的訂單,構建訂單視圖模型 83 /// </summary> 84 /// <returns>滿足條件的訂單視圖模型集合</returns> 85 protected abstract IEnumerable<OrderView> RetrieveOrderViews(); 86 87 #endregion 88 89 #endregion 90 91 #region 私有方法 92 93 /// <summary> 94 /// 單個實體操作測試 95 /// </summary> 96 /// <returns>是否繼續</returns> 97 private bool SingleCrudTest() 98 { 99 string token = DateTime.Now.ToString("hhmmssfff"); 100 Stopwatch watch = new Stopwatch(); 101 Customer customer = new Customer 102 { 103 CustomerName = "郭明鋒@中國" + token, 104 ContactName = "郭明鋒", 105 Address = "北京,北京", 106 PostalCode = "100001", 107 Tel = "13800138001" 108 }; 109 110 //單個客戶添加 111 watch.Restart(); 112 CreateCustomer(customer); 113 watch.Stop(); 114 Console.WriteLine("單個實體添加成功,耗時:{0}", watch.Elapsed); 115 116 //查詢最后添加的客戶信息 117 watch.Restart(); 118 Customer lastCustomer = RetrieveCustomer(customer.CustomerName); 119 watch.Stop(); 120 if (lastCustomer != null && lastCustomer.CustomerName == customer.CustomerName) 121 { 122 Console.WriteLine("單個實體查詢成功,耗時:{0}", watch.Elapsed); 123 } 124 else 125 { 126 Console.WriteLine("單個實體查詢失敗,耗時:{0},測試終止。", watch.Elapsed); 127 return false; 128 } 129 130 //更新上一步查詢出來的客戶信息 131 watch.Restart(); 132 UpdateCustomer(lastCustomer); 133 watch.Stop(); 134 lastCustomer = RetrieveCustomer(customer.CustomerName); 135 if (lastCustomer != null && lastCustomer.PostalCode == "100000" && lastCustomer.Tel == "13800138000") 136 { 137 Console.WriteLine("單個實體更新成功,耗時:{0}", watch.Elapsed); 138 } 139 else 140 { 141 Console.WriteLine("單個實體更新失敗,耗時:{0},測試終止。", watch.Elapsed); 142 return false; 143 } 144 145 //刪除本次添加的單個客戶信息 146 watch.Restart(); 147 DeleteCustomer(lastCustomer.CustomerId); 148 watch.Stop(); 149 lastCustomer = RetrieveCustomer(customer.CustomerName); 150 if (lastCustomer == null) 151 { 152 Console.WriteLine("單個實體刪除成功,耗時:{0}", watch.Elapsed); 153 } 154 else 155 { 156 Console.WriteLine("單個實體刪除成功,耗時:{0},測試終止", watch.Elapsed); 157 return false; 158 } 159 return true; 160 } 161 162 /// <summary> 163 /// 批量實體操作測試 164 /// </summary> 165 /// <returns>是否繼續</returns> 166 private bool MultipleCrudTest() 167 { 168 string token = DateTime.Now.ToString("hhmmssfff"); 169 Stopwatch watch = new Stopwatch(); 170 Console.WriteLine("要開始批量測試,請輸入批量大小:"); 171 int count; 172 bool flag = int.TryParse(Console.ReadLine(), out count); 173 while (!flag) 174 { 175 Console.WriteLine("要開始批量測試,請輸入批量大小:"); 176 flag = int.TryParse(Console.ReadLine(), out count); 177 } 178 List<Customer> customers = new List<Customer>(); 179 for (int index = 0; index < count; index++) 180 { 181 customers.Add(new Customer 182 { 183 CustomerName = string.Format("郭明鋒@中國{0}{1}", token, index), 184 ContactName = "郭明鋒", 185 Address = "北京,北京", 186 PostalCode = "100001", 187 Tel = "13800138001" 188 }); 189 } 190 Console.WriteLine("開始進行批量測試,測試數量為:{0}", count); 191 192 //批量客戶添加 193 watch.Restart(); 194 CreateCustomers(customers); 195 watch.Stop(); 196 Console.WriteLine("批量實體添加成功,耗時:{0}", watch.Elapsed); 197 198 //查詢最后添加的客戶信息 199 watch.Restart(); 200 List<Customer> lastCustomers = RetrieveCustomers(count).ToList(); 201 watch.Stop(); 202 //以前后客戶名稱集合取差集來判斷是否一致 203 if (!lastCustomers.Select(m => m.CustomerName).Except(customers.Select(m => m.CustomerName)).Any()) 204 { 205 Console.WriteLine("批量實體查詢成功,耗時:{0}", watch.Elapsed); 206 } 207 else 208 { 209 Console.WriteLine("批量實體查詢失敗,耗時:{0},測試終止。", watch.Elapsed); 210 return false; 211 } 212 213 //批量更新客戶信息 214 watch.Restart(); 215 UpdateCustomers(lastCustomers); 216 lastCustomers = RetrieveCustomers(count).ToList(); 217 if (!lastCustomers.Select(m => m.Address).Intersect(customers.Select(m => m.Address)).Any()) 218 { 219 Console.WriteLine("批量實體更新成功,耗時:{0}", watch.Elapsed); 220 } 221 else 222 { 223 Console.WriteLine("批量實體更新失敗,耗時:{0},測試終止。", watch.Elapsed); 224 return false; 225 } 226 227 //批量刪除本次添加的客戶信息 228 List<int> customerIds = lastCustomers.Select(m => m.CustomerId).ToList(); 229 watch.Restart(); 230 DeleteCustomers(customerIds); 231 watch.Stop(); 232 lastCustomers = RetrieveCustomers(count).ToList(); 233 if (!lastCustomers.Select(m => m.CustomerName).Intersect(customers.Select(m => m.CustomerName)).Any()) 234 { 235 Console.WriteLine("批量實體刪除成功,耗時:{0}", watch.Elapsed); 236 } 237 else 238 { 239 Console.WriteLine("批量實體刪除失敗,耗時:{0},測試終止。", watch.Elapsed); 240 return false; 241 } 242 return true; 243 } 244 245 private void RetrieveComplex() 246 { 247 Stopwatch watch = new Stopwatch(); 248 watch.Restart(); 249 List<OrderView> orderViews = RetrieveOrderViews().ToList(); 250 watch.Stop(); 251 Console.WriteLine("復雜查詢執行成功,耗時{1},共獲取{0}個訂單視圖信息。", watch.Elapsed, orderViews.Count); 252 } 253 254 #endregion 255 256 #region 公共方法 257 258 /// <summary> 259 /// 開始測試工作,主要對 Customer 表進行操作,工作順序為增、查、改、刪,進行單個操作,批量操作與復雜查詢 260 /// </summary> 261 public void Work() 262 { 263 bool isContinue = SingleCrudTest(); 264 if (!isContinue) 265 { 266 return; 267 } 268 isContinue = MultipleCrudTest(); 269 if (!isContinue) 270 { 271 return; 272 } 273 RetrieveComplex(); 274 } 275 276 #endregion 277 } 278 }
四、如何使用(以EntityFramework為例)
下面,我就以EntityFramework 4.4 來演示一下怎樣使用這個測試框架。可能有同學會說,EF6都出來了,為什么還使用4.4版本?原因一、.net4.0只支持到4.4版本,原因二、windows 2003 只支持到 .net 4.0,原因三、我正在使用的是4.4版本。如果你覺得4.4版本 out 了,可以自己去實現一個 EF6的測試示例,呵呵。廢話不多說,下面我們來實現 EntityFramework 4.4 的測試示例。
(一) 在項目中建立專屬文件夾
為了更好的管理各個數據訪問方案的實現代碼,也為防止項目結構混亂,各個方案的代碼應在自己的文件夾內實現。在這里,創建一個名為EntityFramework的文件夾
(二) 在文件夾內實現數據操作的基礎准備
首先要進行相應數據訪問方案的基礎准備,比如ado.net方案,可能需要一個SqlHelper的輔助操作類,又或者其他ORM,需要進行數據映射,生成數據實體等等。對於EntityFramework,只需要實現一個數據上下文類即可:
1 namespace DataTestFramework.EntityFramework 2 { 3 public class EFDbContext : DbContext 4 { 5 public EFDbContext(string connectionStringOrName) 6 : base(connectionStringOrName) { } 7 8 public DbSet<Customer> Customers { get; set; } 9 10 public DbSet<Category> Categories { get; set; } 11 12 public DbSet<Product> Products { get; set; } 13 14 public DbSet<Order> Orders { get; set; } 15 16 public DbSet<OrderDetail> OrderDetails { get; set; } 17 18 protected override void OnModelCreating(DbModelBuilder modelBuilder) 19 { 20 modelBuilder.Entity<Product>().HasRequired(m => m.Category).WithMany(n => n.Products).HasForeignKey(m => m.CategoryId); 21 modelBuilder.Entity<Order>().HasRequired(m => m.Customer).WithMany(n => n.Orders).HasForeignKey(m => m.CustomerId); 22 modelBuilder.Entity<OrderDetail>().HasRequired(m => m.Product).WithMany(n => n.OrderDetails).HasForeignKey(m => m.ProductId); 23 modelBuilder.Entity<OrderDetail>().HasRequired(m => m.Order).WithMany(n => n.OrderDetails).HasForeignKey(m => m.OrderId); 24 } 25 } 26 }
特別說明:項目中已經添加了測試所需的POCO實體類,如果這些類不符合具體的數據訪問方案的要求(比如實體類是代碼生成器來生成的),可以自行在自己的文件夾中定義需要的實體類,在數據訪問實現中再把操作結果轉換為系統定義的POCO實體類,以進行數據操作結果的驗證。
(三) 繼承TesterBase實現自己的Tester類
測試基類 TesterBase 中已定義了需要實現的測試用例,需要繼承這個基類,實現相應數據訪問方案的具體操作實現。對於 EntityFramework,實現如下:
1 namespace DataTestFramework.EntityFramework 2 { 3 /// <summary> 4 /// EntityFramework測試類 5 /// </summary> 6 public class EntityFrameworkTester : TesterBase 7 { 8 private const int _pageSize = 300; 9 10 #region 單個操作 11 12 /// <summary> 13 /// 添加單個客戶信息 14 /// </summary> 15 /// <param name="customer">待添加的客戶信息</param> 16 protected override void CreateCustomer(Customer customer) 17 { 18 using (EFDbContext db = new EFDbContext(ConnectionString)) 19 { 20 db.Customers.Add(customer); 21 db.SaveChanges(); 22 } 23 } 24 25 /// <summary> 26 /// 獲取指定名稱的客戶信息 27 /// </summary> 28 /// <param name="customerName">帶Token的客戶名稱</param> 29 /// <returns>指定名稱的客戶信息,不存在時返回null</returns> 30 protected override Customer RetrieveCustomer(string customerName) 31 { 32 using (EFDbContext db = new EFDbContext(ConnectionString)) 33 { 34 return db.Customers.SingleOrDefault(m => m.CustomerName == customerName); 35 } 36 } 37 38 /// <summary> 39 /// 更新指定客戶信息的 PostalCode="100000",Tel="13800138000" 40 /// </summary> 41 /// <param name="customer">待更新的客戶信息</param> 42 protected override void UpdateCustomer(Customer customer) 43 { 44 customer.PostalCode = "100000"; 45 customer.Tel = "13800138000"; 46 using (EFDbContext db = new EFDbContext(ConnectionString)) 47 { 48 if (db.Entry(customer).State == EntityState.Detached) 49 { 50 db.Customers.Attach(customer); 51 db.Entry(customer).State = EntityState.Modified; 52 } 53 db.SaveChanges(); 54 } 55 } 56 57 /// <summary> 58 /// 刪除指定客戶信息 59 /// </summary> 60 /// <param name="customerId">客戶編號</param> 61 protected override void DeleteCustomer(int customerId) 62 { 63 using (EFDbContext db = new EFDbContext(ConnectionString)) 64 { 65 Customer customer = db.Customers.SingleOrDefault(m => m.CustomerId == customerId); 66 if (customer == null) 67 { 68 return; 69 } 70 db.Customers.Remove(customer); 71 db.SaveChanges(); 72 } 73 } 74 75 #endregion 76 77 #region 批量操作 78 79 /// <summary> 80 /// 批量添加客戶信息 81 /// </summary> 82 /// <param name="customers">待添加的客戶信息集合</param> 83 protected override void CreateCustomers(IEnumerable<Customer> customers) 84 { 85 using (EFDbContext db = new EFDbContext(ConnectionString)) 86 { 87 try 88 { 89 db.Configuration.AutoDetectChangesEnabled = false; 90 List<Customer> customerList = customers as List<Customer> ?? customers.ToList(); 91 int pageCount = customerList.Count / _pageSize; 92 pageCount = customerList.Count % _pageSize > 0 ? pageCount + 1 : pageCount; 93 for (int index = 0; index < pageCount; index++) 94 { 95 List<Customer> pageData = customerList.Skip(index * _pageSize).Take(_pageSize).ToList(); 96 foreach (Customer customer in pageData) 97 { 98 db.Customers.Add(customer); 99 } 100 db.SaveChanges(); 101 } 102 } 103 finally 104 { 105 db.Configuration.AutoDetectChangesEnabled = true; 106 } 107 } 108 } 109 110 /// <summary> 111 /// 反向(先倒序排序)獲取指定數量的客戶信息 112 /// </summary> 113 /// <param name="count">要獲取的數量</param> 114 /// <returns>獲取的客戶信息集合</returns> 115 protected override IEnumerable<Customer> RetrieveCustomers(int count) 116 { 117 using (EFDbContext db = new EFDbContext(ConnectionString)) 118 { 119 return db.Customers.OrderByDescending(m => m.CustomerId).Take(count).ToList(); 120 } 121 } 122 123 /// <summary> 124 /// 更新指定的多個客戶信息,在Address后面加上當前客戶的CustomerName信息 125 /// </summary> 126 /// <param name="customers">待更新的客戶信息</param> 127 protected override void UpdateCustomers(IEnumerable<Customer> customers) 128 { 129 using (EFDbContext db = new EFDbContext(ConnectionString)) 130 { 131 List<int> ids = customers.Select(m => m.CustomerId).ToList(); 132 int pageCount = ids.Count / _pageSize; 133 pageCount = ids.Count % _pageSize > 0 ? pageCount + 1 : pageCount; 134 for (int index = 0; index < pageCount; index++) 135 { 136 List<int> pageIds = ids.Skip(index * _pageSize).Take(_pageSize).ToList(); 137 db.Customers.Update(m => pageIds.Contains(m.CustomerId), n => new Customer 138 { 139 Address = n.Address + n.CustomerName 140 }); 141 } 142 } 143 } 144 145 /// <summary> 146 /// 批量刪除指定編號的客戶信息 147 /// </summary> 148 /// <param name="customerIds">待刪除的客戶編號集合</param> 149 protected override void DeleteCustomers(IEnumerable<int> customerIds) 150 { 151 List<int> ids = customerIds as List<int> ?? customerIds.ToList(); 152 using (EFDbContext db = new EFDbContext(ConnectionString)) 153 { 154 int pageCount = ids.Count / _pageSize; 155 pageCount = ids.Count % _pageSize > 0 ? pageCount + 1 : pageCount; 156 for (int index = 0; index < pageCount; index++) 157 { 158 List<int> pageIds = ids.Skip(index * _pageSize).Take(_pageSize).ToList(); 159 db.Customers.Delete(m => pageIds.Contains(m.CustomerId)); 160 } 161 } 162 } 163 164 #endregion 165 166 #region 復雜查詢 167 168 /// <summary> 169 /// 查詢所有已完成(Finished == true)的訂單,構建訂單視圖模型 170 /// </summary> 171 /// <returns>滿足條件的訂單視圖模型集合</returns> 172 protected override IEnumerable<OrderView> RetrieveOrderViews() 173 { 174 using (EFDbContext db = new EFDbContext(ConnectionString)) 175 { 176 var orders = db.Orders.Where(m => m.Finished).Select(m => new 177 { 178 m.OrderId, 179 m.OrderDate, 180 m.SumMoney, 181 m.Finished, 182 Customer = new { m.Customer.CustomerName }, 183 ProductNames = m.OrderDetails.Select(n => n.Product.ProductName) 184 }).ToList(); 185 return orders.Select(order => new OrderView 186 { 187 OrderId = order.OrderId, 188 OrderDate = order.OrderDate, 189 SumMoney = order.SumMoney, 190 Finished = order.Finished, 191 CustomerName = order.Customer.CustomerName, 192 ProductNames = order.ProductNames.ExpandAndToString(",") 193 }).ToList(); 194 } 195 } 196 197 #endregion 198 } 199 }
對於批量修改,刪除操作,我使用了 EntityFramework.Extended.dll 程序集(可以在Nuget中獲取)來進行實現,該程序集對於每次批量操作只生成一條sql語句,而不會像EntityFramework提供的原生方法那樣批量N條數據就要生成N條sql語句。並且使用了逐部分處理的方式,以免進行大量數據處理時一次提交太多的數據導致性能低下。
復雜查詢的業務,對於EntityFramework來說,實現是相當簡單的,完全是要什么取什么,使用IQueryable<T>的擴展方法Select來按需獲取,然后用匿名類來裝載查詢結果,再根據這個查詢結果構造視圖模型的列表集合,具體實現如上面代碼中 166行-197行所示。
添加EntityFrameworkTester類之后的 Code Map如下所示:
(四) 添加測試調用入口
在Program類中,定義了很多的Method方法供調用,請不要修改Program類的Main方法,只需要在 HelpInfo 方法中添加使用哪個序號的Method進行調用的信息,然后在相應序號的Method方法中,如這里EntityFramework使用 Method01方法來作為入口,則進行如下修改:
- 修改HelpInfo方法,添加第6行所示代碼,其中1表示Method01的序號,后面為測試名稱:
1 private static void HelpInfo() 2 { 3 Console.WriteLine("=============幫-助-信-息============"); 4 Console.WriteLine("h.幫助信息"); 5 Console.WriteLine("0.退出程序"); 6 Console.WriteLine("1.性能測試——EF"); 7 Console.WriteLine("=============幫-助-信-息============"); 8 }
- 修改Method01方法,實例化Tester類,調用Work方法
1 private static void Method01() 2 { 3 Console.WriteLine("=============EntityFramework測試開始============"); 4 EntityFrameworkTester tester = new EntityFrameworkTester(); 5 tester.Work(); 6 Console.WriteLine("=============EntityFramework測試結束============"); 7 }
(五) 運行測試
至此,EntityFramework使用此測試框架的代碼已添加完畢,運行測試,忽略第一次運行的結果,分別執行5000條與10000條的批量數據操作,結果如下:
五、寫在后面
現在代碼示例中只有本人添加的一個EntityFramework示例,其他的方案比如ado.net及其他ORM期待高手來添加,請大家下載並編寫自己的數據訪問方案的實現,可在評論處留下載鏈接,也可直接發給我,我整理之后將在本系列后續中詳解實現過程,並進行綜合的對比。
另外,如果當前框架不能滿足部分數據訪問方案的要求導致無法添加測試的話,也希望能提出來,我再進行更新。
六、源碼下載
本框架的代碼已發布到 codeplex 上,同學們可以通過VS自帶的團隊項目管理功能(TFS)或SVN 隨時獲取最新代碼。
- 項目地址:https://datatester.codeplex.com
- TFS獲取地址:https://tfs.codeplex.com:443/tfs/TFS17,(似乎只有團隊成員可以用TFS方式)需要登錄,並在項目的 SOURCE CODE 標簽的 Connect 連接處獲取VS端的登錄用戶名,密碼為自己的賬號密碼。
- SVN獲取地址:https://datatester.svn.codeplex.com/svn
- 以上方案都不可用的情況下,可以項目的 SOURCE CODE 標簽頁選擇直接下載最新的源碼壓縮包