Lazy<T>在Entity Framework中的性能優化實踐(附源碼)

来源:http://www.cnblogs.com/libing1/archive/2017/04/22/6747472.html
-Advertisement-
Play Games

小分享:我有幾張阿裡雲優惠券,用券購買或者升級阿裡雲相應產品最多可以優惠五折!領券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 在使用EF的過程中,導航屬性的lazy lo ...


小分享:我有幾張阿裡雲優惠券,用券購買或者升級阿裡雲相應產品最多可以優惠五折!領券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03


在使用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的一次性能優化的閉門造車的嘗試,歡迎各位多提意見。如果有更好的方式來,歡迎指教

參考頁面:http://qingqingquege.cnblogs.com/p/5933752.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 小程式: 行家看看年前,公司開發了微信小程式,我負責後端開發,其中遇到一些坑,分享一下小程式語音錄製和播放微信錄音不能計時,需要自己計時錄音文件是微信獨有格式,只能微信本身播放,否則需要工具轉換為Mp3等通用格式才可以錄音播放不能直接用標簽src引入,需要將語音文件下載到本地後才可以正常播放。錄音播... ...
  • # identity資料庫 ## 創建空資料庫 交給ef管理 ### 添加asp.net identity包 ``` Install-Package Microsoft.AspNet.Identity.EntityFramework Install-Package Microsoft.AspNet.... ...
  • 一、StackPanel StackPanel是以堆疊的方式顯示其中的控制項 1、可以使用Orientation屬性更改堆疊的順序分為水平方向(Orientation="Horizontal")和豎直方向(Orientation="Vertical"),以保證要實現的效果。 二、WrapPanel 以 ...
  • Server=(localdb)\\mssqllocaldb; Database=xxxxx; Serve=伺服器名;Database=資料庫名 Server=(localdb)\\mssqllocaldb; AttachDbFilename=xxxx.mdf;Initial Catalog=xxx ...
  • 在認識WPF之前,在windows開發人員都是基於坐標來將控制項放在正確的位置上,控制項的大小也是由軟體人員來指定。這對於軟體人員來說比較無聊痛苦。但是在WPF中這種痛苦且無聊的工作沒有了,這得益於WPF的佈局。 佈局就是將一些控制項在窗體上進行排布。一般來說都是靠坐標來的,WPF也支持這種方法,但是WP ...
  • 微軟的Bing搜索引擎首頁每天都會提供了一些有趣的圖片,下麵使用正則表達式獲取圖片的地址,不管是在手機app還是在網站上都是很好的圖片素材,而且每天更新,非常不錯。 首先訪問微軟的API,該地址返回的是xml文本,獲取xml文本後使用正則表達式匹配url節點中的內容,加上必應主頁鏈接即可獲得圖片的真 ...
  • using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using Syste ...
  • using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; using System.Net;using System.Manageme ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...