1. 程式人生 > 資料庫 >轉 C# SQLite 資料庫操作學習

轉 C# SQLite 資料庫操作學習

執行環境:Window7 64bit,.NetFramework4.61,C# 7.0; 編者:烏龍哈里 2017-03-19


參考:

章節:

  1. 資料型別
  2. 建立資料庫
  3. 刪除資料庫
  4. 建立表
  5. 刪除表
  6. 查詢表結構
  7. 更改表名
  8. 增加列(欄位)
  9. 讀取建立表的 SQL 語句
  10. 更改列名
  11. 刪除列
  12. 插入資料
  13. 替換資料
  14. 更新資料
  15. 刪除資料
  16. 查詢資料
  17. 獲取查詢資料的行數(多少條記錄)
  18. 事務 Transaction
  19. 整理資料庫

正文:

一、下載安裝

這段時間在學習 C# 程式設計中,想寫一個簡單的進銷存程式,就想到了用資料庫,需要一個簡單便攜的桌面資料庫,想自己寫個,功力太淺,可以做為以後練手學習的專案。原來會用的 Foxpro 已經被微軟不知丟在哪個旮旯了,在網上找了一下,發現只有 Access 和 Sqlite 可選,看了很多對比,決定還是學習使用 Sqlite。

在 的 download 中的 Setups for 64-bit Windows (.NET Framework 4.6) 下載然後執行安裝。
更簡單的做法是在 Visual Studio 2017 的 NuGet 中,輸入:install-package system.data.sqlite.x64。

sqlite資料庫的視覺化工具中,不錯,下載。

工具備齊了,由於知道上面這個System data Sqlite 是用 C# 封裝好的,下來我們開啟Visual Studio 2017,新開個工程,在選單“專案”→“新增引用”→“瀏覽” 中,去 Sqlite 的安裝目錄下選擇 System.Data.SQLite.dll,才305k的連結庫。引用了後,在VS右上角的“解決方案資源管理器”中看看引用下的 System.Data.SQlite 的引用屬性中的“複製到本地” 是不是 true,不是的話弄成 true。在工程中開頭新增 using 語句:


using System.Data.SQLite;

網上很多教程到這就ok了,但在我實際操作中,發現還要把 SQLite.Interop.dll 也拷貝到當前程式執行目錄下(不能引用,只能直接拷貝),不知道是不是新版本的要求。

(ps:在 sqlite 的安裝目錄下有很詳細的幫助文件 SQLite.NET.chm)

二、資料型別

儲存的資料型別有以下5種:

儲存類描述
NULL 一個NULL值
INTERGER 帶符號的整數,根據值的大小,自動儲存為1,2,3,4,5,8位元組6種
REAL 浮點數,儲存為IEEE 8byte浮點數
TEXT 文字字串,預設的編碼為utf-8
BLOG blob資料,不定長

注意了,SQLite 的儲存寬度是根據輸入來自動調整的,這點和原來我用過的 foxpro 不一樣,比如就算你在 create 資料表中設定了一個欄位 varchar(4) 4byte寬的字串,但你輸入了“hello”5個寬度的字串時,它並不會擷取成“hell”,而是完整地儲存為“hello”。數字型別也是如此。

還有更有趣的是,它有個Type Affinity 功能,比如如下:

CREATE TABLE t1(a INT, b VARCHAR(10));
INSERT INTO t1(a,b) VALUES('123',456);

它會運用 Type Affinity 功能自動正確地把 "123" 轉換成數字,把 456轉化成“456”字串。這個Type Affinity 請參考安裝目錄下的 幫助檔案或 。

三、建立資料庫

SQLite 是檔案型的資料庫,建立很簡單,直接指定一個數據庫檔名,字尾名不一定非得是“.sqlite”,字尾隨便命名為".db"也成。執行 SQLiteConnection.open 就會建立個空的指定名字的資料庫檔案。由於它是檔案型的,我們也可以直接用 System.IO.File.Create() 來建立一個空的檔案。

using System.Data.SQLite;
//---建立資料庫
static void CreateDB()
{
    string path = @"d:\test\123.sqlite";
    SQLiteConnection cn = new SQLiteConnection("data source=" + path);
    cn.Open();
    cn.Close();
}

四、刪除資料庫

sqlite 命令中好像沒有提供刪除整個資料庫的命令,但是由於它是個檔案型的,我們直接用 System.IO.File.Delete(string path) 方法來刪除檔案。

//---刪除資料庫
static void DeleteDB()
{
    string path = @"d:\test\123.sqlite";
    if (System.IO.File.Exists(path))
    {
        System.IO.File.Delete(path);
    }
}

