[UWP] Entity Framework Core on UWP

Entity Framework 之前是僅有 Full .NET 限定,最新的 Entity Framewok Core 已經可以跨平台支援了!所以就趁機會來嘗試要把原先用簡單的ORM (SQLite.net) 轉移到微軟官方的 EntityFramework

OK~ 動手之前,得先把介紹先讀過一遍,請參考下面的連結

其實文件不多,所以官方文件裡面的每個章節都點進去看過一遍,初步作一下瞭解,使用方式跟印象中沒有太不一樣,
與目前使用 SQLite.net 的差異除了在標是的 attribute 和 抓資料的 LINQ 和直接下 SQL 指令上有些差異外,
其餘沒有太大的不同...至少一開始我是這麼認為的... XD

看完之後,還是要動手做比較有 Fu!

第一步

當然是加入 Entity Framework Core for Sqlite 的 NuGet Package 囉!
裝完 EFCore for Sqlite package 開開心心的按下 compile 然後就... compile warning

關卡1:
compile 完後跟我說 Sqlite.dll 這個有 reference 上的衝突!WTF ?
後來查了一下是相依性搞的鬼
我原先的 project 用的是 SQLIte.net ,然後有 reference SQLite 官方的 vsis 的 dll,
但是追了一下 EFCore for Sqlite 的相依的 Package 到最後面會有一個 Sqlite.UWP.native 的這個其實就有 reference Sqlite.dll(而且版本還比 SQLite 官網的舊)
解法1:
只好把我原先自己 reference 的 sqlite.dll 移掉,用 NuGet package 裡面的,因為相依性是無法避免的 ~_~

不知道是不是因為 NuGet Package Manager 更新的關係還是機制改變的關係,你安裝了一個 Package 後,它裡面的有相依的 Package 雖然會一併下載到你的電腦,
但是不會出現在你的 Installed Package 清單裡面,所以自己去追了一下 package 的相依性

  • Microsoft.EntityFrameworkCore.Sqlite 裡面有相依 Microsoft.EntityFrameworkCore.RelationalMicrosoft.Data.Sqlite
  • Microsoft.Data.Sqlite  有相依 sqlite
  • sqlite 相依 SQLite.UWP.Native

我索性把上述這些裝進來,起碼在 Installed package 裡面可以看的到比較安心 XD
裝的時候也遇到奇妙的事情,原來 Microsoft.Data.Sqlitesqlite 這兩個 package 是不需要選 proejct 就可以安裝的 @_@a
(Microsoft.EntityFrameworkCore.Relational 裡面的相依我就沒去追了)

這邊可以注意到有 Microsoft.Data.Sqlite 的 package ,實際上你也是可以用喔!(可以看到 SqliteConnection, SqliteCommand 等等東西)
EF Core 底層還是 ADO.NET,只是用 ADO.NET 寫法的話,轉物件就比較麻煩啦~
需要直接下 RawSql 在教學文件裡面也有寫到,不過有一些限制在喔!

第二步

開始動手寫 code 啦!

  1. Model 我本來就好了,所以我就把原先設計的 Model 的 attribute 換掉成 EF 在用的(原來 Column 和 Key 這兩個 attribute 所屬的 namespace 是不一樣的,不知道為什麼)
  2. 建立 DbContext,把繼承做好,並且 override OnConfiguring
  3. 在 App 原先是建立資料庫連線的地方,改用 entity framework 的 DbContext.Migrate
  4. 調整撈資料的地方,都改用 LINQ 的方式改寫
關卡2:
指令改成LINQ時就遇到 InsertOrUpdate 這個 Sqlite 本身就支援指令的改寫,花了點時間看了 EntityFramework 要怎麼做這件事情
解答2:
看到這篇 StackOverFlow 的文章:http://stackoverflow.com/questions/15336248/entity-framework-5-updating-a-record
裡面提到了三種方式,我最後是後第三種,第一種太麻煩我懶,第二種 EF Core 目前無法使用,因為 db.Entry(original).CurrentValues.SetValues(updatedUser); 裡面的 CurrentValues 在 EF Core 裡面目前是沒有的
關卡3:
我原先的 method 是 async 的怎麼辦?
解答3:
當然你也可以直接把 method 改成非 async ,別鬧了!這樣要程式改很大!
或者你可以把結果用 Task.FromResult 去回傳,別鬧了!說好的非同步呢?這樣會卡 UI 的!
正解就是
using Microsoft.EntityFrameworkCore 這個 namespace 後,就可以增加了 Async 的 method 囉!如 FirstOrDefaultAsync, ToListAsync, SaveChangesAsync 等等等等
這算隱藏密技嗎?XD

嗯~一切調整完後,開心的按下執行後,當然事情不會這麼順利,就在 IntelliTrace 裡面收到一堆 exception

關卡4:
遇到的第一個 Exception 是在乎叫 Migration 這個 method 時候遇到的,因為我有一個 Model 的 [Key] attribute 宣告了兩個,因為我想要用組合 PK
結果 runtime 說不行(忘記是出什麼 exception 了)
解答4:
如果要用組合 PK 的話,必須在 DbContext 去 override OnModelCreating 然後自己寫 HasKey 的程式碼(怎麼寫請參考文件)
而且一旦有寫了一個 Model 是自己在 OnModelCreating 的,其他的 Model 也必須在這邊用程式碼的方式建立 PK... 有點想翻桌XD

PS : 後來再重新翻文件的時候,確實有提到組合 PK 是必須要寫程式碼的這件事情

OK~ 現在通過了 Migrate 怎麼還是有 exception 呢?

關卡5:
找了一下看不出原因,去看看資料庫檔案存不存在,確實存在!但是連進去一看,耶黑~啥 Table 都沒有是什麼鬼?
解法5:
文件要看清楚!文件裡面有寫清楚
與 desktop .net core 不同的是,UWP 這邊要多安裝一個 NuGet Pakcage 是 Microsoft.EntityFrameworkCore.Tools –Pre (對!目前還是 Pre )
安裝好後按照文件去執行一個 Add-Migration 的指令,執行完後會發現你的 project 多了一個 Migration 的資料夾,裡面多了兩個 cs(實際上是三個)
執行 Add-Migration 的時候,它其實會去掃你 project 裡面 DbContext 等有繼承自 EntityFramework class 的東西,並且會編譯喔!
所以有可能有錯誤的東西讓他編譯不過,會跟你說錯在那邊,然後失敗的話就不會產生檔案。
另外,記得再執行 Add-Migratoin 前,有兩件事情要注意
1. 要把 console 視窗的 project 選到你實際寫 DbContext 的 project 喔!不然會錯
2. 文件中有說到因為現在那個 Tool 還是 Preview 2 的階段有個 Bug,所以你必須要自己新增一個 App.Config (內容文件有寫)到你的 App 的 project

至於產生的 cs 除了真正建立 table 外,其他目的不明。
看起來像是可以在你調整 db schema 的時候幫你做動作,意思就是如果你有異動 shema 的話就要再執行一次 Add-Migration 的動作囉?!不過這邊還沒研究

OK~ 現在執行後,靠邊~怎麼還是一樣 Exception ?

關卡6:
這次的 exception 有明確的內容 Table Not Found 的 excpeoin?把資料庫檔案抓出來看,Table 也都有建立啊!等等 table name 怎麼不對 囧
解法6:
Well~ 很顯然的你在執行 Add-Migration 的動作的時候,他產生出來的程式碼裡面的 table name 並不會吃你在你的 model 上面標示的 Table 的 attribute ... WTF
所以解法就是在 DbContext 的 OnModelCreating 除了剛加上的 HasKey 外,還要自己寫 ToTable 並指定正確的 table name,這搞什麼鬼?不知道是不是 Bug =.=
不寫 ToTable 的話,他預設產生出來的 table name 會跟你在 DbContext 寫的該 Model 的 property 一樣,
例如:在 DbContext 你是這麼寫的 public DbSet<Blog>  Blogs { get; set; } ,這樣產生出來的 table name 就是 Blogs

OK~ 繼續執行... 還是有 exception!

關卡7:
這次又是什麼 exception ?簡單的意思是說我已經選出了一個 entity ,但是又在別的地方異動出來 (之類的),
我的情境是我用 table 來記錄我下載的圖片檔案,因為同一個圖檔可能會有不同的控制向再要求下載,所以做了一個機制不要重複下載,所以有用 table 來記錄該圖檔下載的狀態。
這樣就是會有 multi-thread 的狀況下,就有可能取得同一個 entity 來判斷狀態 或 改變狀態
解法7:
這個其實滿簡單解,就是你撈資料的時候多呼叫 AsNoTracking 或者在建立 DbContext 把預設的 QueryTracking 的行為換成 NoTracking

然後... 搭拉~~~ 東西正常運作啦!總算是初步嘗試成功了!(灑花)
然後王子和公主就過著幸福快樂的日子嗎?當然是沒有...

關卡8:
效能不夠好!原先用 SQLite.net 的時候,一排的圖檔要出來的時間只要 1~2秒,而且可以漸漸的出來,換了EF Core後,要等個 3 秒,然後一次出來
解法8:
從缺

其實這邊就到了想延伸討論但我也沒結論的地方

DbContext LifeTime

看官方完見裡面的 sample 產生一個 DbContext 都是會用 using 包起來,離開就會把他清除,但這樣對於 檔案型 的資料庫不知道有沒有 Connection Pool 的概念,
用 using 包起來如果會開開關關,或者一直產生新的 connection,在 multi-thread 會同時間開很多的狀況下,應該會滿浪費時間的
雖然我對效能是不是卡在這邊不是那麼確定,但起碼第一眼最明顯的是這邊,不過看了一些資料,主要兩個連結

這兩篇基本上建議 DbContext 該關的時候就關,就如同 sample code 的寫法,因為 Dbcontext 是很輕量的;
另外就是如果只用一個 DbContext 因為裡面的 cache 的狀態會留著,一來可能造成記憶體爆炸,二來可能互相的不相干的指令會有影響,尤其是 Traction;

第一個連結內有提到 DbContext is not thread safe 也有提到他對於 Lifetime 的解法,有放在 GitHub ,用了所謂的 Scoped DbContext,
不過我只有稍微喵,並沒有把他拿來用,因為我覺得如果要用這個應該要改很大,
覺得似乎是要調整成說從呼叫的一連串的 method 頭開始,就要產一個 DbContext 一路進去的概念吧?

有真的只有產生一個 DbContext 試過,這個會造成說我有 Nested Tranction 的 exceptoin,
也有試過維持 using DbContext 的寫法但是用同一個 Connection,不過這樣會造成圖片出不來,雖然沒有任何 exception,感覺就是卡死在 DB 裡面了

總之,目前是放棄把 proejct 改用 EF Core 來改寫,沒有想到好的效能調整方式之前,是不行的!

以上就是 EF Core 在 UWP 上的冒險經驗,謝謝觀賞 Orz