1. 程式人生 > >C#利用System.Data.SQLite實現對SQLite的操作

C#利用System.Data.SQLite實現對SQLite的操作

SQLite介紹

SQLite是一個類似於Access的單機版資料庫管理系統,它將所有資料庫的定義(包括定義、表、索引和資料本身)都儲存在一個單一的檔案中。並且,SQLite是一個用C實現的類庫,它在記憶體消耗、檔案體積、簡單性方面都有不錯的表現,如果資料在10W條以下,查詢速度也是相當快的。
SQLite具有以下特徵:
實現多數SQL92的標準,包括事務(原子性、一致性、隔離性和永續性)、觸發器和大多數的複雜查詢。
不對插入或者更新的資料進行型別檢查,你可以將字串插入到整數列中(這個可能讓有些使用者不太適應)。
支援Windows/Linux/Unix等主流系統,還支援嵌入式系統如Android或Windows Mobile。

System.Data.SQLite的前期準備

1、System.Data.SQLite 庫下載,用於C#操作SQLite的dll檔案。下載地址:http://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki
2、SQLite視覺化工具下載,用於檢視SQLite庫資料表資料。下載地址:http://www.sqliteexpert.com/download.html

System.Data.SQLite通用類

可以分為以下幾種情況:
1、建立資料庫檔案;
2、返回DataTable;
3、返回DataReader;
4、執行增刪改,返回受影響的行數;
5、執行查詢,返回第一行第一列(通常用於帶有行函式的查詢,如SUM/AVG/COUNT等);
6、返回庫中所有的表;
因為在System.Data.SQLite中不存在儲存過程,所以所有的操作都是基於文字的SQL語句,為了避免SQL注入,所以使用了引數化的SQL語句。

一、建立、刪除SQLite資料庫檔案

為了增加格式的規範性,在專案的App.config檔案的配置文字中新增資料庫檔案路徑。
如下connectionStrings標籤:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <connectionStrings>
    <add name="itcastCater" connectionString="data source=C:\Users\**\**\itcastCater.db;version=3;"/>
  </connectionStrings>
</configuration>

執行方法 SQLiteConnection.open 就會建立個空的指定名字的資料庫檔案。

using System.Data.SQLite;
using System.Configuration;

 //從app.config配置文字中讀取連線字串,獲取資料庫檔案的絕對路徑“Data Sounce”
    private static string connStr = ConfigurationManager.ConnectionStrings["itcastCater"].ConnectionString;
    
static void Create()
{
    SQLiteConnection cn = new SQLiteConnection(connStr);
    //按照路徑建立資料庫檔案
    cn.Open();
    cn.Close();
}
 

由於資料庫是檔案型別的,直接用System.IO.File.Delete(string path) 方法來刪除檔案。

//刪除資料庫
static void Delete()
{
    if (System.IO.File.Exists(connStr))
    {
        System.IO.File.Delete(connStr);
    }
}

二、建立、刪除資料庫表

建立表格要用到 SQL 命令。
建立一個表的順序如下步驟(也可以用視覺化工具 SQLiteExpert 來建立):
1、建立資料庫連線;
2、開啟資料庫(如果沒有資料庫,Open 也會新建立一個數據庫);
3、宣告一個 SQLiteCommand 類,主要用來放置和執行 SQL 命令的;
4、把 SQLiteCommandConnectionSQLiteConnection 聯絡起來;
5、往 SQLiteCommandCommandText 輸入 SQL 語句 CREATE TABLE 語句;
6、呼叫 SQLiteCommand.ExcuteNonQuery() 方法執行。

//建立資料庫表
static void CreateTable()
{
    SQLiteConnection cn = new SQLiteConnection(connStr);//建立資料庫連線
    if (cn.State!= System.Data.ConnectionState.Open)
    {
        cn.Open();//開啟資料庫
        SQLiteCommand cmd = new SQLiteCommand();
        cmd.Connection = cn;//把 SQLiteCommand的 Connection和SQLiteConnection 聯絡起來
        cmd.CommandText = "CREATE TABLE IF NOT EXISTS t1(id varchar(4),score int)";//輸入SQL語句
        cmd.ExecuteNonQuery();//呼叫此方法執行
    }
    cn.Close();
}

上面的SQL語句中存在一句“IF NOT EXISTS”,最好加上此句,如果不加此句且原資料庫檔案要是存在同名要建立的表,會出現報錯。SQL語句大小寫並沒有規定,一般為了規範用大寫。

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

//刪除資料庫表
static void DeleteTable()
{
    SQLiteConnection cn = new SQLiteConnection(connStr);
    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();
}

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

//在與資料庫檔案連線成功後
cmd.CommandText = "ALTER TABLE t1 RENAME TO t3";
cmd.ExecuteNonQuery();

三、查詢表結構

查詢表的結構需要用到SQLite特殊的PRAGMA命令
以下為SQliteDataReader讀出來的資料順序列表:

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

程式碼如下:

//連線到資料庫檔案,使用PRAGMA命令準備
SQLiteConnection cn = new SQLiteConnection(connStr);
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
當 type = table 時,name 和 tbl_name 是一樣的,其他比如 type =index 、view 之類時,tbl_name 才是表名。

//遍歷查詢表結構
static void QueryAllTableInfo()
{
    SQLiteConnection cn = new SQLiteConnection(connStr);
    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 表新增一個名為 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[] 中:connStr 資料庫名,1 表名,2 舊列名 3 新列名
static void RenameColumn1(params string[] str)
{
    SQLiteConnection cn = new SQLiteConnection(connStr);
    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(connStr), 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(connStr);
    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(connStr);
    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[] ,connStr 資料庫路徑,1 表名,2 要刪除的列名
static void DeleteColumn2(params string[] str)
{
    SQLiteConnection cn = new SQLiteConnection(connStr);
    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(connStr);
    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 命令 REPLACE 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 [email protected],[email protected] 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();