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一起读取出来就可以了。

是的,就像下面这样就能解决当前的问题,但是会面临不够通用,无法应对变化的情况。

  1. var students = context.Students
  2. .Include(b => b.StudentScore)
  3. .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代码:

  1. [Table("Student")]
  2. public class Student
  3. {
  4. [Key]
  5. public int Id { get; set; }
  6. public string Name { get; set; }
  7. public int Age { get; set; }
  8.  
  9. public virtual StudentScore Score { get; set; }
  10. public virtual StudentHealthy Healthy { get; set; }
  11. }
  12.  
  13. public class StudentScore
  14. {
  15. public int Score { get; set; }
  16. public int StudentId { get; set; }
  17. [ForeignKey("StudentId")]
  18. public virtual Student Student { get; set; }
  19. }
  20.  
  21. public class StudentHealthy
  22. {
  23. public int Score{ get; set; }
  24. public int StudentId { get; set; }
  25. [ForeignKey("StudentId")]
  26. public virtual Student Student { get; set; }
  27. }

三, lazy load的性能

下图中的页面,读取Student的列表,同时访问Student的导航属性Score来获取成绩信息。

从MiniProfiler能看到,页面一共执行了4个Sql, 其中第一个Sql是从Student表中取出了3条数据,

其余的3个sql是每个Student访问导航属性而导致的数据库访问.

这里由于Student表中只有3条数据,如果有100条的话,就会导致1+100*3次数据访问,所以这种使用导航属性来获取数据的方式是不适合这种列表数据显示的

这里是页面View的代码:

  1. @model IEnumerable<Student>
  2.  
  3. <h1>Student With Score(Lazy Load)</h1>
  4. <table border="1">
  5. <tr><td>ID</td><td>Name</td><td>Age</td><td>Score</td></tr>
  6. @foreach(var student in Model)
  7. {
  8. <tr>
  9. <td>@student.Id</td>
  10. <td>@student.Name</td>
  11. <td>@student.Age</td>
  12. <td>@student.Score.Score</td>
  13. </tr>
  14. }
  15. </table>

四, 使用StudentExtensionRepository来提高效率

StudentExtensionRepository解决效率问题的思路是,如果你取出3个student信息的话,它会同时把StudentScore信息取出来。

先来看看StudentExtension的定义

  1. public class StudentExtension
  2. {
  3. public Student Student { get; set; }
  4. public StudentScore StudentScore { get; set; }
  5. }

上面的StudentExtension用来保存Student信息,以及和该Student相关的StudentScore数据。

下面是StudentExtensionRepository.cs的具体内容,GetStudents方法中,先取得Student信息,然后获取相关的Score信息。

  1. public class StudentExtensionRepository : IStudentExtensionRepository
  2. {
  3. private readonly IStudentRepository _studentRepository;
  4. private readonly IRepository<StudentScore> _studentScoreRepository;
  5.  
  6. public StudentExtensionRepository(IRepositoryCenter repositoryCenter)
  7. {
  8. _studentRepository = repositoryCenter.GetRepository<IStudentRepository>();
  9. _studentScoreRepository = repositoryCenter.GetRepository<IRepository<StudentScore>>();
  10. }
  11. public IEnumerable<StudentExtension> GetStudents()
  12. {
  13. var result = new List<StudentExtension>();
  14.  
  15. var students = _studentRepository.GetStudents().ToList();
  16. //取得score信息
  17. var studentsIds = students.Select(s => s.Id);
  18. var scores = _studentScoreRepository.Filter(s => studentsIds.Contains(s.StudentId)).ToList();
  19.  
  20. foreach (var student in students)
  21. {
  22. var temp = new StudentExtension
  23. {
  24. Student = student,
  25. StudentScore = scores.FirstOrDefault(s => s.StudentId == student.Id)
  26. };
  27. result.Add(temp);
  28. }
  29. return result;
  30. }
  31. }

最后,使用新的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加载的。

  1. public class StudentExtension1
  2. {
  3. public Student Student { get; set; }
  4. //Lazy属性
  5. public Lazy<StudentScore> StudentScoreLazy { get; set; }
  6. //非Lazy属性,从Lazy属性中取值。当真正用到该属性的时候,会触发数据库访问
  7. public StudentScore StudentScore { get { return StudentScoreLazy.Value; } }
  8.  
  9. public Lazy<StudentHealthy> StudentHealthyLazy { get; set; }
  10. public StudentHealthy StudentHealthy { get { return StudentHealthyLazy.Value; } }
  11. }

