C# SQLite 數據庫操做學習html
運行環境:Window7 64bit,.NetFramework4.61,C# 7.0; 編者:烏龍哈里 2017-03-19sql
參考:數據庫
章節:編程
正文:工具
這段時間在學習 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 ,我想了下,就我這水平就不班門弄斧了,即便我也會偷偷寫個,方便調用。