Lazy<T>在Entity Framework中的性能优化实践(附源码)
在使用EF的过程中,导航属性的lazy load机制,能够减少对数据库的不必要的访问。只有当你使用到导航属性的时候,才会访问数据库。但是这个只是对于单个实体而言,而不适用于显示列表数据的情况。
这篇文章介绍的是,使用Lazy<T>来提高显示列表页面的效率。
这里是相关的源代码 PerformanceTest.zip
阅读目录:
一、问题的描述
二、数据表和EF实体介绍
三、lazy load的性能
四、使用StudentExtensionRepository来提高效率
五、进一步改进,使用StudentExtensionRepository1来实现按需访问数据库
六、总结
一,问题的描述
在使用EF的过程中,导航属性的lazy load机制,能够减少对数据库的不必要的访问。只有当你使用到导航属性的时候,才会访问数据库。
比如有个学生Student实体,只有当我访问Student的StudentScore(成绩实体)导航属性的时候,才会去访问StudentScore表。
问题是导航属性只是解决了单个实体访问导航属性时候的性能问题,而在实际开发过程中,常常遇到的问题是,我要显示一个列表的Student的信息,并且每个Student都要获取StudentScore信息,怎么办?
也许有人会说,可以使用EF的eager loading, 在读出Student信息的时候,把StudentScore一起读取出来就可以了。
是的,就像下面这样就能解决当前的问题,但是会面临不够通用,无法应对变化的情况。
var students = context.Students
.Include(b => b.StudentScore)
.ToList();
如果遇到需求,不需要StudentScore信息的时候,我们就又要改回lazy load的方式.
如果遇到需求,需要显示Student另外一个关联属性StudentHealthy信息的时候,又必须让StudentScore用Lazy load方式,而StudentHealthy采用eager loading
下面就介绍如何使用Lazy<T>来解决这个兼顾代码设计和性能的问题.
二, 数据表和EF实体介绍
如下图所示,一共用到3张表:
Student: 学生信息表
StudentScores:学生成绩表
StudentHealthies: 学生健康状况表
3张表设计的是1对1关系,现实开发中当然会比这个复杂的多,但是这个对于描述我们的问题已经足够了。

EF中对应于着3张表的Model代码:
[Table("Student")]
public class Student
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public virtual StudentScore Score { get; set; }
public virtual StudentHealthy Healthy { get; set; }
}
public class StudentScore
{
public int Score { get; set; }
public int StudentId { get; set; }
[ForeignKey("StudentId")]
public virtual Student Student { get; set; }
}
public class StudentHealthy
{
public int Score{ get; set; }
public int StudentId { get; set; }
[ForeignKey("StudentId")]
public virtual Student Student { get; set; }
}
三, lazy load的性能
下图中的页面,读取Student的列表,同时访问Student的导航属性Score来获取成绩信息。
从MiniProfiler能看到,页面一共执行了4个Sql, 其中第一个Sql是从Student表中取出了3条数据,
其余的3个sql是每个Student访问导航属性而导致的数据库访问.
这里由于Student表中只有3条数据,如果有100条的话,就会导致1+100*3次数据访问,所以这种使用导航属性来获取数据的方式是不适合这种列表数据显示的
这里是页面View的代码:
@model IEnumerable<Student> <h1>Student With Score(Lazy Load)</h1>
<table border="1">
<tr><td>ID</td><td>Name</td><td>Age</td><td>Score</td></tr>
@foreach(var student in Model)
{
<tr>
<td>@student.Id</td>
<td>@student.Name</td>
<td>@student.Age</td>
<td>@student.Score.Score</td>
</tr>
}
</table>

