Sqlite——内存数据库 & 单元测试 & NHibernate

用Sqlite的内存数据库对nhibernate进行单元测试

 

针对数据访问代码的单元测试处在一个尴尬的位置上,如果操作不是针对真实的数据库执行的,就无法捕获数据库特定的错误,比如 sql 语句语法是否正确,操作是否违反了数据库约束,事务是否正确提交。并且,测试之间应该是隔离的,一个测试不能影响另一个测试的数据,就是说,每个测试运行之前都要重建表结构,重新安装测试数据。在一个真实的数据库上执行这些操作会让测试成为老牛破车。

所幸的是Sqlite提供了内存数据库,避免磁盘IO可以带来性能提升。内存数据库有一个非常重要的特点:即数据库仅在连接打开的时候存在,一旦连接关闭,数据库随即消失。这正是我们想要的,运行测试的步骤如下:

1,在 [TestInitialize] 方法中打开 Session,创建表结构,安装测试数据默认情况下,Sqlite的Unicode字符串比较是区分大小写的,所以,创建表结构的时候要为 TEXT 列指定 COLLATE NOCASE。

2,运行测试

3,在 [TestCleanup] 方法中关闭 Session,这将导致底层的连接关闭,内存数据库消失

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
[TestClass]
public class MyTest
{
    [AssemblyInitialize]
    public static void InitAssembly(TestContext testContext)
    {
        NHibernateHelper.Init();
    }
 
    [AssemblyCleanup]
    public static void CleanupAssembly()
    {
        NHibernateHelper.CloseSessionFactory();
    }
 
 
    protected ISession Session { get; private set; }
 
    [TestInitialize()]
    public void MyTestInitialize()
    {
         
        try
        {
            // 1 打开 Session
            Session = NHibernateHelper.OpenSession();
 
            // 2 创建表
            NHibernate.Tool.hbm2ddl.SchemaExport export = new NHibernate.Tool.hbm2ddl.SchemaExport(NHibernateHelper.Configuration);
            export.SetDelimiter(";");
 
            StringWriter sw = new StringWriter();
            export.Execute(false, false, false, Session.Connection, sw);
 
            using (IDbCommand cmd = Session.Connection.CreateCommand())
            {
                // 替换字段定义语句
                cmd.CommandText = Regex.Replace(sw.ToString(), @"\s+TEXT\s+", " TEXT COLLATE NOCASE ", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                cmd.ExecuteNonQuery();
            }
 
            // 3 创建测试数据
            using (ITransaction tx = Session.BeginTransaction())
            {
                Role role = new Role();
                role.Name = "admins";
                Session.Save(role);
 
                tx.Commit();
            }
 
 
 
            // 4 清除 Session 缓存
            Session.Clear();
        }
        catch (Exception ex)
        {
            // 如果发生异常,则 TestCleanup 不会执行,因此在这里回收资源
 
            if (Session != null)
            {
                Session.Close();
            }
            throw;
        }
    }
 
    [TestCleanup()]
    public void MyTestCleanup()
    {
        Session.Close();
        Session = null;
    }
 
 
    [TestMethod]
    public void MyTestMethod()
    {
        using (ITransaction tx = Session.BeginTransaction())
        {
            Role role = Session.Query<Role>().FirstOrDefault(x => x.Name == "admins");
            Assert.IsNotNull(role);
 
            tx.Commit();
        }
 
 
    }
}

NHibernate 配置,注意 connection.release_mode 属性一定要设为 on_close

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <bytecode-provider type="null"/>
  <reflection-optimizer use="false"/>
  <session-factory>
    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="connection.driver_class">NHibernate.Driver.SQLite20Driver</property>
    <property name="connection.connection_string">
      data source=:memory:
    </property>
    <property name="dialect">NHibernate.Dialect.SQLiteDialect</property>
    <property name="connection.release_mode">on_close</property>
    <property name="hbm2ddl.keywords">none</property>
    <property name="current_session_context_class">managed_web</property>
    <property name="show_sql">false</property>
    <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
    <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
    <mapping assembly="WuTong"/>
  </session-factory>
</hibernate-configuration>

参考链接:

http://ayende.com/blog/3983/nhibernate-unit-testing

http://www.sqlite.org/faq.html#q18

 

 

SQLite内存数据库

 

SQLite 介绍
一. SQLite 是实现了SQL 92标准的一个大子集的嵌入式数据库.其以在一个库中组合了数据库引擎和接口,能将所有数据存储于单个文件中而著名.我觉得SQLite的功能一定程度上居于MySQL 和PostgreSQL之间.尽管如此,在性能上面,SQLite常常快2-3倍 (甚至更多).这利益于其高度调整了的内部架构,因为它除去了服务器端到客户端和客户端到服务器端的通信


二. 而令人印象深刻的特点是你可将你的整个数据库系统放在其中.利用非常高效的内存组织,SQLite只需在很小的内存中维护其很小的尺寸,远远比其它任何数据库系统都小.这些特点使得其成为在需要高效地应用数据库的任务中一个非常方便的工具.


SQLite 优势
一 . 除了速度和效率,SQLite还有其它好多的优势使得其能成为许多任务中一个理想的解决方案.因为SQLite的数据库都是简单文件,因此无须一个管理队伍花时间来构造复杂的权限结构来保护用户的数据库.因为权限通过文件系统自动进行.这也同时意味着(数据库空间的大小只与环境有关,与本身无关)无段特殊的规则来了解用户磁盘空间.用户可以从创建他们想要的任意多的数据库和对其对这些数据库的绝对控制权而得到好处.

 

