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>类上直接做查询(比如使用 ...
随机推荐
- Linux移植的一般过程
前一阵子在公司移植Linux2.6到一块ARM11的开发板上,下面粗略讲讲移植Linux的一般过程. 一开始的UBOOT的移植不多说了.UBOOT最后有两种方式进入Linux,一种是使用uImage, ...
- SqlServer中的自增的ID的最后的值:
SqlServer中的自增的ID的最后的值: SELECT SCOPE_IDENTITY() --返回插入到同一作用域中的 IDENTITY 列内的最后一个 IDENTITY 值.SELECT @@I ...
- C#写爬虫,版本V1.0
之前看了Sql Server中的基本数据类型,发现image这个类型还是比较特殊的. 于是乎就做了一个将图片以二进制流形式存储的程序http://www.cnblogs.com/JsonZhangAA ...
- 【Java每日一题】20161125
package Nov2016; import java.util.LinkedList; import java.util.List; public class Ques1125 { public ...
- Extjs 窗体居中,双重窗体弹出时清除父窗体的鼠标事件
这个是监控窗体缩放的事件 缩放中居中主要在 'beforeshow' 和 'destroy'两个事件里面监控 var EditTempWindow; Ext.EventManager.onWindow ...
- 2015暑假多校联合---CRB and His Birthday(01背包)
题目链接 http://acm.split.hdu.edu.cn/showproblem.php?pid=5410 Problem Description Today is CRB's birthda ...
- jinfo命令的使用
jinfo命令 该命令可以打印出java进程的配置信息:包括jvm参数,系统属性等用法: jinfo [ option ] pid jinfo [ option ] executable core j ...
- Java经典实例:按字符颠倒字符串
使用StringBuilder类的reverse()方法来实现. /** * Created by Frank */ public class StringRevChar { public stati ...
- java集合-集合大家族
在编写 Java 程序中,我们最常用的除了八种基本数据类型,String 对象外还有一个集合类,在我们的的程序中到处充斥着集合类的身影!Java 中集合大家族的成员实在是太丰富了,有常用的 Array ...
- SequenceInputStream
SequenceInputStream从名字上看, 他是一个序列字节输入流 既然是个序列 那么意味着 SequenceInputStream装着许多的输入流 所以 可以用他来合并文件 Sequence ...