1. 程式人生 > >EF6學習筆記十四:上下文管理

EF6學習筆記十四:上下文管理

就會 執行 人才 不能 ctx 函數 reat sys convert

要專業系統地學習EF前往《你必須掌握的Entity Framework 6.x與Core 2.0》這本書的作者(汪鵬,Jeffcky)的博客:https://www.cnblogs.com/CreateMyself/

前面學的東西只算入門,為了理解EF,書中的EF進階非學不可,我看的很吃力。在百度上看關於EF上下文的文章,很少,讓人很無助。

我反復看了幾遍,還是有很多東西不理解,沒辦法了,那些東西真的沒有什麽接觸,想象不出來。現在只能努力用自己的話復述一遍,並記錄一些自己的問題,主要是想記錄自己的思考的過程。

使用EF我們要自己寫一個派生自DbContext的上下文類,這是我們與數據庫交互的橋梁。

我對這個類了解多嗎?其實很少。在EF的最初版本是利用ObjectContext來進行數據訪問的,現在的DbContext底層還是基於ObjectContext,經過改造後的DbContext,性能更好,官方推薦使用DbContext。如果我們想用ObjectContext也沒問題,因為他們之間可以互相轉換。

DbContext 轉 ObjectContext

技術分享圖片
using (EFDbContext ctx = new EFDbContext())
{
       var objCtx = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)ctx).ObjectContext;
} 
View Code

ObjectContext 轉 DbContext

技術分享圖片
System.Data.Entity.Core.Objects.ObjectContext objCtx = new System.Data.Entity.Core.Objects.ObjectContext("
connectionStr"); var ctx = new System.Data.Entity.DbContext(objCtx,true);
View Code

現在來說我理解的一些上下文管理的東西。

上下文生命周期管理

上下文我知道,不過也是後來才知道。當一個東西,不給出上下文你是不能很好的理解他的,因為沒有約束,範圍太大,對於不同的環境有不同的理解。和說話一樣,對於一句話你還要給出這句話的語境,別人才能真正知道。

當我說出一句“真香”,你知道我說的是真的香還是真香定律?這和泛型約束一樣,我們對類型添加的那些約束也可以看做是這個類型的上下文。

我最開始弄EF,同樣的是派生一個上下文,但是那時我給上下文取的變量名是“db”,因為我就認為它是一個虛擬的數據庫。但是覺得不準確,而且直到我無意中看到上下文中有個屬性叫Database……

技術分享圖片

如果上下文就是數據庫,那麽它怎麽還要有一個Database屬性呢?肯定不是這樣的,Database應該看做是EF這個語境中的一句話,這個大語境中還有配置、追蹤……這些內容,所以要理解整個語境才能理解一句話。弄懂上下文和他裏面database的關系。

生命周期,什麽意思,按照字面意思來說,一個人從出生到死亡的過程就是這個人的生命周期,一個手機生產到使用報廢的過程,再怎麽玄之又玄的解釋肯定不能懷疑不能動搖,它就是一個出生到死亡的過程。

我這說了好像等於沒說啊,其實,我想要對自己強調一遍。根本性的東西不能動搖。既然它叫了這個名字,那麽如果實際情況不是這樣,只能說明是它的問題不是我的問題。

因為概念性的東西老是聽別人說來說去,一個這樣說,另一個那樣說,如果你不去繼續研究,他對你就是一個模糊的、說不出來的東西,而且會懷疑他的命名。

上下文生命周期管理,說的就是……上下文生命周期管理

上下文的生命周期開始於初始化,結束於被垃圾回收器回收並釋放。釋放?你釋放就是了,你不是內存托管嗎?

原來,要是你不使用using來包括上下文,就要自己手動釋放資源。

可是你不是內存托管嗎?我自己創建一個Studen類,也從來不用去手動釋放啊

在以前寫ado的時候我記得要創建數據庫連接,用try/catch包裹起來,最後在finally中調用close方法關閉連接。

後來我知道了托管代碼和非托管代碼這一個東西。我的理解就是只要你不寫指針不自己操作內存,那麽就是寫的托管代碼。

所以我認為是不是有些東西不能托管,百度了一下果然:

系統的資源包括托管資源和非托管資源。例如文件流,數據庫的連接,系統的窗口句柄,打印機資源等等這些是非托管資源。” https://www.cnblogs.com/enamorbreeze/p/4711468.html

那就一定是上下文中使用了非托管資源了,難道是上下文對象不銷毀,那麽由它創建的數據庫連接就不會關閉?應該不是,因為我在前面弄EF時候看過EF生成的SQL和SQL的執行情況

比如我根據導航屬性訂單來查詢產品,訂單和產品一對多的關系

var res = ctx.Products.Where(x => x.Order != null).ToList();
Console.WriteLine(JsonConvert.SerializeObject(res, set));

SQL的執行情況是這樣

技術分享圖片

可以看到EF每執行一次SQL,就會打開/關閉一次數據庫連接

那就說明他自己有關閉數據庫連接的能力啊,並不是我的上下文對象銷毀了,數據庫連接才關閉。

但是總的來說上下文你不使用using來包裹,就需要手動釋放資源,肯定是用到了哪些非托管資源。

如何更幹凈地使用上下文

我一開始使用EF,實例化上下文和普通對象是一樣的寫法,因為我不知道除非using包裹就要手動釋放資源。

using幾年前我就見到了,一直沒有去弄過,不知道怎麽回事,在寫ado的時候我用的try/catch也看到有人寫using

原來這個using可以看做就是try/catch的語法糖,using經過反編譯後的本質是創建try/finally塊,並最終在finally中調用Dispose方法釋放。

書中這樣說:"在使用上下文時,始終要謹記一個準則:一個請求對應一個上下文。在項目中上下文全局唯一,這種方式顯然不可取,在多線程web應用程序中性能極低,尤其對並發操作。"

我們來看三種上下文使用方式

Using Pattern 使用using,這種方式的缺點,控制器中的方法可能用到上下文的情況非常多,這樣就會到處充斥這些using

技術分享圖片
using(EFDbContext ctx = new EFDbContext())
{

}
View Code

DisPose Pattern 上下文和控制器一同創建和銷毀

技術分享圖片
public class TestController : Controller
    {
        private EFDbContext _ctx;

        public TestController()
        {
            _ctx = new EFDbContext();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _ctx.Dispose();
            }
            base.Dispose(disposing);
        }
    }
