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关系,现实开发中当然会比这个复杂的多,但是这个对于描述我们的问题已经足够了。

table

 

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> 
复制代码

lazy load

四, 使用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访问,减少来数据库的访问,提交了效率。

2

 

五, 进一步改进,使用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都能提供恰到好处的数据库访问。

 

3

 

六, 总结

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



本文转自JustRun博客园博客,原文链接:http://www.cnblogs.com/JustRun1983/p/3391079.html,如需转载请自行联系原作者


目录
相关文章
|
数据安全/隐私保护 C++ iOS开发
软件开发入门教程网 Search之C++ 继承
软件开发入门教程网 Search之C++ 继承
|
5月前
|
数据库连接 数据库
Entity Framework Core 中的延迟加载与即时加载大揭秘!性能考量全知道,助你高效开发!
【8月更文挑战第31天】Entity Framework Core (EF Core) 是一款强大的对象关系映射(ORM)框架,支持延迟加载与即时加载两种方式。延迟加载即访问关联实体时再加载,适用于减少初始查询负载,但可能导致多次数据库查询;即时加载则在查询主实体时一并加载关联实体,减少数据库访问次数,但可能增加初始查询复杂度。选择加载方式需综合考虑查询复杂性、数据量及数据库连接管理等因素。
98 0
|
5月前
|
数据库
优化数据加载策略:深入探讨Entity Framework Core中的懒加载与显式加载技术及其适用场景
【8月更文挑战第31天】在 Entity Framework Core(EF Core)中,数据加载策略直接影响应用性能。本文将介绍懒加载(Lazy Loading)和显式加载(Eager Loading)的概念及适用场景。懒加载在访问导航属性时才加载关联实体,可优化性能,但可能引发多次数据库查询;显式加载则一次性加载所有关联实体,减少查询次数但增加单次查询的数据量。了解这些策略有助于开发高性能应用。
83 0
|
5月前
|
SQL API 数据库
揭开高效数据层构建的秘密武器:Entity Framework Core 分页查询的最佳实践与性能优化技巧全解析
【8月更文挑战第31天】本文以随笔形式详细探讨了如何在Entity Framework Core中实现分页查询的最佳实践。通过创建基于EF Core的项目,配置数据库上下文,并定义领域模型,文章展示了如何使用`Skip()`和`Take()`方法进行分页查询。此外,还介绍了如何使用惰性加载、显式加载和预加载来优化性能,并通过投影技术减少不必要的数据加载。最后,文章强调了分页查询对于提升应用性能和用户体验的重要性。
109 0
|
5月前
|
SQL 数据处理 数据库
提升数据处理效率:深入探讨Entity Framework Core中的批量插入与更新操作及其优缺点
【8月更文挑战第31天】在软件开发中,批量插入和更新数据是常见需求。Entity Framework Core 提供了批处理功能,如 `AddRange` 和原生 SQL 更新,以提高效率。本文通过对比这两种方法,详细探讨它们的优缺点及适用场景。
166 0
|
5月前
|
SQL Java 数据库连接
揭秘Hibernate Lazy Loading:如何实现按需加载并优化性能?
【8月更文挑战第31天】Hibernate 是一个流行的 Java ORM 框架,其懒加载(Lazy Loading)特性可按需加载数据,减少数据库查询次数,提升应用性能。本文介绍懒加载的实现原理:通过动态代理在访问未加载属性时触发 SQL 查询并缓存结果。此外,还提供了优化建议,包括合理设置 FetchType、使用 @BatchSize 注解批量加载、利用二级缓存和查询缓存,以及避免 N+1 查询问题,以进一步提升性能。开发者应根据具体需求选择最佳策略。
153 0
|
5月前
|
SQL 关系型数据库 数据库连接
Entity Framework Core 入门教程来袭!快速上手强大的 ORM 工具,开启高效数据库开发之旅!
【8月更文挑战第31天】Entity Framework Core(EF Core)是一个轻量且可扩展的对象关系映射(ORM)框架,允许开发者使用 .NET 语言操作数据库而无需直接编写 SQL 语句。本教程涵盖 EF Core 的安装、数据库上下文创建、数据库连接配置及常见数据库操作(如添加、查询、更新和删除),并介绍如何利用数据库迁移功能安全地更改数据库结构。通过本教程,你可以快速掌握 EF Core 的基本用法,提高开发效率。
288 0
|
8月前
|
API 数据库 Swift
【Swift开发专栏】Swift中的数据持久化:Core Data与Realm
【4月更文挑战第30天】本文探讨了Swift中两种流行的数据持久化框架——Core Data和Realm。数据持久化是保持应用数据在不同运行周期间一致性的关键。Core Data,苹果的ORM系统,适合处理复杂数据关系,提供与iOS生态系统的无缝集成。使用Core Data涉及定义数据模型、生成NSManagedObject子类、配置持久化容器及执行数据操作。而 Realm,一个轻量级数据库,以其高性能、易于使用的API和实时数据同步适用于跨平台项目。在Swift中使用Realm,需定义数据模型、配置Realm实例、执行数据操作并观察数据变化。理解这两者能帮助开发者构建更高效、可靠的应用。
211 0
|
前端开发 Java 开发者
探究 Java 中的 @Component 注解:实现组件化开发的利器
在现代的软件开发中,组件化架构已经成为一种重要的设计理念,而 Java 中的 `@Component` 注解则是实现组件化开发的有力工具之一。通过该注解,我们可以轻松地将 Java 类标识为组件,并借助 Spring 框架实现依赖注入、管理和协调。本文将带您深入探索 Java 中的 `@Component` 注解,揭示其作用、用法以及在实际开发中的应用场景。
|
编译器 C语言 C++
软件开发入门教程网 Search之C++ 类 & 对象
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。

热门文章

最新文章

下一篇
开通oss服务