假設你有一個父表(例如:汽車),其關聯一個子表,例如輪子(一對多)。現在你想對於所有的父表汽車,遍歷所有汽車,然后打印出來所有輪子的信息。默認的做法將是:
SELECT CarId FROM Cars;
然后對於每個汽車:
SELECT * FROM Wheel WHERE CarId = ?
這會SELECT 2個表一共N(主表的行數)+1(父表)次,故稱為SELECT N+1問題。
考察下面的代碼。假設ProvinceMeeting是一個會議表,MeetSign是另外一個會議簽到表,ProvinceMeeting和MeetSign是一對多的關系:
不推薦的寫法:
var Ids = container.ProvinceMeeting.Select(f => f.Id).ToList(); var SignEntities = container.MeetingSign.ToList(); foreach (var Id in Ids) { var sign = container.MeetingSign.Where(f => f.MeetingId == Id); }
每次循環都會連接數據庫,執行一條sql語句,select N + 1問題,很消耗性能。
改善后的寫法:
//這是我曾經的寫法,先遍歷獲取主表和所有的子表數據存儲在數組中,然后再foreach循環查詢取出每個主表對應的子表信息, var Entities = container.ProvinceMeeting.ToList(); var SignEntities = container.MeetingSign.ToList(); foreach (var item in Entities) { var sign = SignEntities.Where(f => f.MeetingId == item.Id); }
我們知道foreach會強制LINQ執行,於是,我們可以想象這也是一個SELECT N+1問題的例子:先獲得所有ProvinceMeeting(SELECT * FROM ProvinceMeeting),然后遍歷,再去SELECT 表MeetingSign,共SELECT N+1次。(當然這里我把數據都啦取出來,儲存在內存中,進行操作相當也查詢了兩次,但隨后我還要遍歷循環操作數組)。
解決方法:使用一個匿名對象作為中間表格,預先將兩個表join到一起:
最佳寫法:
var results = container.ProvinceMeeting.Select(f => new { f.Id, son = f.MeetingSign });
我們對結果添加監聽,執行效果如下:
主表和子表對應的關聯數據,綁定在了一起,不止代碼簡單,執行效率也高出了不少!