 二  . 数据库就是一个文件的事实使用SQLite可以轻易地在服务器间移动.SQLite也除去了需要大量内存和其它系统资源的伺候进程.即使当数据库在大量地使用时也是如此.

 

 

创建内存数据库

1.由于业务的需要,我使用SQLite 内存数据库制作一个缓存,IIS 停止内存数据库的数据全部丢失,不知道SQLite  是否可以做分布式的内存数据管理。

 

2.添加using System.Data.SQLite  引用,程序集请到这里http://sqlite.phxsoftware.com/ 下载

 

3.创建内存数据库 , Data Source=:memory:;Version=3;  这个连接字符串折腾了很久, 当然这里也可以使用文件来存储数据,只需要修改 Data Source=:memory:  为  Data Source= Cache.db  就ok !当然这个表需要在Global 里面创建 

 

全局属性,打开数据库连接后创建好表后赋值给这个属性,以后操作数据一致使用这个连接。

 

  public static SQLiteConnection SQLiteConn { get; set; }

 

 

 

使用SQL  语句操作表如下, sql 是代表sql 语句

 

 
 

 

 

string sql = "Update AVTable Set CityPair='" + av.CityPair + "',CacheTime='" + DateTime.Now.ToString() + "',AVNote='" + av.AVNote + "' Where FlightNo='" + av.FlightNo + "' And FlightDate='" + av.FlightDate + "'";
               IDbCommand dbcmd = MemoryDatabse.SQLiteConn.CreateCommand();
               dbcmd.CommandText = sql;
               dbcmd.Connection = MemoryDatabse.SQLiteConn;
               int count = dbcmd.ExecuteNonQuery();

 

 

SQLite 的查询语法 和 SQL SERVER 有一些区别 。

 

 提取 avcache表 11-20 的数据 。

Select * From avcache Limit 9 Offset 10;     以上语句表示从avcache 表获取数据,跳过10行,取9行 。

 

另外一种写法, 从10 开始提取 提取9条数据 。

 select * from avcache limit10,9   

 

 语法

 sql = "select * from avcache  where "+条件+" order by "+排序+" limit "+要显示多少条记录+" offset "+跳过多少条记录;

 

 例子

 select * from avcache limit 15 offset 20      意思是说:   从avcache  表跳过20条记录选出15条记录

 

由于最近工作需要开始学习SQLite  ,写的难免有些粗糙的地方,希望大家指正,提供更好的方法!

 

SQLite学习手册(内存数据库)

一、内存数据库:

