假设你有一个父表(例如:汽车),其关联一个子表,例如轮子(一对多)。现在你想对于所有的父表汽车,遍历所有汽车,然后打印出来所有轮子的信息。默认的做法将是:
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 });
我们对结果添加监听,执行效果如下:
主表和子表对应的关联数据,绑定在了一起,不止代码简单,执行效率也高出了不少!