1.什麼叫延遲加載
字面上可以理解為,一個動作本該立即執行的動作,沒有立即執行2.從代碼上理解
static void Main(string[] args) { //執行該語句的時候,查看sql監視器,發現並沒有生成sql語句 IEnumerable<Student> stu = dbContext.Students.Where(s => s.Id == 1).Select(s => s); //只有當 使用的時候 ,才生成sql語句 Student student = stu.FirstOrDefault(); }
只有對象被使用了,才生成sql語句
3.尋找原因,什麼原因導致延遲加載
先理解兩個Where()方法:
a.集合的Where()
List<string> listStr = new List<string>() { "A", "BB", "CCC" }; string bb = listStr.Where(s => s.Length == 2).Select(s => s).FirstOrDefault();
轉到Where的定義,發現 集合 中的Where方法 實際上是IEnumerable的擴展方法,該接口繼承與IEnumerable
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
public interface IEnumerable<out T> : IEnumerable
b.DbSet中的Where方法
var stu = dbContext.Students.Where(s => s.Id == 1);
public partial class SchoolEntities : DbContext { public virtual DbSet<Student> Students { get; set; } }
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
public interface IQueryable<out T> : IEnumerable<T>, IQueryable, IEnumerable
public interface IQueryable : IEnumerable
c.IEnumerable與IQueryable的區別
var s1 = dbContext.Students.Where(s => s.Id == 1);
var s2 = s1.Where(s => s.Age > 0);
var s3 = s2.Select(s => s).FirstOrDefault();
用sql監視器查看,發現總共就執行了一次sql查詢
得出結論:
1.實現EF延遲加載 的 實際上是IQueryable上的擴展方法,更具體的話是DbQuery類型來實現的4.為什麼要有延遲加載
a.無法確定 本次查詢條件 是否 已經添加結束
DbQuery<Student> s1 = dbContext.Students.Where(s => s.Id == 1).Where(s => s.Age > 0) as DbQuery<Student>;每次添加 查詢條件的時候,都只是返回 包含所有添加條件的 DbQuery對象,只有最後使用的時候,才根據條件生成相應的sql語句
b.對於外鍵實體,按需加載
本次需要用到的兩張 具有 主外鍵關系的兩張表如下
var tea = dbContext.Teachers.Where(t => t.tId == 1); //生成sql語句,如圖 代碼一 Teacher teacher = tea.FirstOrDefault(); //生成sql語句,如圖 代碼二 string className = teacher.TeachClass.cName;
代碼一,如下圖:
SELECT TOP (1) [Extent1].[tId] AS [tId], [Extent1].[tName] AS [tName], [Extent1].[tAge] AS [tAge], [Extent1].[tClass] AS [tClass] FROM [dbo].[Teacher] AS [Extent1] WHERE 1 = [Extent1].[tId]
代碼二,如下圖:
exec sp_executesql N'SELECT [Extent1].[cId] AS [cId], [Extent1].[cName] AS [cName] FROM [dbo].[TeachClass] AS [Extent1] WHERE [Extent1].[cId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
觀察,得出結論:
1.按需加載:在第一次執行的時候,因為沒有用到外鍵屬性,所以生成sql語句的時候,不會去去查詢 TeachClass表c.按需加載的缺點:
實例代碼如下:
DbQuery<Teacher> teachers = dbContext.Teachers; StringBuilder sbTeacher=new StringBuilder(100); foreach (Teacher tea in teachers) { //每次調用 外鍵表Teachers上 的 外鍵實體時,都會去查詢數據庫 //EF有個優化,相同的外鍵實體只查一次,即TeachClass相同只查一次 sbTeacher.Append(tea.TeachClass.cName); }
生成的SQL腳本如下:
exec sp_executesql N'SELECT [Extent1].[cId] AS [cId], [Extent1].[cName] AS [cName] FROM [dbo].[TeachClass] AS [Extent1] WHERE [Extent1].[cId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
第二次,和第三次,因為TeachClass的值相同,則只查詢了一次
exec sp_executesql N'SELECT [Extent1].[cId] AS [cId], [Extent1].[cName] AS [cName] FROM [dbo].[TeachClass] AS [Extent1] WHERE [Extent1].[cId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=2
5.連接查詢
既然EF是只有用到外鍵實體的時候,才加載相應表,那麼如果我們要連接兩張表要怎麼做
a.通過Include方法
DbQuery<Teacher> teachers = dbContext.Teachers.Include("TeachClass"); StringBuilder sbTeacher=new StringBuilder(100); foreach (Teacher tea in teachers) { //只有第一次 查詢的使用,將數據查詢 並保存到內存中, //接下來的操作只是在內存中讀取,並沒有讀取數據庫 sbTeacher.Append(tea.TeachClass.cName); }
查看sql語句,發現 EF為我們生成 left outer join ,連接了兩張表
SELECT [Extent1].[tId] AS [tId], [Extent1].[tName] AS [tName], [Extent1].[tAge] AS [tAge], [Extent1].[tClass] AS [tClass], [Extent2].[cId] AS [cId], [Extent2].[cName] AS [cName] FROM [dbo].[Teacher] AS [Extent1] LEFT OUTER JOIN [dbo].[TeachClass] AS [Extent2] ON [Extent1].[tClass] = [Extent2].[cId]
b.生成 join 的另一種方式
var teachers = dbContext.Teachers.Select(t => new {tName = t.tName, ClassName = t.TeachClass.cName}).ToList();
生成的sql語句如下
SELECT 1 AS [C1], [Extent1].[tName] AS [tName], [Extent2].[cName] AS [cName] FROM [dbo].[Teacher] AS [Extent1] LEFT OUTER JOIN [dbo].[TeachClass] AS [Extent2] ON [Extent1].[tClass] = [Extent2].[cId]