五、建立表

開始要用到 SQL 命令了。建立一個表的順序如下步驟(也可以用視覺化工具 SQLiteExpert 來建立):
1、建立資料庫連線;
2、開啟資料庫(如果沒有資料庫,Open 也會新建立一個數據庫);
3、宣告一個 SQLiteCommand 類,主要用來放置和執行 SQL 命令的;
4、把 SQLiteCommand 的 Connection 和 SQLiteConnection 聯絡起來(切記,經常忘^_^!);
5、往 SQLiteCommand 的 CommandText 輸入 SQL 語句 CREATE TABLE 語句,具體請參考 安裝目錄下的 SQLite.NET.chm 或 ;
6、呼叫 SQLiteCommand.ExcuteNonQuery() 方法執行。

//---新增表
static void CreateTable()
{
    string path = @"d:\test\123.sqlite";
    SQLiteConnection cn = new SQLiteConnection("data source="+path);
    if (cn.State!= System.Data.ConnectionState.Open)
    {
        cn.Open();
        SQLiteCommand cmd = new SQLiteCommand();
        cmd.Connection = cn;
        cmd.CommandText = "CREATE TABLE t1(id varchar(4),score int)";
        //cmd.CommandText = "CREATE TABLE IF NOT EXISTS t1(id varchar(4),score int)";
        cmd.ExecuteNonQuery();
    }
    cn.Close();
}

注意上面那句被註釋掉的 CREATE TABEL IF NOT EXISTS ,一般情況下用這句比較好,如果原來就有同名的表,沒有這句就會出錯。SQL 語句其實也不用全部大寫,全部大寫是 SQL 語句約定俗成的(令我想起讀書的時候學的 COBOL),全部小寫也不會出錯。

六、刪除表

和建立表的步驟一樣,只是把 SQL 語句改了而已。

//---刪除表
static void DeleteTable()
{
    string path = @"d:\test\123.sqlite";
    SQLiteConnection cn = new SQLiteConnection("data source=" + path);
    if (cn.State != System.Data.ConnectionState.Open)
    {
        cn.Open();
        SQLiteCommand cmd = new SQLiteCommand();
        cmd.Connection = cn;
        cmd.CommandText = "DROP TABLE IF EXISTS t1";
        cmd.ExecuteNonQuery();
    }
    cn.Close();
}

七、查詢表結構

需要用到 SQLite 特殊的 PRAGMA 命令, 具體參見
PRAGMA table_info(tablename) ,tablename 用或不用單引號 ' ' 括起來都一樣。
SQliteDataReader 讀出來的資料順序代表:

下標名稱描述
0 cid 序號
1 name 名字
2 type 資料型別
3 notnull 能否null值,0不能,1 能
4 dflt_value 預設值
5 pk 是否主鍵primary key,0否,1是

string path = @"d:\test\123.sqlite";
SQLiteConnection cn = new SQLiteConnection("data source=" + path);
cn.Open();
SQLiteCommand cmd = cn.CreateCommand();

cmd.CommandText= "PRAGMA table_info('t1')";

//寫法一:用DataAdapter和DataTable類,記得要 using System.Data
SQLiteDataAdapter adapter = new SQLiteDataAdapter(cmd);
DataTable table = new DataTable();
adapter.Fill(table);
foreach(DataRow r in table.Rows)
{
    Console.WriteLine($"{r["cid"]},{r["name"]},{r["type"]},{r["notnull"]},{r["dflt_value"]},{r["pk"]} ");
}
Console.WriteLine();

//寫法二:用DataReader,這個效率高些
SQLiteDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
    for(int i = 0; i < reader.FieldCount; i++)
    {
        Console.Write($"{reader[i]},");
    }
    Console.WriteLine();
}
reader.Close();

如果不止一個表,要遍歷所有表的結構如下,就要用到 SQLite 中的特殊表 sqlite_master,它的結構如下:
參考:

CREATE TABLE sqlite_master(
type text,
name text,
tbl_name text,
rootpage integer,
sql text
);
當 type = table 時,name 和 tbl_name 是一樣的,其他比如 type =index 、view 之類時,tbl_name 才是表名。

