在工控领域,经常遇到断电关机数据库文件损坏的情况,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读取时跳过无法解析的错误行,避免了应用崩溃。