View Code

Dependency Injection 依賴註入的方式是最被開發者推薦的模式

技術分享圖片
 public class TestController : Controller
    {
        private EFDbContext _ctx;

        public TestController(EFDbContext ctx)
        {
            _ctx = ctx;
        }
    }
View Code

現在上下文就不是由控制器來創建和銷毀了,而且被註入進來的,控制器只管使用就行了。我對依賴註入沒有什麽認識,一直沒有去弄。註入明白,依賴呢?難道是這個被註入的對象和控制器的構造函數綁定在一起了,二者缺一不可,形成了依賴關系?

行吧,書中講到了生命周期追溯,這裏我就實在看不明白了,因為缺少經驗,完全想象不出那種多數據庫,分布式系統多線程和並行操作那種對我來說很陌生的環境,所以,這裏我就不說了。

作者說上下文派生實例並非類線程安全,因此我們不能再多線程中訪問上下文實例,這會造成多次查詢通過相同的數據庫連接同時並發返回,也會破壞通過上下文維護實體的變更追蹤和工作單元的一級緩存,所以在多線程應用程序中必須為每個線程使用獨立的上下文派生實例。

因此EF6.x引入了異步查詢功能,異步查詢只支持在同一時刻查詢一次,超過一次就會拋出異常

來看一下

技術分享圖片
var res1 = ctx.Products.ToListAsync();
var res2 = ctx.Products.ToListAsync();
View Code

這樣直接報錯,並且VS崩潰

技術分享圖片

需要使用await來等待上一次查詢完成,再接著執行下一步的查詢

技術分享圖片
public async Task<string> Test()
        {
            JsonSerializerSettings set = new JsonSerializerSettings();
            set.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

            string result = string.Empty;
            using (EFDbContext ctx = new EFDbContext())
            {
                var res1 = await ctx.Products.ToListAsync();
                var res2 = await ctx.Products.ToListAsync();
                var res3 = res1.Concat(res2);
                result = JsonConvert.SerializeObject(res3,set);
            }
            return result;
        }

Task<string> res = new Program2().Test();
Console.WriteLine(res.Result);
View Code

行了,就這吧。弄明白了一些,也有很多沒弄明白,路漫修遠兮。

EF6學習筆記十四:上下文管理