//---遍歷查詢表結構
static void QueryAllTableInfo()
{
    string path = @"d:\test\123.sqlite";
    SQLiteConnection cn = new SQLiteConnection("data source=" + path);
    if (cn.State != System.Data.ConnectionState.Open)
    {
        cn.Open();
        SQLiteCommand cmd = new SQLiteCommand();
        cmd.Connection = cn;
        cmd.CommandText = "SELECT name FROM sqlite_master WHERE TYPE='table' ";
        SQLiteDataReader sr = cmd.ExecuteReader();
        List<string> tables = new List<string>();
        while (sr.Read())
        {
            tables.Add(sr.GetString(0));
        }
        //datareader 必須要先關閉,否則 commandText 不能賦值
        sr.Close();
        foreach (var a in tables)
        {
            cmd.CommandText = $"PRAGMA TABLE_INFO({a})";
            sr = cmd.ExecuteReader();
            while (sr.Read())
            {
                Console.WriteLine($"{sr[0]} {sr[1]} {sr[2]} {sr[3]}");
            }
            sr.Close();
        }
    }
    cn.Close();
}

八、更改表名

用 SQL 語句 ALTER TABLE 把 t1 表名改成 t3:

cmd.CommandText = "ALTER TABLE t1 RENAME TO t3";
cmd.ExecuteNonQuery();

注意,表名是不分大小寫的,也不用加單引號括起來。

九、增添列(欄位)

還是用 SQL 命令 ALTER TABLE ,下例中為 t1 表新增一個名為 age,資料型別為 int 的新列:

cmd.CommandText = "ALTER TABLE t1 ADD COLUMN age int";
cmd.ExecuteNonQuery();

十、讀取建立表的 SQL 語句

讀取建立表時的 SQL 語句,在 SqliteExpert 中的 DDL 可以檢視到。讀取這個是為下面增添刪除列做準備。

cmd.CommandText = "SELECT sql FROM sqlite_master WHERE TYPE='table'";
SQLiteDataReader sr = cmd.ExecuteReader();
while (sr.Read())
{
    Console.WriteLine(sr[0].ToString());
}
sr.Close();

十一、更改列名

SQLite 中並沒有提供直接更改列名與刪除列的命令,有兩種方式,
第一種是:
1、把目標表改名;
2、建立一個帶有新列名的新表;
3、把舊錶資料拷貝至新表(記得要 Connection.BeginTransaction())。

第二種是更改 sqlite_master 裡面的 schema,很容易損壞資料庫。
依據是 SQLite 每次連線時,其實都是依據 schema 裡面的每個表建立時的 CREATE TABLE 語句來動態建立 column 的資訊的,只要 column 的資料型別和位置不變,更改 CREATE TABLE 語句就能更改 column 的資訊。具體參考。以下我們兩種方法都寫來看看。

方式一:

//---更改列名1
//總思路:把舊錶更名,建個帶新列名的新表,拷貝資料
//params string[] 中:0 資料庫名,1 表名,2 舊列名 3 新列名
static void RenameColumn1(params string[] str)
{
    SQLiteConnection cn = new SQLiteConnection("data source=" + str[0]);
    cn.Open();
    SQLiteCommand cmd = new SQLiteCommand();
    cmd.Connection = cn;
    
    //取得str[1]表名的表的建表SQL語句
    cmd.CommandText = "SELECT name,sql FROM sqlite_master WHERE TYPE='table' ORDER BY name";
    SQLiteDataReader sr = cmd.ExecuteReader();

    string _sql = "";
    while (sr.Read())
    {
        if (string.Compare(sr.GetString(0), str[1], true) == 0)
        {
            _sql = sr.GetString(1);
            break;
        }
    }
    sr.Close();

    //更改舊錶名為 帶 _old
    string _old = str[1] + "_old";
    cmd.CommandText = $"ALTER TABLE {str[1]} RENAME TO {_old}";
    cmd.ExecuteNonQuery();

    //建立新表,假設輸入的舊列名和表中的列名大小寫等完全一致,不寫能容錯的了
    _sql = _sql.Replace(str[2],str[3]);
    cmd.CommandText = _sql;
    cmd.ExecuteNonQuery();

    //拷貝資料
    using (SQLiteTransaction tr = cn.BeginTransaction())
    {
        cmd.CommandText = $"INSERT INTO {str[1]} SELECT * FROM {_old}";
        cmd.ExecuteNonQuery();
        cmd.CommandText = $"DROP TABLE {_old}";
        cmd.ExecuteNonQuery();
        tr.Commit();
    }
    cn.Close();
}

方式二:

