Lazy<T>在Entity Framework中的性能优化实践
Lazy<T>在Entity Framework中的性能优化实践(附源码)
2013-10-27 18:12 by JustRun, 328 阅读, 4 评论, 收藏, 编辑
在使用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中的性能优化实践(附源码)
在使用EF的过程中,导航属性的lazy load机制,能够减少对数据库的不必要的访问.只有当你使用到导航属性的时候,才会访问数据库.但是这个只是对于单个实体而言,而不适用于显示列表数据的情况. 这篇文 ...
- 学习Entity Framework 中的Code First
这是上周就写好的文章,是在公司浩哥的建议下写的,本来是部门里面分享求创新用的,这里贴出来分享给大家. 最近在对MVC的学习过程中,接触到了Code First这种新的设计模式,感觉很新颖,并且也体验到 ...
- 转载:学习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>类上直接做查询(比如使用 ...
- "Entity Framework数据插入性能追踪"读后总结
园友莱布尼茨写了一篇<Entity Framework数据插入性能追踪>的文章,我感觉不错,至少他提出了问题,写了出来,引起了大家的讨论,这就是一个氛围.读完文章+评论,于是我自己也写了个 ...
- 在Entity Framework 中实现继承关系映射到数据库表
继承关系映射到数据库表中有多种方式: 第一种:TPH(table-per-hiaerachy) 每一层次一张表 (只有一张表) 仅使用名为父类的类型名的一张表,它包含了各个子类的所有属性信息,使用区分 ...
- Entity Framework 教程——Entity Framework中的实体类型
Entity Framework中的实体类型 : 在之前的章节中我们介绍过从已有的数据库中创建EDM,它包含数据库中每个表所对应的实体.在EF 5.0/6.0中,存在POCO 实体和动态代理实体两种. ...
- 关于Entity Framework中的Attached报错相关解决方案的总结
关于Entity Framework中的Attached报错的问题,我这里分为以下几种类型,每种类型我都给出相应的解决方案,希望能给大家带来一些的帮助,当然作为读者的您如果觉得有不同的意见或更好的方法 ...
- 关于Entity Framework中的Attached报错的完美解决方案终极版
之前发表过一篇文章题为<关于Entity Framework中的Attached报错的完美解决方案>,那篇文章确实能解决单个实体在进行更新.删除时Attached的报错,注意我这里说的单个 ...
随机推荐
- c#有关udp可靠传输(包传输数据包) 升级
在c#有关udp可靠传输(包传输数据包)我们讨论,UDP包的发送,可是上一个程序有一个问题.就是数据比較大.一个Message类序列化后都有2048B,而实际的数据量也就只是 50B罢了,这就说明当中 ...
- HDU 4107 线段树
给出N个节点,M次操作,和p 每次操作 对l-r区间的每一个节点+c,若节点值>=p,则加2*c: 结点存当前区间伤害最小值,最大值,以及lazy操作.更新到假设最小值大于等于P,或者最大值小于 ...
- ASP.NET 5- 1
ASP.NET 5 入门(1) - 建立和开发ASP.NET 5 项目 ASP.NET入门(1) - 建立和开发ASP.NET 5 项目 建立项目 首先,目前只有VS 2015支持开发最新的ASP ...
- Cracking Microservices practices
微服务最佳实践 英文原文:Cracking Microservices practices 在我还不知道什么叫微服务架构的时候我就使用过它.以前,我写了一些管道程序(pipeline applicat ...
- IE6下jquery ajax报error的原因
用jquery ajax()方法,在其他浏览都通过,IE7以上都通过,唯独在ie6不行. 我这边的解决方案是:必须保证ajax里面的所有数字为小写,ie6对大小写敏感. 错误: $.ajax({ ur ...
- 上海及周边地区产品技术创业QQ群:98905958
创业是一件骄傲的事,每一个行业里最棒人才都应该创业或參与创业或以一个创业者的心态进行职业远景规划: 创业是一件寻常的事,跟上班打工一样寻常,没有什么必须的前置条件才干够開始,也没有什么前置条件能保证我 ...
- EA强大的绘图工具---设计数据库表格
关于EA这个优秀的软件是从师哥哪里听来的,自己瞎点了点,感觉也没什么.近期和和智福加上一个师哥合作敲机房收费系统时,想到之前听人说EA非常强大,便随便找了找关于EA使用的帮助手冊.果然惊喜-- 如题, ...
- 苹果iOS苹果公司的手机用户都有权索赔
大家知道.手机中的操作系统(基础软件)存储在手机固(firm,ware)之中,一般而言,手机用户自己是不能修改的. 苹果iOS手机的系统后门(服务程序)也存储在手机固件之中.手机用户自己是无法删除的. ...
- 推荐两个针对github的chrome插件
作为一只程序猿,在github上找代码.看代码是再正常不过的事情了.这时候有个工具可以方便你翻看代码,想必是极好的. Sourcegraph for GitHub 这个插件允许你像使用IDE那样浏览代 ...
- PHP 3:从Login界面谈PHP标记
原文:PHP 3:从Login界面谈PHP标记 前一篇文章简要介绍了此实例.OK,我们就从第一个页面Login页面入手吧.还是看看界面怎么样,是不是很想指导它到底是如何实现的呢?好的,看看其代码吧: ...