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 ,我想了下,就我这水平就不班门弄斧了,即便我也会偷偷写个,方便调用。