    在SQLite中,数据库通常是存储在磁盘文件中的。然而在有些情况下,我们可以让数据库始终驻留在内存中。最常用的一种方式是在调用sqlite3_open()的时候,数据库文件名参数传递":memory:",如:
    rc = sqlite3_open(":memory:", &db);
    在调用完以上函数后,不会有任何磁盘文件被生成,取而代之的是,一个新的数据库在纯内存中被成功创建了由于没有持久化,该数据库在当前数据库连接被关闭后就会立刻消失。需要注意的是,尽管多个数据库连接都可以通过上面的方法创建内存数据库,然而它们却是不同的数据库,相互之间没有任何关系。事实上,我们也可以通过Attach命令将内存数据库像其他普通数据库一样,附加到当前的连接中,如:
    ATTACH DATABASE ':memory:' AS aux1;

                    string v = connectionString ?? $"Data Source={contextType.Name};Mode=Memory;Cache=Shared;";
                    var connection = new SqliteConnection(v);
                    connection.Open();

 

二、临时数据库:

    在调用sqlite3_open()函数或执行ATTACH命令时,如果数据库文件参数传的是空字符串,那么一个新的临时文件将被创建作为临时数据库的底层文件,如:
    rc = sqlite3_open("", &db);
    或
    ATTACH DATABASE '' AS aux2;
    和内存数据库非常相似,两个数据库连接创建的临时数据库也是各自独立的,在连接关闭后,临时数据库将自动消失,其底层文件也将被自动删除。
    尽管磁盘文件被创建用于存储临时数据库中的数据信息,但是实际上临时数据库也会和内存数据库一样通常驻留在内存中,唯一不同的是,当临时数据库中数据量过大时,SQLite为了保证有更多的内存可用于其它操作,因此会将临时数据库中的部分数据写到磁盘文件中,而内存数据库则始终会将数据存放在内存中

 

 

FIREDAC操作SQLITE内存数据库

 

SQLite不仅可以把数据库放在硬盘上,还可以放在内存中,经测试,同样条件下数据库放在内存中比放在硬盘上插入记录速度快差不多3倍。

但数据库放在内存中时有如下缺陷

1、断电或程序崩溃后数据库就会消失,你需要定期Attach到硬盘上备份

2、在内存中的数据库不能被别的进程访问(因为没名字,以后可能支持),即使在多线程下,也得使用同一句柄

3、不支持像在硬盘上的读写互斥处理,需要自己加锁

4、只能本进程使用,其它进程无法使用

1)FIREDAC创建和使用参数打开一个SQLite内存数据库
//  建立一个内存数据库,存放当前登录信息
FDConnection1.Params.Add('DriverID=SQLite');
FDConnection1.Params.Add('Database=:memory:');
FDConnection1.Open;
FDConnection1.ExecSQL('CREATE TABLE Log(Id integer PRIMARY KEY)');
// 执行insert语句
FDConnection1.ExecSQL('INSERT INTO Log (1)');

2)使用TFDSQLiteBackup将文件数据库转变为内存数据库。示例代码:
FDConnection1.DriverName := 'SQLite';
FDConnection1.Open;
FDSQLiteBackup1.Database := '\\srv\db\data.sdb';
FDSQLiteBackup1.DestDatabaseObj := FDConnection1.CliObj;
FDSQLiteBackup1.DestMode := smCreate;
FDSQLiteBackup1.Backup;

3)ATTACH(附加数据库)例如:
FDConnection1.ExecSQL('ATTACH ''c:\hr.sdb'' AS hr');
FDConnection1.ExecSQL('ATTACH ''c:\cust.sdb'' AS cust');
FDQuery1.Open('select * from 'Orders' o ' +
'left join hr.'Employees' e on o.EmployeeID = e.EmployeeID ' +
'left join cust.'Customers' c on o.CustomerID = c.CustomerID');
注意,firedac把数据库名作为目录名称理解。

4)将SQLITE内存数据同步到文件数据库中。

实现思路如下:

1、创建文件数据库

2、创建内存数据库(文件数据库、内存数据库的内幕表结构需要一致)

3、在内存数据库中attach文件数据库,这样可以保证文件数据库中的内容在内存数据库中可见;

4、对于insert、select、delete、update操作,在内存数据库中操作

5、定时将内存数据库中的内容同步到文件数据库

 

posted @ 2020-05-15 11:38  PanPan003  阅读(841)  评论(0编辑  收藏  举报