//---更改列名2,改寫schema裡建表時的sql語句
//原理:sqlite 每次開啟的時候,都是依據建表時的sql語句來動態建立column的資訊的
static void RenameColumn2(params string[] str)
{
    SQLiteConnection cn = new SQLiteConnection("data source=" + str[0]);
    cn.Open();
    SQLiteCommand cmd = new SQLiteCommand();
    cmd.Connection = cn;

    //取得str[1]表名的表的建表SQL語句
    cmd.CommandText = $"SELECT sql FROM sqlite_master WHERE TYPE='table' AND name='{str[1]}'";
    SQLiteDataReader sr = cmd.ExecuteReader();
    sr.Read();
    string _sql = sr.GetString(0);
    sr.Close();
    //注意單引號 '
    _sql =$"UPDATE sqlite_master SET sql='{_sql.Replace(str[2],str[3])}' WHERE name= '{str[1]}' ";

    //設定 writable_schema 為 true,準備改寫schema
    cmd.CommandText = "pragma writable_schema=1";
    cmd.ExecuteNonQuery();
    cmd.CommandText = _sql;
    cmd.ExecuteNonQuery();
    //設定 writable_schema 為 false。
    cmd.CommandText = "pragma writable_schema=0";
    cmd.ExecuteNonQuery();

    cn.Close();
}

十二、刪除列

SQLite 也沒有提供刪除列的命令。和上面一樣,也是兩種方式。
其一,把目標表改名,建立沒有要刪除列(欄位)的新表,然後把舊錶的資料拷貝至新表。
其二,直接修改 schema 中建表的 SQL 語句。
其中最主要的是要把建表的列的所有資訊都儲存下來,比如索引、預設值之類的。下面示例使用第二種方式。

//---刪除列2,string[] ,0 資料庫路徑,1 表名,2 要刪除的列名
static void DeleteColumn2(params string[] str)
{
    SQLiteConnection cn = new SQLiteConnection("data source=" + str[0]);
    cn.Open();
    SQLiteCommand cmd = new SQLiteCommand();
    cmd.Connection = cn;
    //取得str[1]表名的表的建表SQL語句
    cmd.CommandText = $"SELECT sql FROM sqlite_master WHERE TYPE='table' AND name='{str[1]}'";
    SQLiteDataReader sr = cmd.ExecuteReader();
    sr.Read();
    string _sql = sr.GetString(0);
    sr.Close();

    //取得列的定義
    //C#7.0的新特徵,Tuple<>的語法糖,需要 NuGet install-package system.valuetuple
    List<(string name, string define)> list = GetColumnDefine(_sql);
    //取得要刪除列的序號
    int _index = list.IndexOf(list.Where(x => x.name == str[2]).First());
    //建立新的sql語句
    StringBuilder sb = new StringBuilder();
    sb.Append($"CREATE TABLE {str[1]}(");
    for (int i = 0; i < list.Count; i++)
    {
        if (i != _index)
        {
            sb.Append($"{list[i].define},");
        }
    }
    sb.Remove(sb.Length - 1, 1);
    sb.Append(")");
    //改寫schema
    _sql = $"UPDATE sqlite_master SET sql='{sb.ToString()}' WHERE name='{str[1]}'";
    //設定 writable_schema 為 true,準備改寫schema
    cmd.CommandText = "pragma writable_schema=1";
    cmd.ExecuteNonQuery();
    cmd.CommandText = _sql;
    cmd.ExecuteNonQuery();
    //設定 writable_schema 為 false。
    cmd.CommandText = "pragma writable_schema=0";
    cmd.ExecuteNonQuery();

    cn.Close();
}
//---取得列的定義
static List<(string, string)> GetColumnDefine(string SqlStr)
{
    int n = 0;
    int _start = 0;
    string _columnStr = "";
    for (int i = 0; i < SqlStr.Length; i++)
    {
        if (SqlStr[i] == '(')
        {
            if (n++ == 0) { _start = i; }
        }
        else
        {
            if (SqlStr[i] == ')')
            {
                if (--n == 0)
                {
                    _columnStr = SqlStr.Substring(_start + 1, i - _start - 1);
                    break;
                }
            }

        }
    }
    string[] ss = _columnStr.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
    //C#7.0的新特徵,Tuple<>的語法糖,需要 NuGet install-package system.valuetuple
    List<(string name, string define)> reslut = new List<(string name, string define)>();
    foreach (var a in ss)
    {
        string s = a.Trim();
        n = 0;
        for (int i = 0; i < s.Length; i++)
        {
            if (s[i] == ' ')
            {
                reslut.Add((s.Substring(0, i), s));
                break;
            }
        }
    }
    return reslut;
}

十三、插入資料

插入資料主要是用 SQL 語句 INSERT INTO

示例1(簡單插入):

cmd.CommandText = "INSERT INTO t1 VALUES('99999',11)";
cmd.ExecuteNonQuery();

示例2(變數插入,要引用 System.Data):

