在工控领域,经常遇到断电关机数据库文件损坏的情况,SQLite如此,LiteDb也是这样。ZTO有数万自动化扫描设备,每天都有数百台设备因断电数据库损坏等原因,而不得不重装软件。因此,设计了CsvDb,用于本地保存少量数据,一般只有几百几千行,极限不超过十万行。

星尘代理StarAgent和物联网客户端IoTClient都使用了CsvDb来存储本地数据。

Nuget包:NewLife.Core

源码:https://github.com/NewLifeX/X/blob/master/NewLife.Core/IO/CsvDb.cs

视频:https://www.bilibili.com/video/BV1eV4y1L7xz


CsvDb内部依赖CsvFile Csv解析CsvFile


插入数据

Add插入一个对象(一行数据)到末尾。

var db = new CsvDb<GeoArea>((x, y) => x.Code == y.Code)
{
    FileName = "data/db.csv"
};
var model = new GeoArea
{
    Code = Rand.Next(),
    Name = Rand.NextString(14),
};
db.Add(model);

// 把文件读出来
var lines = File.ReadAllLines(db.FileName.GetFullPath());
Assert.Equal(2, lines.Length);

实例化CsvDb时,需要指定模型类,它决定数据格式。构造函数中还可以指定比较器,可以理解为主键字段,用于查找目标数据行。

跑起来效果是这样:


查找数据

Find 用于查找一个对象,本质上引擎内部会遍历所有数据行,逐行对比主键。

var db = new CsvDb<GeoArea>((x, y) => x.Code == y.Code)
{
    FileName = "data/db.csv"
};
var model = db.Find(new GeoArea { Code = 1234 });

此外,还可以使用 FindAll 查找多行满足条件的数据,指定委托。

public IList<T> FindAll();
IList<T> FindAll(Func<T, Boolean> predicate, Int32 count = -1);
Int32 FindCount();


更新数据

Update 用于更新一个对象,本质上读取所有数据进入内存,修改对应数据后再写回去,成本较高,在数据量不大(小于1万)时问题不大。

var db = new CsvDb<GeoArea>((x, y) => x.Code == y.Code)
{
    FileName = "data/db.csv"
};
var model = new GeoArea
{
    Code = 1234,
    Name = "Stone",
};
db.Update(model);


删除数据

Remove 用于删除满足条件的对象,本质上读取所有数据进入内存,删除满足条件的对象后写回去,成本较高。如果删除后没有剩余数据行,则直接删除文件。

Int32 Remove(T model);
Int32 Remove(IEnumerable<T> models);
Int32 Remove(Func<T, Boolean> predicate);


总结

数万工控设备多年实践表明,Csv数据库很靠谱!

在断电关机的时候,csv文件同样也会损坏,只不过是CsvDb读取时跳过无法解析的错误行,避免了应用崩溃。