SoloDB
A document database built on top of SQLite with full LINQ/IQueryable support.
Install / Use
/learn @Unconcurrent/SoloDBREADME
SoloDB - A Document Database With Full LINQ Support
Version 1.0 - Stable Release and Version 1.1 - Relational Support
SoloDB is a high-performance, lightweight, and robust embedded .NET database that elegantly combines the power of a NoSQL document store with the reliability of SQL. Built directly on top of SQLite and its native JSONB support, SoloDB offers a serverless, feature-rich experience, combining a simple MongoDB-like API with full LINQ support for expressive, strongly-typed queries.
SoloDB includes native relational-document support via DBRef<T>, DBRefMany<T>, and [SoloRef(...)], so you can model references and graph-like data while staying in a strongly-typed document API.
It is designed for developers who need a fast, reliable, and easy-to-use database solution without the overhead of a separate server. It's perfect for desktop applications, mobile apps (via .NET MAUI), and web applications.
I wrote a detailed comparison with a popular alternative, LiteDB — including benchmarks, API differences, and developer experience. Read the article here.
Table of Contents
- Core Features
- Why SoloDB?
- Installation
- Getting Started: A 60-Second Guide
- Usage and Examples
- Database Management
- License
- FAQ
Core Features
SoloDB is packed with features that provide a seamless and powerful developer experience.
- SQLite Core: Leverages the world's most deployed database engine, ensuring rock-solid stability, performance, and reliability.
- Serverless Architecture: As a .NET library, it runs in-process with your application. No separate server or configuration is required.
- Hybrid NoSQL & SQL: Store and query schemaless JSON documents with a familiar object-oriented API, or drop down to raw SQL for complex queries.
- Full LINQ Support: Use the full power of LINQ and IQueryable<T> to build expressive, strongly-typed queries against your data.
- ACID Transactions: Guarantees atomicity, consistency, isolation, and durability for all operations, thanks to SQLite's transactional nature.
- Expressive Indexing: Create unique or non-unique indexes on document properties for lightning-fast queries, using simple attributes.
- Integrated File Storage: A robust, hierarchical file system API (similar to System.IO) for storing and managing large files and binary data directly within the database.
- Polymorphic Collections: Store objects of different derived types within a single collection and query them by their base or concrete type.
- Events API: Subscribe to collection lifecycle events (
OnInserting,OnInserted,OnUpdating,OnUpdated,OnDeleting,OnDeleted) with full transactional guarantees via SQLite triggers. - Thread-Safe: A built-in connection pool ensures safe, concurrent access from multiple threads.
- Customizable ID Generation: Use the default long primary key, or implement your own custom ID generation strategy (e.g., string, Guid).
- .NET Standard 2.0 & 2.1: Broad compatibility with .NET Framework, .NET Core, and modern .NET runtimes.
- Open Source: Licensed under the permissive LGPL-3.0.
- Documentation: See the official documentation for detailed guides.
Why SoloDB?
In a world of countless database solutions, SoloDB was created to fill a specific niche: to provide a simple, modern, and powerful alternative to document databases like MongoDB, but with the unmatched reliability and zero-configuration nature of SQLite. It's for developers who love the flexibility of NoSQL but don't want to sacrifice the transactional integrity and robustness of a traditional SQL database.
Installation
Install SoloDB directly from the NuGet Package Manager.
dotnet add package SoloDB
Getting Started: A 60-Second Guide
Here is a complete example to get you up and running instantly.
using SoloDatabase;
using SoloDatabase.Attributes;
// 1. Initialize the database (on-disk or in-memory)
using var db = new SoloDB("my_app_data.db");
// 2. Get a strongly-typed collection
var users = db.GetCollection<User>();
// 3. Insert some data
var user = new User
{
Name = "John Doe",
Email = "john.doe@example.com",
CreatedAt = DateTime.UtcNow
};
users.Insert(user);
Console.WriteLine($"Inserted user with auto-generated ID: {user.Id}");
// 4. Query your data with LINQ
var foundUser = users.FirstOrDefault(u => u.Email == "john.doe@example.com");
if (foundUser != null)
{
Console.WriteLine($"Found user: {foundUser.Name}");
// 5. Update a document
foundUser.Name = "Johnathan Doe";
users.Update(foundUser);
Console.WriteLine("User has been updated.");
}
// 6. Delete a document
users.Delete(user.Id);
Console.WriteLine($"User deleted. Final count: {users.Count()}");
// Define your data model
public class User
{
// A 'long Id' property is automatically used as the primary key.
public long Id { get; set; }
[Indexed] // Create an index on the 'Email' property for fast lookups.
public string Email { get; set; }
public string Name { get; set; }
public DateTime CreatedAt { get; set; }
}
Usage and Examples
Initializing the Database
You can create a database on disk for persistence or in-memory for temporary data and testing.
using SoloDatabase;
// Create or open a database file on disk
using var onDiskDB = new SoloDB("path/to/database.db");
// Create a named, shareable in-memory database
using var sharedMemoryDB = new SoloDB("memory:my-shared-db");
Working with Collections
A collection is a container for your documents, analogous to a table in SQL.
// Get a strongly-typed collection. This is the recommended approach.
var products = db.GetCollection<Product>();
// Get an untyped collection for dynamic scenarios.
var untypedProducts = db.GetUntypedCollection("Product");
public class Product { /* ... */ }
Relations (DBRef, DBRefMany, SoloRef)
Overview
SoloDB relations are explicit, typed links between collections:
DBRef<TTarget>: single relation to one target row.DBRefMany<TTarget>: many relation via link rows.[SoloRef(...)]: relation policy annotation (OnDelete,OnOwnerDelete,Unique).
Relations are persisted through dedicated link tables (SoloDBRelLink_*) and relation metadata (SoloDBRelation), not embedded owner-document arrays of foreign keys.
DBRef and DBRefMany Basics
using SoloDatabase;
using SoloDatabase.Attributes;
using System.Collections.Generic;
public class Team
{
public long Id { get; set; }
public string Name { get; set; } = "";
// Single relation
public DBRef<Member> Lead { get; set; } = DBRef<Member>.None;
// Many relation
public DBRefMany<Member> Members { get; set; } = new();
}
public class Member
{
public long Id { get; set; }
public string Name { get; set; } = "";
}
using var db = new SoloDB("memory:relations-basics");
var teams = db.GetCollection<Team>();
var members = db.GetCollection<Member>();
// DBRef.To(existingId): link to an already persisted target.
var existingLeadId = members.Insert(new Member { Name = "Existing Alice" });
teams.Insert(new Team
{
Name = "Ops",
Lead = DBRef<Member>.To(existingLeadId)
});
// DBRef.From(entity): insert target first, then link owner to inserted target.
var team = new Team
{
Name = "Core",
Lead = DBRef<Member>.From(new Member { Name = "Alice" })
};
// DBRefMany supports Add/Clear/Remove mutations tracked on Update.
team.Members.Add(new Member { Name = "Bob" });
team.Members.Add(new Member { Name = "Carol" });
teams.Insert(team);
// Default policy behavior in this basics example:
// - No [SoloRef(...)] overrides are applied here.
// - One owner with one referenced Member: deleting the owner deletes that orphan Member (default OnOwnerDelete=Deletion).
// - Two owners sharing the same Member: deleting one owner keeps the shared Member; deleting the last owner deletes it.
SoloRef Policy Matrix
DeletePolicy values in SoloDB are: Restrict, Cascade, Unlink, Deletion.
| Policy Surface | Trigger Operation | Persistence Result | Reject Behavior |
|---|---|---|---|
| OnDelete = Restrict | Delete target while owners still reference it | Target delete blocked; links and owners unchanged | Typed reject (InvalidOperationException) |
| OnDelete = Cascade | Delete target | Referencing owners are deleted (and their links removed) | N/A when valid |
| OnDelete = Unlink | Delete target | Owner rows survive; relation links removed / refs become empty | N/A when valid |
| OnOwnerDelete = Restrict | Delete owner that still has links | Owner delete blocked | Typed reject (InvalidOperationException) |
| OnOwnerDelete = Unlink | Delete owner | Owner removed; links removed; targets survive | N/A when valid |
| OnOwnerDelete = Deletion | Delete owne
