Entity Framework技术系列之5:延迟加载
前言
延迟加载也可以叫做按需加载,可以分两方面来理解,一方面指暂时不需要该数据,不用在当前马上加载,而可以推迟到使用它时再加载;另一方面指不确定是否将会需要该数据,所以暂时请不要加载,待确定需要后再加载它。延迟加载是一种很重要的数据访问特性,可以有效地减少与数据源的交互(注意,这里所提的交互不是指交互次数,而是指交互的数据量),从而提升程序性能。
接下来,我将针对上一篇文章中提出的DIY实体数据模型,讨论如何使其具有延迟加载特性。本文的所有测试均建立在已有的测试数据之上,测试数据准备代码如下:
1 [ClassInitialize()] 2 public static void MyClassInitialize(TestContext testContext) 3 { 4 using (var db = new EntityContext()) 5 { 6 var role = db.Roles.FirstOrDefault(); 7 if (role == null) 8 { 9 role = new Role(); 10 role.ID = Guid.NewGuid(); 11 role.Name = "员工"; 12 } 13 14 var user = db.Users.FirstOrDefault(); 15 if (user == null) 16 { 17 user = new User(); 18 user.ID = Guid.NewGuid(); 19 user.Account = "Apollo"; 20 user.Password = "11111111"; 21 user.Roles = new List<Role>() { role }; 22 23 var userDetail = new UserDetail(); 24 userDetail.ID = user.ID; 25 userDetail.Name = "Yilin"; 26 userDetail.Sex = "男"; 27 userDetail.Birthday = DateTime.Now; 28 29 user.UserDetail = userDetail; 30 31 db.Users.AddObject(user); 32 db.SaveChanges(); 33 } 34 } 35 }
即时加载
上一篇文章提出的DIY实体数据模型采用的是即时加载模式,它存在几点缺陷。首先,如果需要关联对象数据,必须在查询数据时显式指明,如下面的查询Lambda表达式中,通过使用Include(“UserDetail”)方法来指定需要加载User对象的UserDetail数据:
1 [TestMethod] 2 public void IncludeQueryTest() 3 { 4 using (var db = new EntityContext()) 5 { 6 var user = db.Users.Include("UserDetail").FirstOrDefault(o => o.Account == "Apollo"); 7 Assert.IsNotNull(user); 8 Assert.IsNotNull(user.UserDetail); 9 Assert.IsNotNull(user.Roles); 10 } 11 }
运行上面的测试,会发现前面两个断言都通过,第三个断言失败,这是因为没有显式指定加载User关联的Roles数据。
其次,即时加载会在查询时加载所有显式指示的加载项。来看即时加载测试代码运行情况与SQL Server Profiler工具截获的SQL对应图:
图1即时加载
从上图中可以得到两点信息,首先在执行Lambda表达式语句时,EF即时将其翻译成了SQL语句,并执行了查询操作;其次生成的SQL语句包括了所有显式指定的加载数据项。
延迟加载
要使DIY实体数据模型支持延迟加载特性,其实很简单,仅需要将关联对象属性声明为virtual即可:
1 public class User 2 { 3 public Guid ID { get; set; } 4 5 public string Account { get; set; } 6 7 public string Password { get; set; } 8 9 public virtual UserDetail UserDetail { get; set; } 10 11 public virtual IList<Role> Roles { get; set; } 12 }
测试方法:
1 [TestMethod] 2 public void QueryTest() 3 { 4 using (var db = new EntityContext()) 5 { 6 var user = db.Users.FirstOrDefault(o => o.Account == "Apollo"); 7 Assert.IsNotNull(user); 8 Assert.IsNotNull(user.UserDetail); 9 Assert.IsNotNull(user.Roles); 10 } 11 }
运行以上测试方法,所有断言全部通过,证明延迟加载模式下,不需要显式指定需加载的数据项。并且,在Lambda查询表达式语句执行后,并没有执行关联对象数据的查询操作,如下图所示:
图2延迟加载
而是在使用数据时才执行关联对象的数据查询操作,如下图所示:
图3按需加载
实践建议
延迟加载虽然有诸多优点,但并不是所有情景下都是最好的选择。滥用延迟加载,有时不但不能提升软件性能,反而会适得其反。在大多数情况下,作为程序开发人员,我们都清楚在当前的程序上下文环境中,有哪些关联数据将是需要的。此时,通过显式指定要加载的关联数据,可以强制Entity Framework一次性将所需数据全部取回,这样就能避免因延迟加载而导致的与数据源进行多次交互而带来的性能问题。所以,往往需要在加载数据量和数据交互次数上权衡后作出取舍。
有两种方式可以显式指定Entity Framework使用即时加载模式进行数据查询,一种是通过在实体对象上下文环境类的构造函数中设置是否支持延迟加载属性值,如下所示:
1 public EntityContext() 2 : this("name=Membership") 3 { 4 this.ContextOptions.LazyLoadingEnabled = true; 5 }
这种方法是作用于实体对象模型全局的,所以并不推荐。另一种方法就是前文提到的使用Include()方法显式指定需加载的关联对象属性,这种方法方便灵活,值得推荐:
1 var user = db.Users.Include("UserDetail").FirstOrDefault(o => o.Account == "Apollo");
总结
本文首先对DIY实体数据模型即时加载特性进行了验证;然后提出了使该实体数据模型支持延迟加载特性的方法,并对修改后的实体数据模型进行了延迟加载特性验证;最后给出了在项目开发中如何在即时加载和延迟加载两者中进行取舍,以及在延迟加载模式下临时执行即时加载的一些建议。希望本文对你了解和合理应用Entity Framework的即时加载和延迟加载有所帮助。
下一篇文章将分享如何ASP.NET页面中使用EntityDataSource数据源控件,轻松实现实体数据的绑定和持久化。