C# SQLite 數據庫操作


C# SQLite 數據庫操作學習

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


參考:

章節:

  1. 下載安裝
  2. 數據類型
  3. 創建數據庫
  4. 刪除數據庫
  5. 創建表
  6. 刪除表
  7. 查詢表結構
  8. 更改表名
  9. 增加列(字段)
  10. 讀取創建表的 SQL 語句
  11. 更改列名
  12. 刪除列
  13. 插入數據
  14. 替換數據
  15. 更新數據
  16. 刪除數據
  17. 查詢數據
  18. 獲取查詢數據的行數(多少條記錄)
  19. 事務 Transaction
  20. 整理數據庫

正文:

一、下載安裝

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

System.Data.SQLite 官網的 download 中的 Setups for 64-bit Windows (.NET Framework 4.6) sqlite-netFx46-setup-x64-2015-1.0.104.0.exe (17.99 MiB) 下載然后運行安裝。
更簡單的做法是在 Visual Studio 2017 的 NuGet 中,輸入:install-package system.data.sqlite.x64。

sqlite數據庫的可視化工具中, SqliteExpert 不錯,下載 SQLite Expert Personal 4.x

工具備齊了,由於知道上面這個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 親和(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 或 SQLite 創建表
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 Statements
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,它的結構如下:
參考:
2.6. Storage Of The SQL Database Schema
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 的信息。具體參考 How do I rename a column in a SQLite database table?。以下我們兩種方法都寫來看看。

方式一:

//---更改列名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 語句,具體的請參考 SQL 教程

//查詢第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 ,我想了下,就我這水平就不班門弄斧了,即使我也會偷偷寫個,方便調用。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM