RizeDb 2.0.0-beta5

This is a prerelease version of RizeDb.
dotnet add package RizeDb --version 2.0.0-beta5
NuGet\Install-Package RizeDb -Version 2.0.0-beta5
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RizeDb" Version="2.0.0-beta5" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add RizeDb --version 2.0.0-beta5
#r "nuget: RizeDb, 2.0.0-beta5"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install RizeDb as a Cake Addin
#addin nuget:?package=RizeDb&version=2.0.0-beta5&prerelease

// Install RizeDb as a Cake Tool
#tool nuget:?package=RizeDb&version=2.0.0-beta5&prerelease

Document-Oriented

RizeDb has a document-oriented implementation that allows for the storing, indexing and retrieving of documents. Documents are not stored into tables and have very little structure to them.

RizeDb's document store requires no schema and document values can change types without requiring the database to update all existing data. By default, nearly all document data is indexed so that finding data is fast and easy.

Table of Contents

RizeDb Primer

The first step to creating a document store is simply to instantiate the DocumentStore object with a stream.

using(var stream = new MemoryStream())
{
   using(var documentStore = new RizeDb.DocumentStore(stream))
   {
      //Code goes here
   }
}

Once your document store is created, create POCO classes.

public class Order
{
    public long Id { get; set; } //This is the only required field
    public string Number { get; set; }
    public DateTime Date { get; set; }
    public List<OrderItem> OrderItems = { get; set; }
}