改造后的StudentExtensionRepository1

  1. public IEnumerable<StudentExtension1> GetStudents()
  2. {
  3. var result = new List<StudentExtension1>();
  4.  
  5. var students = _studentRepository.GetStudents().ToList();
  6. var studentsIds = students.Select(s => s.Id);
  7. //存储Score查询的结果,避免多次访问数据库来获取Score信息
  8. List<StudentScore> scoreListTemp = null;
  9. Func<int, StudentScore> getScoreFunc = id =>
  10. {
  11. //第一个Student来获取Score信息的时候,scoreListTemp会是null, 这个时候,会去访问数据库获取Score信息
  12. //第二个以及以后的Student获取Score信息的时候,scoreListTemp已经有值了, 这样就不会再次访问数据库
  13. if (scoreListTemp == null)
  14. {
  15. scoreListTemp =
  16. _studentScoreRepository.Filter(
  17. s => studentsIds.Contains(s.StudentId)).ToList();
  18. }
  19. return scoreListTemp.FirstOrDefault(s => s.StudentId == id);
  20. };
  21.  
  22. //存储Healthy查询的结果,避免多次访问数据库来获取Healthy信息
  23. List<StudentHealthy> healthyListTemp = null;
  24. Func<int, StudentHealthy> getHealthyFunc = id =>
  25. {
  26. if (healthyListTemp == null)
  27. {
  28. healthyListTemp =
  29. _studentHealthyRepository.Filter(
  30. s => studentsIds.Contains(s.StudentId)).
  31. ToList();
  32. }
  33. return
  34. healthyListTemp.FirstOrDefault(s => s.StudentId == id);
  35. };
  36.  
  37. foreach (var student in students)
  38. {
  39. var id = student.Id;
  40. var temp = new StudentExtension1
  41. {
  42. Student = student,
  43. StudentScoreLazy = new Lazy<StudentScore>(() => getScoreFunc(id)),
  44. StudentHealthyLazy = new Lazy<StudentHealthy>(() => getHealthyFunc(id))
  45. };
  46. result.Add(temp);
  47. }
  48. return result;
  49. }
  50. }

接下来,创建2个不同的页面index2和index3, 他们的代码完全相同,只是View页面中访问的属性不同。

  1. public ActionResult Index2()
  2. {
  3. var studentExtensionRepository1 = _repositoryCenter.GetRepository<IStudentExtensionRepository1>();
  4. var students = studentExtensionRepository1.GetStudents().ToList();
  5. return View(students);
  6. }
  7.  
  8. public ActionResult Index3()
  9. {
  10. var studentExtensionRepository1 = _repositoryCenter.GetRepository<IStudentExtensionRepository1>();
  11. var students = studentExtensionRepository1.GetStudents().ToList();
  12. return View(students);
  13. }

index2.cshtml中,访问了StudentScore属性

  1. <h1>Student With Score(Use the StudentExtensionRepository1)</h1>
  2. <table border="1">
  3. <tr><td>ID</td><td>Name</td><td>Age</td><td>Score</td></tr>
  4. @foreach(var student in Model)
  5. {
  6. <tr>
  7. <td>@student.Student.Id</td>
  8. <td>@student.Student.Name</td>
  9. <td>@student.Student.Age</td>
  10. <td>@student.StudentScore.Score</td>
  11. </tr>
  12. }
  13. </table>

index3.cshtml,访问了StudentHealthy属性

  1. <h1>Student With Healthy(Use the StudentExtensionRepository1)</h1>
  2. <table border="1">
  3. <tr><td>ID</td><td>Name</td><td>Age</td><td>Healthy</td></tr>
  4. @foreach(var student in Model)
  5. {
  6. <tr>
  7. <td>@student.Student.Id</td>
  8. <td>@student.Student.Name</td>
  9. <td>@student.Student.Age</td>
  10. <td>@student.StudentHealthy.Score</td>
  11. </tr>
  12. }
  13. </table>

如下图,尽管Index2和Index3的代码中,获取数据的代码完全相同,但是实际的访问数据库的sql却是不同的。这是由于它们View中的显示数据的需求不同引起的。改造后的StudentExtensionRepository1能够根据所需来访问数据库, 来减少对于数据库的不必要的访问。同时它也能够适应更多的情况,无论是只显示Student表数据,还是要同时显示Student, StudentScore和StudentHealthy数据,StudentExtensionRepository1都能提供恰到好处的数据库访问。

六, 总结

以上是使用Lazy<T>结合EF的一次性能优化的闭门造车的尝试,欢迎各位多提意见。如果有更好的方式来,欢迎指教。

