C# SQLite 數據庫操做

C# SQLite 數據庫操做學習html

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


參考:數據庫

章節:編程

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

正文:工具

1、下載安裝學習

這段時間在學習 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。 ui

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 也拷貝到當前程序運行目錄下(不能引用,只能直接拷貝),不知道是否是新版本的要求。 spa

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

2、數據類型

儲存的數據類型有如下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)類型

3、建立數據庫

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();
}

4、刪除數據庫

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);
    }
}

5、建立表

開始要用到 SQL 命令了。創建一個表的順序以下步驟(也能夠用可視化工具 SQLiteExpert 來建立)
一、創建數據庫鏈接;
二、打開數據庫(若是沒有數據庫,Open 也會新建立一個數據庫);
三、聲明一個 SQLiteCommand 類,主要用來放置和運行 SQL 命令的;
四、把 SQLiteCommand 的 Connection 和 SQLiteConnection 聯繫起來(切記,常常忘^_^!);
五、往 SQLiteCommand 的 CommandText 輸入 SQL 語句 CREATE TABLE 語句,具體請參考 安裝目錄下的 SQLite.NET.chm 或 SQLite 建立表
六、調用 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),所有小寫也不會出錯。

6、刪除表

和創建表的步驟同樣,只是把 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();
}

7、查詢表結構

須要用到 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();
}

8、更改表名

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

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

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

9、增添列(字段)

仍是用 SQL 命令 ALTER TABLE ,下例中爲 t1 表添加一個名爲 age,數據類型爲 int 的新列:

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

10、讀取建立表的 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();

11、更改列名

SQLite 中並無提供直接更改列名與刪除列的命令,有兩種方式,
第一種是:
一、把目標表更名;
二、建立一個帶有新列名的新表;
三、把舊錶數據拷貝至新表(記得要 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();
}

12、刪除列

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;
}

十3、插入數據

插入數據主要是用 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();

十4、替換數據

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();

十5、更新數據

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();

十6、刪除數據

SQL 命令:DELETE FROM tablename WHERE 條件

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

十7、查詢數據

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();

十8、獲取查詢數據的行數(多少條記錄)

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

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

十9、事務

事務就是對數據庫一組按邏輯順序操做的執行單元。用事務的好處就是成熟的數據庫都對 密集型的磁盤 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();
    }
}


二10、整理數據庫

SQLite 的自帶命令 VACUUM。用來從新整理整個數據庫達到緊湊之用,好比把刪除的完全刪掉等等。

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


到這裏 SQLite 數據庫基本上能操做了,至於那些用 linq 操做等的須要安裝 ORM 的,我想了一下,下次再學習吧。對於個人小項目來講,帶着兩個加起來不到 1.5M的 dll ,仍是很簡練的。 SQLite 也是數據庫,主要的仍是各類 SQL 語句的調用,着眼於 SQL 語句的學習是下段時間我折騰的目標。 看到滿大街的 SQLiteHelper ,我想了下,就我這水平就不班門弄斧了,即便我也會偷偷寫個,方便調用。

相關文章
相關標籤/搜索