四, 使用StudentExtensionRepository来提高效率
StudentExtensionRepository解决效率问题的思路是,如果你取出3个student信息的话,它会同时把StudentScore信息取出来。
先来看看StudentExtension的定义
public class StudentExtension
{
public Student Student { get; set; }
public StudentScore StudentScore { get; set; }
}
上面的StudentExtension用来保存Student信息,以及和该Student相关的StudentScore数据。
下面是StudentExtensionRepository.cs的具体内容,GetStudents方法中,先取得Student信息,然后获取相关的Score信息。
public class StudentExtensionRepository : IStudentExtensionRepository
{
private readonly IStudentRepository _studentRepository;
private readonly IRepository<StudentScore> _studentScoreRepository; public StudentExtensionRepository(IRepositoryCenter repositoryCenter)
{
_studentRepository = repositoryCenter.GetRepository<IStudentRepository>();
_studentScoreRepository = repositoryCenter.GetRepository<IRepository<StudentScore>>();
}
public IEnumerable<StudentExtension> GetStudents()
{
var result = new List<StudentExtension>(); var students = _studentRepository.GetStudents().ToList();
//取得score信息
var studentsIds = students.Select(s => s.Id);
var scores = _studentScoreRepository.Filter(s => studentsIds.Contains(s.StudentId)).ToList(); foreach (var student in students)
{
var temp = new StudentExtension
{
Student = student,
StudentScore = scores.FirstOrDefault(s => s.StudentId == student.Id)
};
result.Add(temp);
}
return result;
}
}
最后,使用新的StudentExtensionRepository,能够发现,显示同样的页面,只用了2个sql访问,减少来数据库的访问,提交了效率。

五, 进一步改进,使用StudentExtensionRepository1来实现按需访问数据库
上面的StudentExtensionRepository的实现,还是无法解决我们开始提出的问题:
如果遇到需求,不需要StudentScore信息的时候,我们就又要改回lazy load的方式.
如果遇到需求,需要显示Student另外一个关联属性StudentHealthy信息的时候,又必须让StudentScore用Lazy load方式,而StudentHealthy采用eager loading
如
果我们要显示Student的另外一个关联表StudentHealthy的数据,还是使用StudentExtensionRepository,会导
致对StudentScore表的访问,但是这是我们不需要的数据,如何改进StudentExtensionRepository的实现,来达到按需访
问数据库呢?
这个时候,就可以用到Lazy<T>来实现Lazy属性,只有真正用到属性的时候,才会真正的访问数据库。
看看我们改造后的StudentExtension1类, 该类同时包含了Score和Healthy信息,但是都是Lazy加载的。
public class StudentExtension1
{
public Student Student { get; set; }
//Lazy属性
public Lazy<StudentScore> StudentScoreLazy { get; set; }
//非Lazy属性,从Lazy属性中取值。当真正用到该属性的时候,会触发数据库访问
public StudentScore StudentScore { get { return StudentScoreLazy.Value; } } public Lazy<StudentHealthy> StudentHealthyLazy { get; set; }
public StudentHealthy StudentHealthy { get { return StudentHealthyLazy.Value; } }
}
改造后的StudentExtensionRepository1
public IEnumerable<StudentExtension1> GetStudents()
{
var result = new List<StudentExtension1>(); var students = _studentRepository.GetStudents().ToList();
var studentsIds = students.Select(s => s.Id);
//存储Score查询的结果,避免多次访问数据库来获取Score信息
List<StudentScore> scoreListTemp = null;
Func<int, StudentScore> getScoreFunc = id =>
{
//第一个Student来获取Score信息的时候,scoreListTemp会是null, 这个时候,会去访问数据库获取Score信息
//第二个以及以后的Student获取Score信息的时候,scoreListTemp已经有值了, 这样就不会再次访问数据库
if (scoreListTemp == null)
{
scoreListTemp =
_studentScoreRepository.Filter(
s => studentsIds.Contains(s.StudentId)).ToList();
}
return scoreListTemp.FirstOrDefault(s => s.StudentId == id);
}; //存储Healthy查询的结果,避免多次访问数据库来获取Healthy信息
List<StudentHealthy> healthyListTemp = null;
Func<int, StudentHealthy> getHealthyFunc = id =>
{
if (healthyListTemp == null)
{
healthyListTemp =
_studentHealthyRepository.Filter(
s => studentsIds.Contains(s.StudentId)).
ToList();
}
return
healthyListTemp.FirstOrDefault(s => s.StudentId == id);
}; foreach (var student in students)
{
var id = student.Id;
var temp = new StudentExtension1
{
Student = student,
StudentScoreLazy = new Lazy<StudentScore>(() => getScoreFunc(id)),
StudentHealthyLazy = new Lazy<StudentHealthy>(() => getHealthyFunc(id))
};
result.Add(temp);
}
return result;
}
}
接下来,创建2个不同的页面index2和index3, 他们的代码完全相同,只是View页面中访问的属性不同。
public ActionResult Index2()
{
var studentExtensionRepository1 = _repositoryCenter.GetRepository<IStudentExtensionRepository1>();
var students = studentExtensionRepository1.GetStudents().ToList();
return View(students);
} public ActionResult Index3()
{
var studentExtensionRepository1 = _repositoryCenter.GetRepository<IStudentExtensionRepository1>();
var students = studentExtensionRepository1.GetStudents().ToList();
return View(students);
}
index2.cshtml中,访问了StudentScore属性
<h1>Student With Score(Use the StudentExtensionRepository1)</h1>
<table border="1">
<tr><td>ID</td><td>Name</td><td>Age</td><td>Score</td></tr>
@foreach(var student in Model)
{
<tr>
<td>@student.Student.Id</td>
<td>@student.Student.Name</td>
<td>@student.Student.Age</td>
<td>@student.StudentScore.Score</td>
</tr>
}
</table>
index3.cshtml,访问了StudentHealthy属性
<h1>Student With Healthy(Use the StudentExtensionRepository1)</h1>
<table border="1">
<tr><td>ID</td><td>Name</td><td>Age</td><td>Healthy</td></tr>
@foreach(var student in Model)
{
<tr>
<td>@student.Student.Id</td>
<td>@student.Student.Name</td>
<td>@student.Student.Age</td>
<td>@student.StudentHealthy.Score</td>
</tr>
}
</table>
如下图,尽管Index2和Index3的代码中,获取数据的代码完全相同,但是实际的访问数据库的sql却是不同的。这是由于它们View中的显示数据的需求不同引起的。改造后的StudentExtensionRepository1能够根据所需来访问数据库, 来减少对于数据库的不必要的访问。同时它也能够适应更多的情况,无论是只显示Student表数据,还是要同时显示Student, StudentScore和StudentHealthy数据,StudentExtensionRepository1都能提供恰到好处的数据库访问。

