Entity Framework提供了三种加载相关实体的方法:Lazy Loading,Eager Loading和Explicit Loading。首先我们先来看一下MSDN对三种加载实体方法的定义。
Lazy Loading:对于这种类型的加载,在您访问导航属性时,会从数据源自动加载相关实体。 使用此加载类型时,请注意,如果实体尚未在 ObjectContext 中,则您访问的每个导航属性都会导致针对数据源执行一个单独的查询。
Eager Loading:当您了解应用程序需要的相关实体的图形的确切形状时,可以使用 ObjectQuery 的 Include 方法来定义查询路径,此查询路径控制将哪些相关实体作为初始查询的一部分返回。 当定义查询路径时,仅需对数据库请求一次,即可在单个结果集中返回查询路径所定义的所有实体,并且属于在路径中定义的类型的所有相关实体将随查询返回的每个对象一起加载。
Explicit Loading:将实体显式加载到 ObjectContext 需要多次往返数据库,并且可能需要多个活动结果集,但是返回的数据量仅限于所加载的实体。 可以对 EntityCollection或 EntityReference 使用 Load 方法或对 ObjectContext 使用 LoadProperty 方法,以便从数据源显式检索相关实体。 对于 Load 方法的每个调用都会打开与数据库的连接,以检索相关信息。 这可确保在没有对相关实体的显式请求时,始终不会执行查询。
下面我们就以上三种加载方式来一一进行测试
在测试之前,我们先建立一个测试用的数据库,并在其中插入一些数据:
图1
Lazy Loading
在Entity Framework4.0及其以后版本,LazyLoading是默认打开的,从数据库生成Model后,我们可以在EDMX文件空白处单击,并在属性窗口看到这一设置:
图2
也可以以XML形式打开EDMX文件,在CSDL部分看到这一设置:
1 <!-- CSDL content -->
2 <edmx:ConceptualModels>
3 <Schema Namespace="TestModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
4 <EntityContainer Name="TestEntities" annotation:LazyLoadingEnabled="true">
5 <EntitySet Name="Players" EntityType="TestModel.Player" />
6 <EntitySet Name="PlayerDetails" EntityType="TestModel.PlayerDetail" />
7 <EntitySet Name="Teams" EntityType="TestModel.Team" />
8 <AssociationSet Name="FK_Player_Team1" Association="TestModel.FK_Player_Team1">
9 <End Role="Team" EntitySet="Teams" />
10 <End Role="Player" EntitySet="Players" />
11 </AssociationSet>
12 <AssociationSet Name="FK_PlayerDetails_Player" Association="TestModel.FK_PlayerDetails_Player">
13 <End Role="Player" EntitySet="Players" />
14 <End Role="PlayerDetails" EntitySet="PlayerDetails" />
15 </AssociationSet>
16 </EntityContainer>
注意:Lazy Loading的设置是针对所有Model的,并非某一个Model。
下面,我们写一段简单的代码来测试一下Lazy Loading:
using (var context = new TestEntities())
{
IQueryable<Team> teams = from t in context.Teams select t;
foreach (Team t in teams)
{
Console.WriteLine(t.Players.Count());
}
Console.Read();
}
运行后的结果如下图:
图3
我们可以看到,在query语句中,我们只是要求返回所有的team信息,并没有像数据库请求加载player的信息,但在Foreach语句中,我们要求打印出每支team的player数量,却成功了,这就是Lazy Loading实现的效果。实际上,当执行Count语句时,程序会再去取请求数据库,返回player信息,这也就是说,如果我们有100支球队,程序会访问100次数据库来执行此操作。
下面我们关闭Lazy Loading来看看效果。关闭Lazy Loading有多种方法,我们可以在图2的属性窗口直接将Lazy Loading Enabled设置为False,也可以在XML代码中将Lazy Loading Enabled赋值False,以下我们用程序代码来关闭Lazy Loading并执行上面代码来看一下效果:
using (var context = new TestEntities())
{
//Disable Lazy Loading
context.ContextOptions.LazyLoadingEnabled = false; IQueryable<Team> teams = from t in context.Teams select t;
foreach (Team t in teams)
{
Console.WriteLine(t.Players.Count());
}
Console.Read();
}
执行结果如下:
图4
从执行结果我们可以看到,当执行Foreach语句时,程序并没有去查询数据库,而我们的query语句又没有向数据库请求关于player的信息,故无法打印出player的数量。
最后我们来总结一下Lazy Loading的优势和劣势:当打开Lazy Loading时,我们可以不用去在意某实体是否已经加载,不会出现在调用某一实体时,出现null的尴尬,省去程序员不少心力,但同时劣势也非常明显,如果我们有大量实体,且频繁去调用相关实体,程序就会频繁地访问数据库,这很显然地会影响程序的性能。
下一次我们会来分析关闭Lazy Loading的情况下,如何显示加载相关实体,即Explicit Loading。
上一回我们在《Entity Framework加载相关实体——Lazy Loading》分析了Lazy Loading,这一回我们来分析一下在关闭Lazy Loading的情况下,如果显式加载实体。
数据库我们依旧使用Lazy Loading中使用的数据库。之前我们分析过来,当Lazy Loading关闭时,执行以下代码是无法得到结果的,因为Player的信息并没有被加载。
1 using (TestEntities context = new TestEntities())
2 {
3 IQueryable<Team> teams = from t in context.Teams select t;
4 foreach (Team t in teams)
5 {
6 Console.WriteLine(t.Players.Count());
7 }
8 Console.Read();
9 }
如果我们想要得到t.Players.Count()的结果,我们可以显式地加载Player信息:
1 using (TestEntities context = new TestEntities())
2 {
3 IQueryable<Team> teams = from t in context.Teams select t;
4 foreach (Team t in teams)
5 {
6 //explicitly loading players
7 t.Players.Load();
8 Console.WriteLine(t.Players.Count());
9 }
10 Console.Read();
11 }
当t.Players.Load()执行时,Object Service会向数据库发出请求返回该team的所有player信息。
我们也可以从many端加载与它相对应的one端,在这个例子中也就是从Player加载与该player相对应的team信息。这里需要用到TeamReference:
1 using (TestEntities context = new TestEntities())
2 {
3 IQueryable<Player> players = from p in context.Players select p;
4 foreach (Player p in players)
5 {
6 if (p.Age > 30)
7 {
8 p.TeamReference.Load();
9 Console.WriteLine(p.PlayerName + " -> " + p.Team.TeamName);
10 }
11 }
12 Console.Read();
13 }
执行结果如下:
当我们打出p.后会发现,智能提示中出现了Team和TeamReference,这里我们选择TeamReference,因为EF将p.Team认为就是一个Team实体。
无论使用Lazy Loading还是将Load方法放入foreach循环语句,都会导致程序频繁访问数据库,导致程序性能下降。我们可以选择性地加载需要的实体,例如上面代码,我们只加载年龄超过30岁的球员所在球队的信息。
至此,Lazy Loading和Explicit Loading都已经分析完了,下一次我们来分析加载相关实体的最后一种形式Eager Loading.
在前面两回我们分别分析了Lazy Loading和Explicit Loading,这一回我们来分析一下Eager Loading。
在某些情况下,我们可能事先知道要需要加载某些实体的相关实体,这时我们就可以用Include方法来在加载实体的查询语句中把相关实体也一并查询出来。下面我们还用前两回使用的数据库来写一个例子来分析一下Eager Loading:
1 using (var context = new TestEntities())
2 {
3 var players = from p in context.Players.Include("Team").Include("PlayerDetails") where (p.PlayerDetails.Any(d => d.Height > 200)) select p;
4 foreach (var v in players)
5 {
6 string output = string.Format("Team: {0} Player: {1}", v.Team.TeamName, v.PlayerName);
7 Console.WriteLine(output);
8 }
9 Console.Read();
10 }
以上代码,我们查询出了身高在2米以上的球员的名字和所在球队,在查询Player信息的同时,也查询了Team表和PlayerDetails表的信息,将三个表的信息全部加载了进来。这里我们需要注意,我们可以控制将哪些相关实体加载入内,但不能够对Include进来的实体进行筛选,如以下代码:
1 var teams = from t in context.Teams.Include("Players") where (t.TeamID == 1) select t;
我们可以选择加载哪些球队,却不能选择加载球队的哪些球员,该球队的所有球员都将被加载进来。
至此,Entity Framework加载相关实体的三种形式全部分析完毕!