Lazy<T>在Entity Framework中的性能优化实践的更多相关文章

  1. Lazy<T>在Entity Framework中的性能优化实践(附源码)

    在使用EF的过程中,导航属性的lazy load机制,能够减少对数据库的不必要的访问.只有当你使用到导航属性的时候,才会访问数据库.但是这个只是对于单个实体而言,而不适用于显示列表数据的情况. 这篇文 ...

  2. 学习Entity Framework 中的Code First

    这是上周就写好的文章,是在公司浩哥的建议下写的,本来是部门里面分享求创新用的,这里贴出来分享给大家. 最近在对MVC的学习过程中,接触到了Code First这种新的设计模式,感觉很新颖,并且也体验到 ...

  3. 转载:学习Entity Framework 中的Code First

    看完觉得不错,适合作为学习资料,就转载过来了 原文链接:http://www.cnblogs.com/Wayou/archive/2012/09/20/EF_CodeFirst.html 这是上周就写 ...

  4. Entity framework 中Where、First、Count等查询函数使用时要注意

    在.Net开发中,Entity framework是微软ORM架构的最佳官方工具.我们可以使用Lambda表达式在Entity framework中DbSet<T>类上直接做查询(比如使用 ...

  5. "Entity Framework数据插入性能追踪"读后总结

    园友莱布尼茨写了一篇<Entity Framework数据插入性能追踪>的文章,我感觉不错,至少他提出了问题,写了出来,引起了大家的讨论,这就是一个氛围.读完文章+评论,于是我自己也写了个 ...

  6. 在Entity Framework 中实现继承关系映射到数据库表

    继承关系映射到数据库表中有多种方式: 第一种:TPH(table-per-hiaerachy) 每一层次一张表 (只有一张表) 仅使用名为父类的类型名的一张表,它包含了各个子类的所有属性信息,使用区分 ...

  7. Entity Framework 教程——Entity Framework中的实体类型

    Entity Framework中的实体类型 : 在之前的章节中我们介绍过从已有的数据库中创建EDM,它包含数据库中每个表所对应的实体.在EF 5.0/6.0中,存在POCO 实体和动态代理实体两种. ...

  8. 关于Entity Framework中的Attached报错相关解决方案的总结

    关于Entity Framework中的Attached报错的问题,我这里分为以下几种类型,每种类型我都给出相应的解决方案,希望能给大家带来一些的帮助,当然作为读者的您如果觉得有不同的意见或更好的方法 ...

  9. 关于Entity Framework中的Attached报错的完美解决方案终极版

    之前发表过一篇文章题为<关于Entity Framework中的Attached报错的完美解决方案>,那篇文章确实能解决单个实体在进行更新.删除时Attached的报错,注意我这里说的单个 ...

随机推荐

  1. 【Cocos得知】技术要点通常的积累

    1.粒子特效 CCParticleSystem*sp = CCParticleSnow::create(); sp->setTexture(CCTextureCache::sharedTextu ...

  2. MessageDigest简要

    本文博客原 參考文章:http://blog.sina.com.cn/s/blog_4f36423201000c1e.html 一.概述 java.security.MessageDigest类用于为 ...

  3. 前端学习笔记(zepto或jquery)——对li标签的相关操作(二)

    对li标签的相关操作——8种方式获取li标签的第一个元素的内容 1.alert($("ul>li").first().html());2.alert($('ul>li' ...

  4. Go as continuous delivery tool for .NET

    http://simon-says-architecture.com/2014/02/28/go-as-continuous-delivery-tool-for-net/ Following my p ...

  5. 深入理解C指针之六:指针和结构体

    原文:深入理解C指针之六:指针和结构体 C的结构体可以用来表示数据结构的元素,比如链表的节点,指针是把这些元素连接到一起的纽带. 结构体增强了数组等集合的实用性,每个结构体可以包含多个字段.如果不用结 ...

  6. 基于jsoup的Java服务端http(s)代理程序-代理服务器Demo

    亲爱的开发者朋友们,知道百度网址翻译么?他们为何能够翻译源网页呢,iframe可是不能跨域操作的哦,那么可以用代理实现.直接上代码: 本Demo基于MVC写的,灰常简单,copy过去,简单改改就可以用 ...

  7. Linux下tomcat管理查看控制台|杀死tomcat进程

    查看控制台 # tail -f catalina.out 脚本执行权限chmod u+x *.sh #看是否已经有tomcat在运行了 ps -ef |grep tomcat #如果有,用kill; ...

  8. HTML静态分页(形如:首页,上一页,下一页,尾页)

    在HTML中有时候我们会用到静态分页,一次拿回一定量的数据结果条目,我们会以形如:第2页,共12页  首页 上一页 下一页 尾页 的方式进行静态分页,以下是该种静态分页的代码,供兄弟姐妹们参考. &l ...

  9. CSS3实战开发:使用CSS3实现photoshop的过滤效果

    原文:CSS3实战开发:使用CSS3实现photoshop的过滤效果 我们知道,使用Photoshop来调整图像的亮度和对比度,或者将图片转化为灰度等等是很常见的功能.今天我将给大家介绍几个新特性,我 ...

  10. 快速构建Windows 8风格应用27-漫游应用数据

    原文:快速构建Windows 8风格应用27-漫游应用数据 本篇博文主要介绍漫游应用数据概览.如何构建漫游应用数据.构建漫游应用数据最佳实践. 一.漫游应用数据概览 1.若应用当中使用了漫游应用数据, ...