SQLite CodeFirst、Migration 的趟坑過程 [附源碼]

負二、配置說明

最近想做個東西,用到了SQLite,按照之前的方法步驟搭建的結果失敗了,因爲SQLite的版本升級了,導致Migration自動遷移各種報錯,而且我查了一下自動遷移的包貌是不再更新了。——2018年1月24日

能正常使用的配置清單如下(不要升級包,升級包會導致一系列的坑):
EF 6.1.3
SQLite.CodeFirst 1.3.1.18
System.Data.SQLite 1.0.105.2
System.Data.SQLite.Core 1.0.105.2
System.Data.SQLite.EF6 1.0.105.2
System.Data.SQLite.EF6.Migrations 1.0.104
System.Data.SQLite.Linq 1.0.105.2
文章最底下可以下載源碼,能正常使用,但是SQLite升級到了1.0.106之後,就不能再使用原來的System.Data.SQLite.EF6.Migrations了。

負一、吐個槽

關於SQLite的CodeFirst,我找了很久,有很多博客都寫過,但是真正好用的非常少,還有很多根本就是DBFirst的代碼,真是坑的我夠嗆,研究了幾天也算有點成果,整理一下,希望對路過的朋友有所幫助。

零、冷知識

1、SQLite.CodeFirst

使用NuGet安裝的EF和SQLite可能是沒有CodeFirst功能的,安裝了“SQLite.CodeFirst”之後可以爲SQLite提供CodeFirst的功能。

2、System.Data.SQLite.EF6.Migrations 數據庫遷移

SQLite 是沒有數據庫遷移 MigrationSqlGenerator 功能的 ,我所查詢的資料是這樣的(有錯請指正,非常感謝),甚至有人說得自己寫一個數據遷移的功能,那是不是有點辛苦。

還好在 NuGet 上有一個“System.Data.SQLite.EF6.Migrations”(詳細內容可以訪問他的網站,網站上有說明文檔),安裝之後可以做簡單的數據庫修改,可以添加表中的列,但是並不能刪除和修改列,因爲 SQLite 並不認識 drop column 和 rename column,至於爲什麼不認識看下條。

3、SQLite不支持的語法

關於這個,可以自己百度查一下 ^ _ ^ ~

4、關於SQLite中的數據關係

我在試驗的過程中,發現SQLite的數據關係並不可靠,尤其是EF的子查詢我就沒有嘗試成功,還有使用外鍵的時候也並不正常。

5、SQLite 中的 Guid

在 SQLite 中使用 Guid 的時候,因爲 SQLite 中 Guid 會使用 BLOB 類型來存儲(就是二進制),所以用 Linq 直接 Id==id 是查不到東西的,使用 tostring 也得不到正確的格式,建議在使用 EF SQLite 的時候,使用 string 來存儲 Guid 數據。

使用 SQLite 讀取工具打開數據庫文件,就會發現 Guid 是 BLOB 存儲的,並且順序和 C# 生成的 Guid 並不相同。(如果非要使用 Guid 可以研究一下 Guid 和二進制的轉換。)

一、安裝過程

OK 進入正題:

使用NuGet安裝時,相關聯的都會自動下載,所以這點還是很方便的,這裏列一下安裝完之後都有啥:

  1. EntityFramework
  2. EntityFramework.zh-Hans(中文說明)
  3. SQLite.CodeFirst(實現CodeFirst)
  4. System.Data.SQLite
  5. System.Data.SQLite.Core
  6. System.Data.SQLite.EF6
  7. System.Data.SQLite.EF6.Migrations(提供數據庫遷移)
  8. System.Data.SQLite.Linq

安裝過程就這樣……

二、簡單配置一下

雖然安裝的時候會自動寫入一些配置信息,但是還是可能有些遺漏,查漏補缺一下:

App.config 文件:

entityFramework->providers 下檢查添加:

<provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />

connectionStrings 下檢查添加:

<add name="DefaultConnection" connectionString="data source=sqliter.db" providerName="System.Data.SQLite" />

三、數據庫工具類

一個簡單例子只需要4個類:

  1. Entity 類(實體類)
  2. Mapping 類(實體配置)
  3. DbContext 類(數據庫操作)
  4. Configuration 類(設置信息)

首先來一個簡單“栗子”:

Entity 類:

public class Bus
{
    public string Id { get; set; }
    public string Name { get; set; }
}
public class Person
{
    public string Id { get; set; }
    public string Name { get; set; }
}

Mapping 類:

public class Mapping
{
    public class BusMap : EntityTypeConfiguration<Bus>
    {
        public BusMap()
        {
        }
    }
    public class PersonMap : EntityTypeConfiguration<Person>
    {
        public PersonMap()
        {
        }
    }
}

DBContext 類:

public class SQLiteDb : DbContext
{
    public SQLiteDb() : base("DefaultConnection")
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<SQLiteDb, Configuration>());
    }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Configurations.AddFromAssembly(typeof(SQLiteDb).Assembly);
    }
}

Configuration 類:

public class Configuration : DbMigrationsConfiguration<SQLiteDb>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = true;
        SetSqlGenerator("System.Data.SQLite", new SQLiteMigrationSqlGenerator());
    }

    protected override void Seed(SQLiteDb 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.People.AddOrUpdate(
        //      p => p.FullName,
        //      new Person { FullName = "Andrew Peters" },
        //      new Person { FullName = "Brice Lambson" },
        //      new Person { FullName = "Rowan Miller" }
        //    );
        //
    }
}

四、操作代碼

