ASP.NET MVC 的開發原則有個 SoC (Separation of Concern) 的觀念,我們在開發較大型的 ASP.NET MVC 應用程式時會特別將資料存取層(Data Access Layer) 再細分為兩個層次,分別是 Repository Layer (資料倉儲層) 負責資料存取與欄位格式驗證,與 Service Layer (服務提供層) 負責資料篩選與商業邏輯驗證,但分層之後遇到了一個之前沒想過的問題,進而導致 LINQ to SQL 查詢效能不彰。
我寫了一個很小的專案來驗證這個問題:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication2
{
class Program
{
private static MyDataContext db;
static void Main(string[] args)
{
db = new MyDataContext();
db.Log = Console.Out;
var all = GetCustomers();
Console.WriteLine("Total Rows: " + all.Count());
Console.WriteLine();
var page1 = all.Where(p => p.LastName == "Liu");
Console.WriteLine("Total Rows: " + page1.Count());
}
private static IEnumerable<Customer> GetCustomers()
{
return db.Customers;
}
}
}
由上述程式可得知,我定義了一個 GetCustomers() 方法,用來回傳資料庫中所有 Customers 表格的資料,我為了將回傳的資料抽象化,所以改用 IEnumerable<T> 型別回傳,執行的結果如下:
兩次執行 Count() 查詢時都沒有最佳化,而是將所有資料都回傳回來變成 Entity 物件後才對這些物件進行彙整運算。
這時,我們稍微修改一下程式,在透過 GetCustomers() 取回結果的時候加上 AsQueryable() 擴充方法將結果轉型成 IQueryable<T> 型別,請看以下程式第 16 行的地方:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication2
{
class Program
{
private static MyDataContext db;
static void Main(string[] args)
{
db = new MyDataContext();
db.Log = Console.Out;
var all = GetCustomers().AsQueryable();
Console.WriteLine("Total Rows: " + all.Count());
Console.WriteLine();
var page1 = all.Where(p => p.LastName == "Liu");
Console.WriteLine("Total Rows: " + page1.Count());
}
private static IEnumerable<Customer> GetCustomers()
{
return db.Customers;
}
}
}
我們看一下修改過後的結果:
你會發現雖然結果一樣,但是程式執行的效率卻差非常多,如果你的資料表有 100 萬筆資料時,那才真的會「很有感覺」,所以在撰寫程式的時候必須特別小心這個地方。
除此之外,就算你用的是 Entity Framework 也是會有一樣的狀況,都需要先將 IEnumerable<T> 轉型成 IQueryable<T> 才能提高執行效率!(需透過 SQL Server Profiler 分析實際執行的 T-SQL 語法)
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication3
{
class Program
{
private static AdventureWorksLTEntities db
= new AdventureWorksLTEntities();
static void Main(string[] args)
{
var all = GetCustomers().AsQueryable();
Console.WriteLine("Total Rows: " + all.Count());
Console.WriteLine();
var page1 = all.Where(p => p.LastName == "Liu");
Console.WriteLine("Total Rows: " + page1.Count());
}
private static IEnumerable<Customer> GetCustomers()
{
return db.Customers;
}
}
}
相關連結