FileDb - 适用于 Silverlight、Windows Phone 和 .NET 的简单 NoSql 数据库






4.89/5 (18投票s)
如何在 .NET、Silverlight 和 Windows Phone 应用程序中使用 FileDb 作为本地数据库
引言
我开发 FileDb
是为了提供一个简单的本地数据库解决方案,适用于 .NET、Silverlight 和 Windows Phone 应用程序。本文演示了如何使用 FileDb
作为简单的本地数据库解决方案。FileDb
支持每个文件一个表,带有一个索引,该索引可以有一个可选的主键。它还可以存储文件中的单个元数据值(string
或 byte[]
)。FileDb
是 No-SQL,因此没有像 SQL 那样连接表的概念。这种复杂性通常不需要满足本地数据库的需求。但是,在需要连接的情况下,您可以直接对 FileDb
表使用 LINQ,因此您实际上不需要 SQL。通过保持简单,FileDb
DLL 的大小保持非常小(< 100K)——更少的复杂性意味着更少出错的可能性。
FileDb
的搜索功能强大。它支持正则表达式(RegEx)和复合表达式,例如FirstName = 'Bob' and (LastName = 'Smith' or LastName = 'Jones')
。过滤器表达式解析器会为您解析string
表达式,或者您可以自己创建和填充FilterExpression
对象。FileDb
数据库是可加密的(使用 AES)。加密是在记录级别进行的——数据库模式不会被加密。FileDb
支持字段类型Int
、UInt
、Int64
、Decimal
、Bool
、String
、Byte
、Float
、Double
、DateTime
、Guid
、(以及null
)以及相同类型的数组。这些与我们在 .NET 程序中使用的类型相同,因此 .NET 程序员使用FileDb
是很自然的。Int
字段可以自增,并且您可以选择一个字段作为主键,但它必须是Int
或String
类型。FileDb
对多线程环境是线程安全的,因此可以同时从多个线程访问它,而无需担心数据库损坏。- 支持类型化数据集,因此您可以使用内置的 Table 或您自己的 POCO 对象(Plain Old Class Object)。
FileDb
是一个开源项目,托管在 Google Code 上(http://code.google.com/p/filedb-database),由 EzTools Software 提供(www.eztools-software.com)。
背景
需要简单数据库进行本地存储的 .NET 应用程序,通常必须使用过于复杂的解决方案(这些方案通常有其自身的负面问题),或者自己编写解决方案,而这种解决方案并非理想。SQL 数据库本质上是复杂且庞大的。大多数本地数据库不需要那么强大的功能(当然也不需要那么大的占地空间)。对于本地数据库需求,大多数应用程序只需要单个表来存储数据,例如列表和数组值。一直以来缺乏的是一个简单的 .NET No-SQL 数据库来满足此需求。
Using the Code
正如您所料,您使用 FileDb
类对象来与 FileDb
数据库进行交互。主要的 FileDb
类包括:FileDb
、Table
、Field
和 Record
。以下是所有类的列表
FileDb
:表示数据库文件。所有数据库操作都通过此类启动。Table
:表示查询返回的二维dataset
。Table
由Fields
和Records
组成。Field
:定义表列的属性,例如Name
和DataType
。Fields
:Field
对象列表。Record
:数据对象列表,代表Table
中的单行。实现IEnumerable
和Data
属性,用于DataBinding
。Records
:Record
对象列表。FieldValues
:简单的 Name/Value 对Dictionary
。在添加和更新记录时使用此类。FilterExpression
:用于过滤用于查询、更新和删除的记录。FilterExpressionGroup
:用于通过组合FilterExpressions
和FilterExpressionGroups
来创建复合表达式。
首先,我们要明确,FileDb
不是一个多用户数据库——FileDb
数据库只能由单个应用程序打开。任何尝试在文件已打开时打开它的行为都将失败。这符合我们对仅供单个应用程序使用的本地数据库的期望。
好了,现在让我们看看如何使用 FileDb
。
创建数据库
您可以通过编程方式创建数据库,方法是定义字段并将它们添加到数组中,然后调用 FileDb.Create
,如下所示。请注意,我们将 ID
字段设置为自增且为主键。此代码创建一个包含各种字段类型的数据库。
Field field;
var fieldLst = new List<Field>( 20 );
field = new Field( "ID", DataType.Int );
field.AutoIncStart = 0;
field.IsPrimaryKey = true;
fields.Add( field );
field = new Field( "FirstName", DataType.String );
fields.Add( field );
field = new Field( "LastName", DataType.String );
fields.Add( field );
field = new Field( "BirthDate", DataType.DateTime );
fields.Add( field );
field = new Field( "IsCitizen", DataType.Bool );
fields.Add( field );
field = new Field( "DoubleField", DataType.Double );
fields.Add( field );
field = new Field( "ByteField", DataType.Byte );
fields.Add( field );
// array types
field = new Field( "StringArrayField", DataType.String );
field.IsArray = true;
fields.Add( field );
field = new Field( "ByteArrayField", DataType.Byte );
field.IsArray = true;
fields.Add( field );
field = new Field( "IntArrayField", DataType.Int );
field.IsArray = true;
fields.Add( field );
field = new Field( "DoubleArrayField", DataType.Double );
field.IsArray = true;
fields.Add( field );
field = new Field( "DateTimeArrayField", DataType.DateTime );
field.IsArray = true;
fields.Add( field );
field = new Field( "BoolArray", DataType.Bool );
field.IsArray = true;
fields.Add( field );
_db.Create( "MyDatabase.fdb", fieldLst.ToArray() );
添加记录
您通过创建 FieldValues
对象并添加字段值来向数据库添加记录。您不需要表示数据库中的每个字段。缺少的字段将初始化为默认值。
var record = new FieldValues();
record.Add( "FirstName", "Nancy" );
record.Add( "LastName", "Davolio" );
record.Add( "BirthDate", new DateTime( 1968, 12, 8 ) );
record.Add( "IsCitizen", true );
record.Add( "Double", 1.23 );
|record.Add( "Byte", 1 );
record.Add( "StringArray", new string[] { "s1", "s2", "s3" } );
record.Add( "ByteArray", new Byte[] { 1, 2, 3, 4 } );
record.Add( "IntArray", new int[] { 100, 200, 300, 400 } );
record.Add( "DoubleArray", new double[] { 1.2, 2.4, 3.6, 4.8 } );
record.Add( "DateTimeArray", new DateTime[]
{ DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now } );
record.Add( "BoolArray", new bool[] { true, false, true, false } );
_db.AddRecord( record );
搜索和过滤
FileDb
使用 FilterExpressions
和 FilterExpressionGroups
来过滤查询和更新中的记录。我们使用 FilterExpressions
进行简单的查询,这些查询由单个字段比较组成(例如,field = 'value'
),并且我们使用 FilterExpressionGroups
进行复合表达式,其中需要多个表达式和分组。您可以将 FilterExpressions
或 FilterExpressionGroups
添加到 FilterExpressionGroup
中,从而创建复杂的表达式(FileDb
递归处理 FilterExpressionGroups
)。
您可以在代码中手动创建它们,或者使用内置的表达式解析器为您创建它们。表达式解析器识别标准的 SQL 比较运算符,但也引入了 ~=
,它表示 NoCase
比较(仅限 string
)。您可以在下面的示例中看到它。它还识别 LIKE
,它会转换为使用正则表达式(MatchType.RegEx
)。有关更多信息,请参阅下面的正则表达式部分。
在每个示例中,我们将展示如何手动创建 FilterExpression
或 FilterExpressionGroup
,以及/或使用过滤器表达式解析器。
示例 1:创建 FilterExpression
// LastName = "Peacock"
FilterExpression searchExp = new FilterExpression
( "LastName", "Peacock", Equality.Equal, MatchType.Exact );
// using the expression parser
searchExp = FilterExpression.Parse( "LastName = 'Peacock'" );
Table table = _db.SelectRecords( searchExp, new string[]
{ "ID", "LastName" }, false, null );
示例 2:创建 FilterExpressionGroup
我们使用 FilterExpressionGroups
来处理复合表达式。此示例创建了两个相同的 FilterExpressionGroups
,一个使用表达式解析器,另一个使用代码。每次使用 ()
围绕表达式时,都会创建一个新的 FilterExpressionGroup
。内部表达式会先进行评估,就像在 SQL 中一样。
// the easy way, using the expression parser
FilterExpressionGroup srchExpGrp = FilterExpressionGroup.Parse
( "(FirstName ~= 'andrew' OR FirstName ~= 'nancy') AND LastName = 'Fuller'" );
Table table = _db.SelectRecords( srchExpGrp, null, false, null );
// equivalent building it manually
var fname1Exp = new FilterExpression
( "FirstName", "andrew", Equality.Equal, MatchType.NoCase );
var fname2Exp = new FilterExpression
( "FirstName", "nancy", Equality.Equal, MatchType.NoCase );
var lnameExp = new FilterExpression
( "LastName", "Fuller", Equality.Equal, MatchType.Exact );
var fnamesGrp = new FilterExpressionGroup();
fnamesGrp.Add( BoolOp.Or, fname1Exp );
fnamesGrp.Add( BoolOp.Or, fname2Exp );
var allNamesGrp = new FilterExpressionGroup();
allNamesGrp.Add( BoolOp.And, lnameExp );
allNamesGrp.Add( BoolOp.And, fnamesGrp );
// should get the same records
table = _db.SelectRecords( allNamesGrp, null, false, null );
FileDb 支持这些比较运算符
= | 等于 | |
~= | 不区分大小写的相等(仅限字符串) | |
<> | 不等于 | |
!= | 不等于 | |
>= | 大于或等于 | |
<= | 小于或等于 | |
LIKE | 使用正则表达式 |
搜索和过滤中的正则表达式
FileDb
支持使用正则表达式。您可以使用 .NET 支持的任何 RegEx。表达式解析器使用 LIKE
运算符支持 MatchType.RegEx
。在下面的示例中,两个 FilterExpressionGroups
都是相同的。
// Using the Expression Parser
FilterExpressionGroup srchExpGrp = FilterExpressionGroup.Parse
( "(FirstName ~= 'steven' OR [FirstName] LIKE 'NANCY') AND LastName = 'Fuller'" );
Table table = _db.SelectRecords( srchExpGrp, null, false, null );
// we can manually build the same FilterExpressionGroup
var fname1Exp = FilterExpression.Parse( "FirstName ~= steven" );
var fname2Exp = new FilterExpression
( "FirstName", "NANCY", Equality.Equal, MatchType.RegEx );
var lnameExp = new FilterExpression
( "LastName", "Fuller", Equality.Equal, MatchType.Exact );
var fnamesGrp = new FilterExpressionGroup();
fnamesGrp.Add( BoolOp.Or, fname1Exp );
fnamesGrp.Add( BoolOp.Or, fname2Exp );
var allNamesGrp = new FilterExpressionGroup();
allNamesGrp.Add( BoolOp.And, lnameExp );
allNamesGrp.Add( BoolOp.And, fnamesGrp );
table = _db.SelectRecords( allNamesGrp, null, false, null );
排序顺序
查询方法允许按字段对结果进行排序。要进行反向排序,请在排序字段列表前加上 !
。要进行 NoCase
排序,请前缀加上 ~
。要同时进行反向和 NoCase
排序,请同时使用 !
和 ~
。
示例
Table table = _db.SelectAllRecords( new string[]
{ "ID", "Firstname", "LastName", "Age" }, false, new string[]
{ "~LastName", "~FirstName", "!Age" } );
将 LINQ 与 FileDb 结合使用
一旦您将选定的记录放在 Table
对象中,您就可以使用 LINQ 来连接表并获取所需的关系。下面是一个连接 Northwind 的 Customers
、Orders
、OrderDetails
和 Products
表并选择匿名对象的示例。在使用 LINQ 结合 FileDb
时需要记住的一点是,这是一个两步过程:首先使用 FileDb
查询选择所需的记录,然后对生成的 Tables
使用 LINQ 将它们连接成您所需的关系。
此示例使用 FileDb IN
表达式和 FilterExpression.CreateInExpressionFromTable
从其他表中获取相关记录。
FileDb customersDb = new FileDb(),
ordersDb = new FileDb();
customersDb.Open( "Customers.fdb" );
ordersDb.Open( "Orders.fdb" );
// get our target Customer records
// Note that we should select only fields we need from each table, but to keep the code
// simple we just pass null for the field list
FilterExpression filterExp =
FilterExpression.Parse( "CustomerID IN( 'ALFKI', 'BONAP' )" );
FileDbNs.Table customers = customersDb.SelectRecords( filterExp );
// now get only Order records for the target Customer records
// CreateInExpressionFromTable will create an IN FilterExpression, which uses a HashSet
// for high efficiency when filtering records
filterExp = FilterExpression.CreateInExpressionFromTable
( "CustomerID", customers, "CustomerID" );
FileDbNs.Table orders = ordersDb.SelectRecords( filterExp );
// now we're ready to do the join
var query =
from custRec in customers
join orderRec in orders on custRec["CustomerID"] equals orderRec["CustomerID"]
select new
{
ID = custRec["CustomerID"],
CompanyName = custRec["CompanyName"],
OrderID = orderRec["OrderID"],
OrderDate = orderRec["OrderDate"]
};
foreach( var rec in query )
{
Debug.WriteLine( rec.ToString() );
}
通过将 LINQ 与 FileDb
结合使用,您可以获得 LINQ 提供的所有强大功能,包括聚合和分层对象图。
使用您自己的 POCO 对象(Plain Old Class Objects)
FileDb Table
对象本身很好,但与 LINQ 结合使用时,缺点是在构建 LINQ 查询时无法获得 Intellisense。因此 FileDb
为每个“SelectRecords
”方法提供了并行的泛型/模板版本。因此,您可以使用自己的类对象,FileDb
会在查询中填充它们。唯一的要求是类的字段必须与表中的字段名称匹配。这是与上面相同的代码,但使用了自定义 POCO。
// Customer and Order classes are defined elsewhere
FilterExpression filterExp = FilterExpression.Parse
( "CustomerID IN( 'ALFKI', 'BONAP' )" );
IList<Customer> customers = customersDb.SelectRecords<Customer>( filterExp );
filterExp = FilterExpression.CreateInExpressionFromTable<Customer>
( "CustomerID", customers, "CustomerID" );
IList<Order> orders = ordersDb.SelectRecords<Order>( filterExp );
// now we're ready to do the join
var query =
from custRec in customers
join orderRec in orders on custRec.CustomerID equals orderRec.CustomerID
select new
{
ID = custRec.CustomerID,
CompanyName = custRec.CompanyName,
OrderID = orderRec.OrderID,
OrderDate = orderRec.OrderDate,
};
foreach( var rec in query )
{
Debug.WriteLine( rec.ToString() );
}
加密
使用 FileDb
加密很简单。您只需在打开数据库时指定一个 string
键。之后,一切都是自动的。唯一的注意事项是,在添加任何记录之前必须设置一个键。一旦在未设置键的情况下添加了任何一条记录,之后就不能再添加记录了。要么全部加密,要么都不加密。同样,您也不能在加密后添加记录而不加密。
FileDb Explorer
拥有一个出色的数据库工具固然好,但您可能还需要一个可视化和编辑数据的工具。为此,我创建了 FileDb
Explorer,如下所示。您可能已经注意到 SELECT
语句,并想知道,如果 FileDb
是 No-SQL,我们如何使用 SQL 语句来查询数据。答案是,我实现了一个小型的 SQL 子集,仅用于 select
语句(仅在 Explorer 中,不在 DLL 中)。不支持 UPDATE
或 DELETE
。您可以通过直接在 Grid
中编辑数据来执行这些操作。此工具不是免费的,但价格低廉。您可以从 EzTools 网站 下载 FileDb
Explorer。

关注点
我一直对数据库很感兴趣,从 20 世纪 80 年代的 DBase 开始。我一直对 Windows 平台上缺乏简单、小型、高效的数据库来存储本地应用程序数据感到失望。编写这个存储原生 .NET 数据类型和表达式解析器的简单数据库非常有趣。
为了演示如何使用 FileDb
,我还编写了 ConfigDb
,它实现了一个“配置数据库”。它的工作方式类似于 Windows 注册表。这也是一种很好的替代 XML 配置文件的方法,XML 配置文件不易更新。Windows 注册表有时可能会在用户计算机上被锁定。ConfigDb
解决了这些问题。它实现为一个单独的文件,您可以将其包含在您的项目中(包含在此下载中)。以下是如何使用 ConfigDb
来获取和设置值的示例。
// open the file
string configFilename = Path.Combine( Application.StartupPath, "app.configdb" );
ConfigDb configDb = new ConfigDb();
configDb.Open( configFilename );
// open the Key
ConfigDbKey key = configDb.OpenKey( ConfigDbKey.CurrentUser, "Settings", false );
// get the value as a String
string value = configDb.GetValue( key, "CmdTimeout");
// get the same value as an Int
Int iValue = configDb.GetValueAsInt( key, "CmdTimeout");
// set a value
configDb.SetValue( key, "CmdTimeout", ConfigDbDataType.String, 90 );
// set an array value
configDb.SetValue( key, "StringArray", ConfigDbDataType.String,
new String[] { "s1", "s2", "s3" } );
您可以看到使用 ConfigDb
有多么简单。我还创建了一个编辑器/查看器,它非常类似于 Windows RegEdit 程序(也可以免费使用和分发)。您可以从 EzTools 网站下载此工具。
历史
- 更新的提交