SQLite 跟 SQL Server Server 有很大的不一樣,透過 EF 讓它們操作起來幾乎沒有不一樣,不過還是會碰到 SQLite 不支援的情況,但 CRUD 的使用是沒有問題的;這次我要演練 SQLite Code First,這當中有碰到一些問題,幸好都可以解決,趕緊寫下筆記...
管理工具
DB Browser for SQLite
管理的工具只是用來確定 Code First 產生的資料結構如預期,我採用 DB Browser for SQLite,下載位置如下
開發環境
- VS 2019
- .NET Framework 4.8
- EF 6.3
- System.Data.SQLite 1.0.112.0
實作
安裝套件
Install-Package System.Data.SQLite.EF6.Migrations
連同相關的套件都裝好了
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="6.3.0" targetFramework="net48" /> <package id="MSTest.TestAdapter" version="1.3.2" targetFramework="net48" />
<package id="MSTest.TestFramework" version="1.3.2" targetFramework="net48" />
<package id="SQLite.CodeFirst" version="1.5.3.29" targetFramework="net48" />
<package id="System.Data.SQLite" version="1.0.112.0" targetFramework="net48" />
<package id="System.Data.SQLite.Core" version="1.0.112.0" targetFramework="net48" /> <package id="System.Data.SQLite.EF6" version="1.0.112.0" targetFramework="net48" /> <package id="System.Data.SQLite.EF6.Migrations" version="1.0.106" targetFramework="net48" /> <package id="System.Data.SQLite.Linq" version="1.0.112.0" targetFramework="net48" />
</packages>
組態
增加 Provider
<provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
<entityFramework> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" /> <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" /> </providers> </entityFramework>
增加連線字串
<connectionStrings>
<add name="DefaultConnection" connectionString="data source=lab.db" providerName="System.Data.SQLite" />
</connectionStrings>
Configuration
這是 Code First 很重要的一個物件,Migration 會需要用到它
public class Configuration : DbMigrationsConfiguration<LabDbContext>
{
public Configuration()
{
this.AutomaticMigrationsEnabled = true;
this.AutomaticMigrationDataLossAllowed = true;
this.SetSqlGenerator("System.Data.SQLite", new SQLiteMigrationSqlGenerator());
}
protected override void Seed(LabDbContext context)
{
//This method will be called after migrating to the latest version.
//You can use the DbSet<T>.AddOrUpdate() helper extension method
//to avoid creating duplicate seed data.E.g.
//context.Employees
// .AddOrUpdate(p => p.Name,
// new Employee {Name = "Andrew Peters"},
// new Employee {Name = "Brice Lambson"},
// new Employee {Name = "Rowan Miller"}
// );
}
}
調用 Enable-Migrations 會得到以下錯誤,只好手動增加 Configuration
No MigrationSqlGenerator found for provider 'System.Data.SQLite'. Use the SetSqlGenerator method in the target migrations configuration class to register additional SQL generators.
POCO
先建立單一資料表 Employee
[Table("Employee")]
public class Employee
{
[Required]
[Key]
public Guid Id { get; set; }
[StringLength(100)]
public string Name { get; set; }
public int Age { get; set; }
}
SQLite 中的 Guid
SQLite 的 Guid 使用 BLOB 類型存儲 (binary),我試著查詢資料,資料可以順利的對應到 C# 的 GUID,用 Where(p=>p.Id==id) 會找不到資料,原因很簡單 EF 翻成 SQLite 的時候並不會把 GUID 轉成 Binary,若真的需要用 Id 當 Where 條件,可以考慮改用 string 存放
var employee = db.Employees.Where(p => p.Id == id).FirstOrDefault();
LabDbContext
public class LabDbContext : DbContext
{
private static readonly bool[] s_migrated = {false};
public DbSet<Employee> Employees { get; set; }
public LabDbContext() : base("DefaultConnection")
{
Migrate();
}
private static void Migrate()
{
if (!s_migrated[0])
{
lock (s_migrated)
{
if (!s_migrated[0])
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<LabDbContext,
Configuration>());
s_migrated[0] = true;
}
}
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
}
}
第一個案例
最後來個簡單的新增案例
新增成功會回傳異動筆數,我只驗證筆數,沒有驗證欄位內容,真實的專案可以用 Specflow 驗證多個欄位內容
[TestMethod]
public void Insert()
{
var toDb = new Employee
{
Id = Guid.NewGuid(),
Name = "yao",
Age = 18
};
using (var db = new LabDbContext())
{
db.Employees.Add(toDb);
var count = db.SaveChanges();
Assert.AreEqual(1, count);
}
}
用工具打開看看,測試跑出來的有沒有錯
異動 POCO
我想要增加 Identity 資料表並關聯到 Employee
[Table("Identity")]
public class Identity
{
[Index("UK_Account",IsUnique = true)]
public string Account { get; set; }
public string Password { get; set; }
[ForeignKey("Employee")]
[Key]
public Guid Employee_Id { get; set; }
public virtual Employee Employee { get; set; }
}
public class Employee
{
[Required]
[Key]
public Guid Id { get; set; }
[StringLength(100)]
public string Name { get; set; }
public int Age { get; set; }
public virtual Identity Identity { get; set; }
}
調用 Migration
PM> Add-Migration "Add Identity table and relational to employee"
更多的參數可以參考
https://www.entityframeworktutorial.net/code-first/code-based-migration-in-code-first.aspx
調用 Update-Database
PM> Update-Database
Update-Database 後,資料庫也跟著異動了,觀察一下 __MigrationHistory 資料表,它會記錄著異動過程
還原測試資料
為了讓每一個案例都能獨立、不互相影響,還原是一個很重要的步驟;換言之,測試的目標是檔案的話,就還原目錄。
還原資料表
在每一次的案例開始之前和結束之後把資料通通都清掉
[TestCleanup]
public void After()
{
using (var db = new LabDbContext())
{
db.Database.ExecuteSqlCommand("delete from Identity;");
db.Database.ExecuteSqlCommand("delete from Employee;");
}
}
[TestInitialize]
public void Before()
{
using (var db = new LabDbContext())
{
db.Database.ExecuteSqlCommand("delete from Identity;");
db.Database.ExecuteSqlCommand("delete from Employee;");
}
}
還原資料庫
在 SQL Server 我會這樣把測試專案用的 Local DB 整個移除,讓 CI Server 的環境保持乾淨,不過很可惜的是 SQLite 不支援 dbContext.Database.Delete()
[AssemblyCleanup]
public static void AssemblyCleanup()
{
using (var dbContext = new LabDbContext())
{
if (dbContext.Database.Exists())
{
dbContext.Database.Delete();
}
}
}
讓測試案例跑本地端的 DB,可避免網路問題造成測試失敗
最後我就直接把檔案砍掉
[TestClass]
public class MsTestHook
{
[AssemblyCleanup]
public static void AssemblyCleanup()
{
DeleteDb();
}
[AssemblyInitialize]
public static void AssemblyInitialize(TestContext context)
{
DeleteDb();
}
private static void DeleteDb()
{
var filePath = "lab.db";
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
}
延伸閱讀
[EF Core 3] 如何使用 Code First 的 Migration
SQLite CodeFirst、Migration 的趟坑过程 [附源码]
範例位置
https://github.com/yaochangyu/sample.dotblog/tree/master/ORM/EF6/Lab.EF6.SqliteCodeFirst
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET