FileDb - A Simple NoSql Database for Silverlight, Windows Phone and .NET
Introduction
I developed FileDb
as a local database solution for .NET apps, but especially for Silverlight and Windows Phone platforms which have no database support at all. This article demonstrates how to use FileDb
as a simple local database solution. FileDb
supports a single table per file with an index which can have an optional primary key. FileDb
is NoSql
, so there is no concept of joining tables as in SQL. This sort of complexity is often not needed for local database needs. By keeping it simple, the size of the FileDb
DLL is kept very small (< 50K) and less complexity means less opportunity for bugs.
FileDb
's searching is powerful. It supports Regular Expressions (RegEx) and compound expressions, e.g. FirstName = 'Bob' and (LastName = 'Smith' or LastName = 'Jones')
. The filter expression parser will parse string
expressions for you or you can create and populate FilterExpression
objects yourself.
FileDb
databases are encryptable (uses AES). Encryption is done at the record level - the database schema is not encrypted.
FileDb
supports field types Int
, UInt
, Bool
, String
, Byte
, Float
, Double
and DateTime
and also arrays of the same types. These are the same types we work with in our .NET programs, so using FileDb
is natural to .NET programmers.
Int
Fields can be AutoIncrementing
, and you can optionally specify one field to be Primary Key, but it must be of type Int
or String
.
FileDb
is fast and it's free from EzTools Software (www.eztools-software.com).
Background
The .NET runtimes for Silverlight and Windows Phone lack support for databases - the System.Data
namespace is not even there. So for local data storage, you must roll your own solution. Furthermore, regular .NET applications which need a simple database for local storage quite often must either use overkill solutions (which often have their own negative issues) or roll your own solution which is less than ideal.
SQL databases are inherently complex and large. Most local databases don't need all that power (and definitely not the footprint). For local database requirements, most applications only require single tables for storing data, such as lists and array values. What has been lacking is a simple NoSql
database for .NET which can fill this requirement.
Using the Code
As you might expect, you interact with FileDb
databases using a FileDb
class object. The main FileDb
classes are: FileDb
, Table
, Field
and Record
. Here's a list of all of the classes:
FileDb
: Represents a database file. All database operations are initiated through this class.Table
: Represents a two dimensionaldataset
returned from a query. ATable
consists ofFields
andRecords
.Field
: Defines the properties of thetable
column, such asName
andDataType
.Fields
: AList
ofField
objects.Record
: A list of data objects represents a single row in aTable
. ImplementsIEnumerable
and theData
property which is used forDataBinding
.Records
: AList
ofRecord
objects.FieldValues
: A simpleName
/Value
pairDictionary
. Use this class when adding and updating records.FilterExpression
: Used to filter records for query, update and delete.FilterExpressionGroup
: Used to create compound expressions by groupingFilterExpressions
andFilterExpressionGroups
.
First, let us be clear that FileDb
is NOT re-entrant for multithreading. Thread access must be synchronized by your application. Failure to synchronise access will result in database corruption.FileDb
databases can only be opened by a single application. Any attempt to open the file when already open will fail. This is as we should expect for a local database meant only for use by a single application.
FileDb
doesn't support the notion of NULL
fields for the non-array type. Only array type fields can have NULL
values. The non-array field values will always have a value, either zero or empty.
OK, now let's see how to use FileDb
.
Creating a Database
You create your database programmatically by defining Fields and adding them to an array, then calling FileDb.Create
, similar to below. Notice we set the ID
field to be AutoIncrementing
and PrimaryKey
. This code creates a database with every type of field.
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() );
Adding Records
You add records to a database by creating a FieldValues
object and adding field values. You do not need to represent every field of the database. Fields that are missing will be initialized to the default value.
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 );
Searching and Filtering
FileDb
uses FilterExpressions
and FilterExpressionGroups
to filter records in queries and updates. We use FilterExpressions
for simple queries which consist of a single field comparison (e.g. field = 'value'
) and we use FilterExpressionGroups
for compound expressions, where multiple expressions and grouping are required. You can add either FilterExpressions
or FilterExpressionGroups
to a FilterExpressionGroup
, thus creating complex expresssions (FileDb
processes FilterExpressionGroups
recursively).
You can either create your own manually in code or use the built-in Expression Parser to create them for you. The Expression Parser recognizes standard SQL comparison operators, but also introduces ~=
, which means NoCase
comparison (for string
s only). You can see it used in the examples below. It also recognizes LIKE
, which translates to use Regular Expression (MatchType.RegEx
). See the section on Regular Expressions below for more information.
In each example, we will show how to build a FilterExpression
or FilterExpressionGroup
manually and/or using the filter expression parser.
Example 1: Create a 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.GetMatchingRecords( searchExp, new string[] { "ID", "LastName"
}, false, null );
Example 2: Create a FilterExpressionGroup
We use FilterExpressionGroups
for compound expressions. This example creates two identical FilterExpressionGroups
, one using the Expression Parser and the other with code. Each time you use ()
around an expression, a new FilterExpressionGroup
will be created. The inner-most expressions are evaluated first, just as in SQL.
// the easy way, using the expression parser
FilterExpressionGroup srchExpGrp =
FilterExpressionGroup.Parse( "(FirstName ~= 'andrew' OR FirstName ~= 'nancy')
AND LastName = 'Fuller'" );
Table table = _db.GetMatchingRecords( 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.GetMatchingRecords( allNamesGrp, null, false, null );
FileDb
supports these comparison operators:
= | Equal | |
~= | Case insensitive Equal (for strings only) | |
<> | Not Equal | |
!= | Not Equal | |
>= | Greater than or Equal | |
<= | Less than or Equal | |
LIKE | Use Regular Expression |
Regular Expressions in Searches and Filtering
FileDb
supports using Regular Expressions. You can use any RegEx supported by .NET. The Expression Parser supports MatchType.RegEx
using the LIKE
operator. In the example below, both FilterExpressionGroups
are identical.
// Using the Expression Parser
FilterExpressionGroup srchExpGrp = FilterExpressionGroup.Parse( "(FirstName
~= 'steven' OR [FirstName] LIKE 'NANCY') AND LastName = 'Fuller'" );
Table table = _db.GetMatchingRecords( 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.GetMatchingRecords( allNamesGrp, null, false, null );
Sort Ordering
Query methods allow for sorting the results by fields. To get a reverse sort, prefix the sort field list with !. To get a NoCase
sort, prefix with ~
. To get both reverse and NoCase
sort, use both !
and ~
.
Example:
Table table = _db.GetAllRecords( new string[] { "ID", "Firstname", "LastName",
"Age" }, false, new string[] { "~LastName", "~FirstName", "!Age" } );
Encryption
Using encryption with FileDb
is simple. You only need to specify a string
key when you open the database. After that, everything is automatic. The only caveat is you must set a key before you add any records. Once a single record has been added without a key set, you cannot later add records with a key. It's all or nothing. Likewise, you cannot add records with encryption and later add records without.
FileDb Explorer
Having a great database tool is good, but you may also need a tool to visualize and edit your data. For this, I have created the FileDb
Explorer, shown just below. You may have noticed the SELECT
statement and wondering why, if FileDb
is NoSql
, how can we use a SQL statement to query the data. The answer is that I've implemented a small subset of SQL just for select
statements (only in the Explorer, not the DLL). There is no support for UPDATE
or DELETE
. You can perform these operations by editing the data directly in the Grid
. This tool is not free, but it is inexpensive. You can download FileDb
Explorer from the EzTools website.

Points of Interest
I've always been interested in databases starting with DBase back in the 1980s. I've been disappointed by the lack of simple, small, efficient databases for storing local application data on the Windows platform. Writing this simple database which stores native .NET data types, and the expression parser was a lot of fun.
To demonstrate how to use FileDb
I have also written ConfigDb
, which implements a "configuration database". It works much like the Windows Registry. This is also a great alternative to using XML config files, which are not easily updatable. Often the Windows Registry can be locked on user's machine. ConfigDb
solves these problems. It's implemented in a single file which you include in your project (included in this download). Here's an example of using ConfigDb
to get
and set
values.
// 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" } );
You can see how easy it is to use ConfigDb
. I have also created an Editor/Viewer which is much like the Windows RegEdit program (also free to use and distribute). You can download this tool from the EzTools website.
History
- New submission