public class OrderItems
{
    public string ItemName { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

Now that your POCO classes are created, instantiate and fill an Order object with data.

var order = new Order()
{
    Numer = "1001",
    Date = DateTime.Now,
    OrderItems = new List<OrderItem>();
}

order.OrderItems.Add(new OrderItem()
{
    ItemName = "Sun Glasses",
    Price = 19.99,
    Quantity = 1
});

order.OrderItems.Add(new OrderItem()
{
    ItemName = "Flashlight",
    Price = 10.50,
    Quantity = 4
});

Once your order is ready to be stored simply add it to a document collection.

documentStore.Store("Orders", order);

This will store the order object and its' items as one document into a collection named "Orders". If the collection "Orders" does not already exist it will be created. Now that the order document has been stored into a collection it will have a unique value assigned to the Id property of order. This The Id value is what will be used to retrieve, update or delete the order in the future.

Retrieving your document is as simple as requesting it by Id.

var order = documentStore.Retrieve<order>("Orders", 1 /*Assuming the document Id is 1*/);

The retrieve method will create an Order object and its' OrderItem objects and populate them with the exact same data that was stored.

You can also lookup documents by values.

var orders = documentStore.Retrieve<order>("Orders", o => o.Number == "1001");

This call to the Retrieve method will create an IEnumerable object containing all Orders objects with the Number "1001".

You can also search by child object values.

var orders = documentStore.Retrieve<order>("Orders", o => o.OrderItems.Any(i => i.ItemName == "Flashlight"/);

Again you will get an IEnumerable object containing all orders that have an order item with the ItemName of "Flashlight".

You can even use a different object to retrieve data.

public class OrderHeader
{
    public long Id { get; set; }
    public string Number { get; set; }
}

var orderHeader = documentStore.Retrieve<OrderHeader>("Orders", 1 /*Assuming the document Id is 1*/);

This call will create and return an OrderHeader object with the Number property set to "1001".

And finally you can change the type of the property and the values will still get set as long as they are compatible.

public class OrderHeader2
{
    public long Id { get; set; }
    public int Number { get; set; } //<-- This was changed to an Int32
}

var orderHeader2 = documentStore.Retrieve<OrderHeader2>("Orders", 1 /*Assuming the document Id is 1*/);

Now the newly create OrderHeader2 will have an integer value for the Number property set to 1001. Changing types will work for most types, but some are not compatible and will either be null or default when the document is retrieved.

Operations

Storing

One of the many downsides of Relational Databases is the work it takes to create and alter tables. And while Relation Databases provide many benefits, often developers don't need those benefits and so the extra work becomes a barrier to the rest of the development process. The Document Store in RizeDb was designed to make storing, retrieving, updating and deleting of records as easy as possible. In fact, there is only one requirement and that is that every document has a property named Id that is along. The Id field is the primary key and is required to be present when adding, updating or removing a document.

As seen in the [Getting Started] section adding a document is as easy as:

class Customer
{
    public long Id { get; set; }
    public string Name { get; set; }
}

var customer = new Customer() { Name = "John Smith" }

using(var stream = new MemoryStream())
{
   using(var documentStore = new RizeDb.DocumentStore(stream))
   {
      documentStore.Store("Customers", customer);
   }
}

In the above code example, the customer was stored in the document store and a unique int64 value was assigned to the Id property. It is important to note that if the Id property is not 0 when storing a document that RizeDb will first try and find an existing document with that Id and replace it with the new document. If a document did not already exist, the new document will be added without generating a new Id value.

Retrieving

The exception to the rule is Byte arrays. So searching and retrieving documents is very easy and flexible.

The simplest way to retrieve a document is with the Id.

class Customer
{
    public long Id { get; set; }
    public string Name { get; set; }
}

using(var stream = new MemoryStream())
{
   using(var documentStore = new RizeDb.DocumentStore(stream))
   {
      var customer = documentStore.Retrieve<Customer>("Customers", 1);
   }
}

The code above will look for the document with the Id of one and if found will create a customer object and populate any matching field names with the stored data. Using the Id will return just one document if a document is found.

A lambda predicate can also be used to retrieve documents.

class Customer
{
    public long Id { get; set; }
    public string Name { get; set; }
}

using(var stream = new MemoryStream())
{
   using(var documentStore = new RizeDb.DocumentStore(stream))
   {
      var customers = documentStore.Retrieve<Customer>("Customers", c => c.Name == "John Smith");
   }
}

The code above will look for any Document in the Customers collection that has a Name field with the value "John Smith". Since the system cannot be sure that only one document matches the predicate an IEnumerable of Customer is returned. With all matching documents. There are some scenarios where properties cannot be searched on, or where they may not find matching documents even though they exist. These scenarios are always a result of index changes. Please see Index Optimizing for more details.

Updating

Updates are essentially overwriting the existing document. If you create a new document and assign it an Id that already exists in the collection. The previous document will be removed and the new document will be added. This means that all existing data will be lost unless it is included in the new document.

In the following example, the current customer will be loaded and the name changed.

class Customer
{
    public long Id { get; set; }
    public string Name { get; set; }
}

using(var stream = new MemoryStream())
{
   using(var documentStore = new RizeDb.DocumentStore(stream))
   {
      var customer = documentStore.Retrieve<Customer>("Customers", 1);
      customer.Name = "John Doe";
      documentStore.Store("Customers", customer);
   }
}

Removing

Removing a document is very straight forward. All you need is the Id of the document.

using(var stream = new MemoryStream())
{
   using(var documentStore = new RizeDb.DocumentStore(stream))
   {
      var customer = documentStore.Remove("Customers", 1);
   }
}

The document with the Id of 1 will no longer exist in the customers collection.

Transaction Group Operations

The Transaction Group ensures that all operations in that group are written to the document store or none of them are. An example of this would be if you create a new customer and that customer has a new order. You would not want the customer being stored in a collection without the order or the Order without the Customer.

When using a transaction group if there is some sort of failure and the order or customer is not fully committed to the document store. Both operations will be rolled back as if they never happened.

The following example shows how easy it is to use a Transaction Group.

var transactionGroup = documentStore.CreateTransactionGroup()
    .Store("Customer", customer)
    .Store("Order", order)
    .Commit();

Additionally, you can have an action that gets called after each operation. This allows you to provide information such as the Id of parent documents to children documents.

var transactionGroup = documentStore.CreateTransactionGroup()
    .Store("Customer", customer, c => order.CustomerId = c.Id)
    .Store("Order", order)
    .Commit();

It is important to remember to call the Commit method without it your documents will not be stored.

Indexing

When a collection is created an index is added to the Id field. Additional indexes can be created on properties so that look ups on those properties can be much faster.

class Log
{
    public long Id { get; set; } 

    [Index(IndexSearchOptions.Dates)]
    public DateTime Date { get; set; }

    [Index(IndexSearchOptions.String)]
    public string Source { get; set; }

    public string Message { get; set; }
}

In the above example, all properties will be indexed except for the Message property. Since string properties are slow to index especially when they are large, not adding indexing to the Message property is a good idea.

Since this is a document database, property types can be changed. If you change the property type on an indexed property, you may want to tell the database that it needs to search the old index as well as the new one. In the example below the Source field was changed to an Int and the additional index attribute was added to let the engine know that it needs to search the string index as well.

class Log
{
    public long Id { get; set; } 

    [Index(IndexSearchOptions.Dates)]
    public DateTime Date { get; set; }

    [Index(IndexSearchOptions.String)]
    [Index(IndexSearchOptions.Int)]
    public int Source { get; set; }

    public string Message { get; set; }
}
var customers = documentStore.Retrieve<Log>("Logs", c => c.Source = "23");

Both the String index, and Int index will be searched when retrieving documents using that field.

As a side note, when searching through multiple indexes for values threading is used. This helps keep searching fast but optimizing your indexes can still have significant benefits to the performance of the system.

Dropping Collections

There may come a time where you no longer need the data in a collection. You can drop that collection and all of its' indexes. When a collection is dropped its' file space is flagged for reuse. This allows other collections and or new collections to use that file space in the future.

To drop a collection follow the example below.

database.DropCollection("Logs");

Encryption

RizeDb supports AES encryption. When a database is encrypted all data including the logs are encrypted so that no part of your database is exposed. However, of the password that unlocks the database is ever lost, you will not be able to recover your data. To use encryption the database must be created with it, and once created the password cannot be changed or removed.

Future versions of RizeDb will allow for changing and removing of passwords but for now, it is a one-way trip.

An example of creating an encrypted data store can be seen below.

using (var database = new RizeDb.DocumentStore(stream, "Password")) 
{
//Do operations here 
}

Use the same code when opening the database in the future.

Settings

Every application needs some sort of location to store simple data. Perhaps login credentials to a third-party app. Maybe a version number of your app or a timestamp of the last time data was synchronized. Whatever it is, it does not make sense to create a custom file or a full collection in the database to store that data. The Document Store in RizeDb has a feature called Settings. The feature allows you to store whatever objects you want with a key-value pair interface. And if you are using encryption the data stored in settings will be encrypted as well.

Using Settings is simple.

using (var database = new RizeDb.DocumentStore(stream)) {     
    var settings = new SettingsClass() { Value = "Hey Yall" };     
    database.SetSetting("MySetting", settings); 
}

To retrieve a setting is simple.

using (var database = new RizeDb.DocumentStore(stream)) 
{ 
    var restoredSetting = database.GetSetting<SettingsClass>("MySetting"); 
}

vizeotech@outlook.com

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on RizeDb:

Package Downloads
EnvironmentVault2.Shared

Supporting assembly for Environment Vault.

EnvironmentVault.Shared

Companion assemblies for Environment Vault

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.0-beta5 91 8/11/2023
2.0.0-beta4 97 1/23/2023
2.0.0-beta3 155 9/18/2022
2.0.0-beta2 163 9/18/2022
2.0.0-beta 129 9/6/2022
1.6.5 459 7/20/2021
1.6.0 317 5/16/2021
1.5.0 442 4/11/2021
1.4.0 446 4/8/2021
1.3.5 296 3/21/2021
1.3.0 597 9/16/2020
1.2.0 393 9/10/2020
1.1.0 467 8/19/2020
1.0.2 434 7/9/2020
1.0.1 538 7/26/2019
1.0.0 609 7/24/2019