using System.Data;

string s = "123456";
int n = 10;
cmd.CommandText = "INSERT INTO t1(id,age) VALUES(@id,@age)";
cmd.Parameters.Add("id", DbType.String).Value = s;
cmd.Parameters.Add("age", DbType.Int32).Value = n;
cmd.ExecuteNonQuery();

十四、替換資料

SQL 命令 INSERT INTO。
下面示例中, t1 表中 id 為主鍵,相同主鍵值的就 UPDATE,否則就 INSERT

string s = "123456";
int n = 30;
cmd.CommandText = "REPLACE INTO t1(id,age) VALUES(@id,@age)";
cmd.Parameters.Add("id", DbType.String).Value = s;
cmd.Parameters.Add("age", DbType.Int32).Value = n;
cmd.ExecuteNonQuery();

十五、更新資料

SQL 命令 UPDATE tablename SET column1=value,column2=value... WHERE 條件

string s = "333444";
int n = 30;
cmd.CommandText = "UPDATE t1 SET id=@id,age=@age WHERE id='0123456789'";
cmd.Parameters.Add("id", DbType.String).Value = s;
cmd.Parameters.Add("age", DbType.Int32).Value = n;
cmd.ExecuteNonQuery();

十六、刪除資料

SQL 命令:DELETE FROM tablename WHERE 條件

cmd.CommandText = "DELETE FROM t1 WHERE id='99999'";
cmd.ExecuteNonQuery();

十七、查詢資料

SQL 命令:SELETE 語句,具體的請參考。

//查詢第1條記錄,這個並不保險,rowid 並不是連續的,只是和當時插入有關
cmd.CommandText = "SELECT * FROM t1 WHERE rowid=1";
SQLiteDataReader sr = cmd.ExecuteReader();
while (sr.Read())
{
    Console.WriteLine($"{sr.GetString(0)} {sr.GetInt32(1).ToString()}");
}
sr.Close();
//執行以下的就能知道 rowid 並不能代表 行數
cmd.CommandText = "SELECT rowid FROM t1 ";
sr = cmd.ExecuteReader();
while (sr.Read())
{
    Console.WriteLine($"{sr.GetString(0)} {sr.GetInt32(1).ToString()}");
}
sr.Close();

十八、獲取查詢資料的行數(多少條記錄)

從上面示例中我們得知,rowid 並不是正確的行數(記錄數),而是 INSERT 的時候的B-Tree 的相關數。
如要知道表中的行數(記錄數),要如下:

cmd.CommandText = "SELECT count(*) FROM t1";
sr = cmd.ExecuteReader();
sr.Read();
Console.WriteLine(sr.GetInt32(0).ToString());
sr.Close();

十九、事務

事務就是對資料庫一組按邏輯順序操作的執行單元。用事務的好處就是成熟的資料庫都對 密集型的磁碟 IO 操作之類進行優化,而且還能進行撤回回滾操作。其實在上面改變列名的示例中就用過。

//---事務
static void TransActionOperate(SQLiteConnection cn,SQLiteCommand cmd)
{
    using (SQLiteTransaction tr = cn.BeginTransaction())
    {
        string s = "";
        int n = 0;
        cmd.CommandText = "INSERT INTO t2(id,score) VALUES(@id,@score)";
        cmd.Parameters.Add("id", DbType.String);
        cmd.Parameters.Add("score", DbType.Int32);
        for (int i = 0; i < 10; i++)
        {
            s = i.ToString();
            n = i;
            cmd.Parameters[0].Value = s;
            cmd.Parameters[1].Value = n;
            cmd.ExecuteNonQuery();
        }
        tr.Commit();
    }
}


二十、整理資料庫

SQLite 的自帶命令 VACUUM。用來重新整理整個資料庫達到緊湊之用,比如把刪除的徹底刪掉等等。

cmd.CommandText = "VACUUM";
cmd.ExecuteNonQuery();

 

 


到這裡 SQLite 資料庫基本上能操作了,至於那些用 linq 操作等的需要安裝 ORM 的,我想了一下,下次再學習吧。對於我的小專案來說,帶著兩個加起來不到 1.5M的 dll ,還是很簡練的。
SQLite 也是資料庫,主要的還是各種 SQL 語句的呼叫,著眼於 SQL 語句的學習是下段時間我折騰的目標。
看到滿大街的 SQLiteHelper ,我想了下,就我這水平就不班門弄斧了,即使我也會偷偷寫個,方便呼叫。

 

 

轉自:https://www.cnblogs.com/leemano/p/6578050.html 感謝

的分享