public partial class Form1 : Form
{
    //創建Bus
    Bus Bus = new Bus() { Id = Guid.NewGuid().ToString(), Name = "11 路" };
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        //默認往數據庫添加一個Bus
        using (SQLiteDb db = new SQLiteDb())
        {
            db.Set<Bus>().Add(Bus);
            db.SaveChanges();
        }
    }
    //添加人員
    void InsertPerson(string name)
    {
        using (SQLiteDb db = new SQLiteDb())
        {
            Bus dbBus = db.Set<Bus>().Where(x => x.Id == Bus.Id).FirstOrDefault();
            db.Set<Person>().Add(new Person() { Id = Guid.NewGuid().ToString(), Name = name, Bus = dbBus });
            db.SaveChanges();
        }
    }
    //刪除人員
    void DeletePerson(string id)
    {
        using (SQLiteDb db = new SQLiteDb())
        {
            Person p = new Person() { Id = id };
            Person person = db.Set<Person>().Attach(p);
            db.Set<Person>().Remove(person);
            db.SaveChanges();
        }
    }
    //更新人員
    void UpdatePerson(string id, string name)
    {
        using (SQLiteDb db = new SQLiteDb())
        {
            Person p = db.Set<Person>().Where(x => x.Id == id).FirstOrDefault();
            p.Name = name;
            db.SaveChanges();
        }
    }

    #region 點擊按鈕操作
    private void BtInsert_Click(object sender, EventArgs e)
    {
        //清空文本框
        TbTxt.Text = "";
        //插入人員
        InsertPerson(TbInsert.Text);
        //顯示人員信息
        using (SQLiteDb db = new SQLiteDb())
        {
            List<Person> persons = db.Set<Person>().ToList();
            if (persons != null)
            {
                persons.ForEach(x =>
                {
                    TbTxt.Text += x.Id + "  " + x.Name + Environment.NewLine;
                });
            }
        }
    }
    private void BtDelete_Click(object sender, EventArgs e)
    {
        TbTxt.Text = "";
        DeletePerson(TbDelete.Text);
        using (SQLiteDb db = new SQLiteDb())
        {
            List<Person> persons = db.Set<Person>().ToList();
            if (persons != null)
            {
                persons.ForEach(x =>
                {
                    TbTxt.Text += x.Id + "  " + x.Name + Environment.NewLine;
                });
            }
        }
    }
    private void BtUpdate_Click(object sender, EventArgs e)
    {
        TbTxt.Text = "";
        UpdatePerson(TbUpdate.Text, DateTime.Now.ToString("mm:ss"));
        using (SQLiteDb db = new SQLiteDb())
        {
            List<Person> persons = db.Set<Person>().ToList();
            if (persons != null)
            {
                persons.ForEach(x =>
                {
                    TbTxt.Text += x.Id + "  " + x.Name + Environment.NewLine;
                });
            }
        }
    }
    private void BtSelect_Click(object sender, EventArgs e)
    {
        TbTxt.Text = "";
        if (TbSelect.Text == "")
        {
            //查詢所有人員信息
            using (SQLiteDb db = new SQLiteDb())
            {
                List<Person> persons = db.Set<Person>().ToList();
                if (persons != null)
                {
                    persons.ForEach(x =>
                    {
                        TbTxt.Text += x.Id + "  " + x.Name + Environment.NewLine;
                    });
                }
            }
        }
        else
        {
            //根據Id查詢人員
            using (SQLiteDb db = new SQLiteDb())
            {
                Person person = db.Set<Person>().Where(x => x.Id == TbSelect.Text).FirstOrDefault();
                TbTxt.Text = person.Id + "  " + person.Name + Environment.NewLine;
            }
        }
    }
    #endregion
}

長了點,但一看就懂的。

五、異常處理

一、“System.InvalidOperationException”類型的未經處理的異常在 EntityFramework.dll 中發生

其他信息: No Entity Framework provider found for the ADO.NET provider with invariant name ‘System.Data.SQLite’. Make sure the provider is registered in the ‘entityFramework’ section of the application config file. See http://go.microsoft.com/fwlink/?LinkId=260882 for more information.

解決:App.config中,使用NuGet安裝了SQLite,SQLite.Core,EF……之後,默認的配置是這樣的:

<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" />
</providers>

而異常提示 provider with invariant name ‘System.Data.SQLite’,所以只需要加上這行provider 配置就可以。

<providers>
  <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
</providers>

二、“System.InvalidOperationException”類型的未經處理的異常在 EntityFramework.dll 中發生

其他信息: Unable to determine the principal end of an association between the types ‘SQLiter.TestEF.DbTool.IdCard’ and ‘SQLiter.TestEF.DbTool.Student’. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.

解決:在MVC中如果數據庫表關係是一對一的,需要加 [Required] ,如:

public class Employee
{
    //員工必須有工牌
    [Required]
    public virtual ECard ECard { get; set; }
}

三、“System.Data.Entity.Validation.DbEntityValidationException”類型的未經處理的異常在 EntityFramework.dll 中發生

其他信息: Validation failed for one or more entities. See ‘EntityValidationErrors’ property for more details.

解決:檢查是否有字段是必須的。

注:可以通過catch來獲取更詳細的提示:

catch (DbEntityValidationException dbEx)
{
    foreach (var validationErrors in dbEx.EntityValidationErrors)
    {
        foreach (var validationError in validationErrors.ValidationErrors)
        {
            Console.WriteLine(
                    "Class: {0}, Property: {1}, Error: {2}",
                    validationErrors.Entry.Entity.GetType().FullName,
                    validationError.PropertyName,
                    validationError.ErrorMessage);
        }
    }
}

代碼下載

完整源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章