Sqlite && EF Code FIRST 終極解決方案 2019.5.17#
包括根據模型自動生成數據庫,初始化數據,模型改變時的自動數據遷移等
2019.12.25 更新
支持EF6.3的SQL Generation:NuGet:Link.EntityFramework.Sqlite
我是真的服了,用nuget自動安裝的config每次都用不了,需要添加factory:
<remove invariant="System.Data.SQLite" />
<add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
-----------------------------------以下原文-----------------------------------------
目錄
- 01 知識匯總
- 02 在過去的經驗里EF與Sqlite的使用經驗
- 03 Sqlite數據庫在EF Code First中遇到的困難
- 04 解決方案
- 05 解決方案的不足
- 06 與老方案對比
01 知識匯總
What is EF
Entity Framework是MS提供的一個ORM框架,它旨在為小型應用程序中數據層的快速開發提供便利。
ORM 技術是在對象和關系之間提供了一條橋梁,前台的對象型數據和數據庫中的關系型的數據通過這個橋梁來相互轉化,這樣,我們在具體的操作實體對象的時候,就不需要再去和復雜的 SQ L 語句打交道,只需簡單的操作實體對象的屬性和方法。
Code First模式我們稱之為“代碼優先”模式,是從EF4.1開始新建加入的功能。使用Code First模式進行EF開發時開發人員只需要編寫對應的數據類(其實就是領域模型的實現過程),然后自動生成數據庫。這樣設計的好處在於我們可以針對概念模型進行所有數據操作而不必關系數據的存儲關系,使我們可以更加自然的采用面向對象的方式進行面向數據的應用程序開發。
遇到的講解EF非常好的博客
EF性能全面講解:https://www.cnblogs.com/yaopengfei/p/9196962.html
EF遷移全面講解:https://www.cnblogs.com/farb/p/DBMigration.html
What is Sqlite
SQLite是一種嵌入式數據庫,它的數據庫就是一個文件,它占用資源非常的低。
02 Sqlite數據庫在EF Code First 中遇到的困難
- 設計上的EF瓶頸:無法實現接口類的數據庫化
- EF的Sqlite Data Provider無法提供SQL Generation
- Sqlite數據庫自身許多語法不支持,后續模型修改的數據遷移Sql語句比較受限
- EF瓶頸:對於視圖支持不好,但有臨時的解決方案
03 在過去的經驗里EF與Sqlite的使用經驗
由於Sqlite的Data Provider不支持Sql Generation 所以EF無法從模型中自動創建數據庫,所以創建數據庫以及后續修改模型時需要的修改數據庫工作手動去完成.
- Model - 構建模型 - 修改模型
- DataBase - 手寫Sql構建數據庫,手寫Sql填充初始數據 - 手寫Sql修改模型
EF COde First利用數據庫中自動生成的__MigrationHistory表的信息與當前代碼的模型去判斷是否需要更新數據庫表結構
手寫SQL語句是通過數據庫某一個標識去判斷需不需要更新,例如:Version列
04 解決方案
Sql Generation解決方法
目前我發現兩個實現了Sqlite SQL Generation的開源庫:
System.Data.SQLite.EF6.Migrations
SQLite.CodeFirst
目前來看的話兩種庫幾乎一樣,都是添加了關於Sql Generation的方法,兩個庫都可在NuGet上獲取。
需要注意的是,SQLite.CodeFirst在NuGet上的最新版1.5.2.28並不具有數據遷移功能,該Github項目並沒有集成,具有數據遷移功能的版本可以點擊上方鏈接,是另一個人在此基礎上集成的。
另外,最新的EF6.2在初始化數據的時候會有錯誤,項目建議在Add-Migration的時候降級EF到6.13,我當前的辦法是使用EF6.3預覽版.
解決接口類的數據庫化
目前想到的方法只有一個基類去實現這個接口,然后接口的實現類去繼承這個基類
public interface ICourse: Iid
{
string Name { get; set; }
ICollection<Student> Students { get; set; }
}
public class CourseBase : ICourse
{
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
public string ID { get; set ; }
}
[Table("Chinese")]
public class Chinese : CourseBase
{
public Chinese()
{
ID = Guid.NewGuid().ToString("N");
}
public string Extend { get; set; }
public string Extend1 { get; set; }
}
然后在模型中:
public class Student:Iid
{
public Student()
{
ID = Guid.NewGuid().ToString("N");
}
public string Name { get; set; }
public Weapon.Weapon Weapon { get; set; }
public string ID { get; set; }
public virtual ICollection<CourseBase> Courses { get; set; }
}
用CourseBase代替ICource
視圖解決方案(通用)
EF本身是不支持View的,但是如果你的數據庫已經存在View,你是可以去建Table模型一樣去建立View模型並查詢他。
那么我們只需要解決兩個問題就可以實現視圖的控制:
- 初始化數據庫時如何建立View
- 修改數據模型時,Migration時屏蔽View的實體類修改的影響
目前通用的解決方案是
- 初始化數據時手寫Sql去建立View
- 不用自動數據遷移,而用API去控制遷移
- 如果需要修改View,手動Drop視圖並再次Add New View
1 關閉自動遷移,創建初始化API Add-Migration
AutomaticMigrationsEnabled = false;
2 在Up方法中手寫Sql語句創建View
public override void Up()
{
string script =
@"
CREATE VIEW [StudentWeaponView]
AS SELECT p.ID AS StudentID, p.Name AS StudentName,u.ID AS WeaponID,u.Name AS WeaponName
FROM [Students] p
INNER JOIN [Weapon] u ON u.Id = p.Id";
DBContext.DBContext ctx = new DBContext.DBContext();
ctx.Database.ExecuteSqlCommand(script);
}
3 當你想修改模型時,執行Add-Migration ChangeViewNmae
public override void Up()
{
string script =
@"
Drop View If Exists [StudentWeaponView];";
DBContext.DBContext ctx = new DBContext.DBContext();
ctx.Database.ExecuteSqlCommand(script);
string script2 =
@"
CREATE VIEW [StudentWeaponView]
AS SELECT p.ID AS StudentID, p.Name AS StudentName,u.ID AS WeaponID,u.Name AS WeaponName
FROM [Students] p
INNER JOIN [Weapon] u ON u.Id = p.Id";
ctx.Database.ExecuteSqlCommand(script2);
}
05 當前解決方案的不足
- 需要使用EF3 Preview Or Data Migration時降級EF到6.13版本
- 視圖支持不好,當然不僅僅是針對Sqlite,EF的解決方案通病
- EF數據遷移初始化數據方式單一
這里解釋一下第三點,看一下使用方法的對比就可以了
老方案(手寫SQL創建數據庫)初始化數據庫:
- 腳本1:創建數據庫
- 腳本2:添加兩條數據
- 腳本3:增加一個字段
- 腳本4:添加一條數據
EF Code First 初始化數據:
- Migration1:創建數據庫
- Migration1:增加一個字段
- Seed方法:添加兩條數據,添加一條數據
Seed方法是每次執行完從低版本到高版本的數據遷移都會執行的方法,官方推薦用AddOrUpdate方法,就是因為每次遷移都會調用,所以會出現重復數據的情況
那么上面兩種方法有什么區別呢?
如果下面Seed方法中,原封不動翻譯上面的腳本2和腳本4,那么是一定會報錯的,為什么?
因為在上面的腳本2執行中,Sql模型中是沒有腳本三增加的字段的,而下面Seed方法執行時,腳本三的字段已經添加完畢了,即在Seed方法執行的時候,調用的是最終的數據模型,上面每次執行腳本的時候,調用的當前版本的數據模型
也就是說,如果你想實現一個版本一個版本增量的初始化數據庫內容,那么在Seed方法中你要進行大量的對於版本的判斷。
目前的解決辦法:
數據庫架構方面的版本是由EF的__MigrationHistory控制的
那么數據庫初始內容上也加一個版本號
然后
if(version>1)
{
//腳本1初始數據內容.....
}
if(version>2)
{
//腳本2初始數據內容.....
}
附上完整代碼地址實現了Sqlite有關於EF Code First的全部功能。
Master利用的是System.Data.SQLite.EF6.Migrations庫
分支用的是SQLite.CodeFirst庫