The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

解決 N-Tier 多層架構設計下 LINQ to SQL 效能不彰的問題

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;
        }
    }
}

相關連結