六, 总结
以上是使用Lazy<T>结合EF的一次性能优化的闭门造车的尝试,欢迎各位多提意见。如果有更好的方式来,欢迎指教。
Lazy<T>在Entity Framework中的性能优化实践(附源码)的更多相关文章
- Lazy<T>在Entity Framework中的性能优化实践
Lazy<T>在Entity Framework中的性能优化实践(附源码) 2013-10-27 18:12 by JustRun, 328 阅读, 4 评论, 收藏, 编辑 在使用EF的 ...
- 分享基于Entity Framework的Repository模式设计(附源码)
关于Repository模式,在这篇文章中有介绍,Entity Framework返回IEnumerable还是IQueryable? 这篇文章介绍的是使用Entity Framework实现的Rep ...
- python3-开发进阶 django-rest framework 中的 版本操作(看源码解说)
今天我们来说一说rest framework 中的 版本 操作的详解 首先我们先回顾一下 rest framework的流程: 请求进来走view ,然后view调用视图的dispath函数 为了演示 ...
- V8中的快慢数组(附源码、图文更易理解😃)
接上一篇掘金 V8 中的快慢属性,本篇分析V8 中的快慢数组,了解数组全填充还是带孔.快慢数组.快慢转化.动态扩缩容等等.其实很多语言底层都采用类似的处理方式,比如:Golang中切片的append操 ...
- 【 js 性能优化】【源码学习】underscore throttle 与 debounce 节流
在看 underscore.js 源码的时候,接触到了这样两个方法,很有意思: 我先把实现的代码撂在下面,看不懂的可以先跳过,但是跳过可不是永远跳过哦- 一个是 throttle: _.throttl ...
- 学习Entity Framework 中的Code First
这是上周就写好的文章,是在公司浩哥的建议下写的,本来是部门里面分享求创新用的,这里贴出来分享给大家. 最近在对MVC的学习过程中,接触到了Code First这种新的设计模式,感觉很新颖,并且也体验到 ...
- Entity Framework在Asp.net MVC中的实现One Context Per Request(附源码)
上篇中"Entity Framework中的Identity map和Unit of Work模式", 由于EF中的Identity map和Unit of Work模式,EF体现 ...
- 转载:学习Entity Framework 中的Code First
看完觉得不错,适合作为学习资料,就转载过来了 原文链接:http://www.cnblogs.com/Wayou/archive/2012/09/20/EF_CodeFirst.html 这是上周就写 ...
- Entity framework 中Where、First、Count等查询函数使用时要注意
在.Net开发中,Entity framework是微软ORM架构的最佳官方工具.我们可以使用Lambda表达式在Entity framework中DbSet<T>类上直接做查询(比如使用 ...
随机推荐
- ScrollView与ListView冲突解决
正 常来说,在ScrollView添加一个ListView后在真机上只会显示ListView的一行多一点,我也不理解为什么会这样,后来我把 ListView的layout_height改成400dip ...
- JPA一对多循环引用的解决
说是解决,其实不是很完美的解决的,写出来只是想记录一下这个问题或者看一下有没有哪位仁兄会的,能否知道一二. 下面说说出现问题: 问题是这样的,当我查询一个一对多的实体的时候,工具直接就爆了,差不多我就 ...
- Light OJ 1031---Easy Game(区间DP)
题目链接 http://lightoj.com/volume_showproblem.php?problem=1031 Description You are playing a two player ...
- Java集合源码分析(一)
Java集合工具包位于Java.util包下,包含了很多常用的数据结构,如数组.链表.栈.队列.集合.哈希表等.学习Java集合框架下大致可以分为如下五个部分:List列表.Set集合.Map映射.迭 ...
- qt (5.60/5.70) 编译 QOCI 驱动
转载请注明原文链接http://www.cnblogs.com/majianming/p/5925105.html 在学习qt过程中,遇到了编译oracle驱动的问题,在开源协议下没有编译好的,那就只 ...
- .NET ORM 的 “SOD蜜”--零基础入门篇
PDF.NET SOD框架不仅仅是一个ORM,但是它的ORM功能是独具特色的,我在博客中已经多次介绍,但都是原理性的,可能不少初学的朋友还是觉得复杂,其实,SOD的ORM是很简单的.下面我们就采用流行 ...
- 与锤子手机HR的对话——创业没有联合创始人,CTO 等高管会把它当做自己的事业吗?
以下对话,是在被之前的锤子HR磨叽2周约面试折腾的火大的心态下进行…… 这个问题发到知乎,被一群人骂啊……cnblogs都是工程师,估计懂期权参加创业的同学多一些,有空前往知乎声援一下……在这里:ht ...
- Web 开发最有用的50款 jQuery 插件集锦——《图片特效篇》
<Web 开发最有用的50款 jQuery 插件集锦>系列文章向大家分享最具创新的50款 jQuery 插件,这些插件分成以下类别:网页布局插件,导航插件,表格插件,滑块和转盘插件,图表插 ...
- Manifesto – HTML5 离线应用程序缓存校验工具
Manifesto 是一个 HTML5 离线应用程序缓存校验工具,提供了快速校验 HTML5 manifest 文件有效性的方法.离线应用程序缓存在使用中最困难的部分之一就是无法正常工作的时候没有明显 ...
- SharePoint 部署解决方案Feature ID冲突
中文报错: 部署步骤“添加解决方案”中出现错误: 已在此服务器场中安装 ID 为 735efe4e-8b50-4310-b588-c6ae2ba0759f 的功能.请使用强制属性显式地重新安装此功能. ...