{"id":19140939,"url":"https://github.com/theeightbot/tychodb","last_synced_at":"2026-05-04T18:40:05.764Z","repository":{"id":214694996,"uuid":"369333281","full_name":"TheEightBot/TychoDB","owner":"TheEightBot","description":"The death of traditional SQLite sparks the new life of SQLite as an object store","archived":false,"fork":false,"pushed_at":"2024-12-18T15:53:35.000Z","size":751,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-02-16T13:00:06.276Z","etag":null,"topics":["csharp","database","json","nosql"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TheEightBot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-05-20T20:42:42.000Z","updated_at":"2024-12-18T15:53:07.000Z","dependencies_parsed_at":"2023-12-30T00:42:52.116Z","dependency_job_id":"fd0d4560-3141-4b54-9342-55198045e01c","html_url":"https://github.com/TheEightBot/TychoDB","commit_stats":null,"previous_names":["theeightbot/tychodb","theeightbot/tycho"],"tags_count":51,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheEightBot%2FTychoDB","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheEightBot%2FTychoDB/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheEightBot%2FTychoDB/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheEightBot%2FTychoDB/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TheEightBot","download_url":"https://codeload.github.com/TheEightBot/TychoDB/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240222499,"owners_count":19767458,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","database","json","nosql"],"created_at":"2024-11-09T07:19:29.234Z","updated_at":"2026-05-04T18:40:05.757Z","avatar_url":"https://github.com/TheEightBot.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TychoDB\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/logo.png\" alt=\"TychoDB Logo\" width=\"200\"\u003e\n\u003c/p\u003e\n\nTychoDB is a high-performance .NET library that provides a simple and efficient way to store and retrieve JSON objects using SQLite. It's designed to be a lightweight and fast database solution for .NET applications, with a focus on ease of use and flexibility.\n\n![License](https://img.shields.io/github/license/TheEightBot/TychoDB)\n![NuGet](https://img.shields.io/nuget/v/TychoDB)\n\n## Features\n\n- **Simple API**: Intuitive methods for storing, retrieving, and querying JSON data\n- **Type Registration**: Flexible registration of C# types with custom ID selectors\n- **Advanced Querying**: Rich filtering and sorting capabilities for complex data retrievals\n- **Partitioning**: Organize your data using logical partitions\n- **Binary Data Support**: Store and retrieve binary large objects (BLOBs)\n- **Indexing**: Create indexes on properties for faster query performance\n- **Encryption**: Optional AES-256 full-database encryption via SQLCipher (`TychoDB.Encrypted`)\n- **Multiple Serialization Options**: Support for System.Text.Json, Newtonsoft.Json, and MessagePack\n- **Asynchronous Operations**: Full async/await support for all database operations\n- **LINQ-like Syntax**: Familiar querying patterns for .NET developers\n- **Nested Object Support**: Query and filter on nested object properties\n- **Optimized Performance**: Efficient memory usage and connection management\n\n## Installation\n\nInstall TychoDB via NuGet:\n\n```bash\ndotnet add package TychoDB\n```\n\nDepending on your preferred JSON serializer, you can also install one of the following:\n\n```bash\ndotnet add package TychoDB.JsonSerializer.SystemTextJson\ndotnet add package TychoDB.JsonSerializer.NewtonsoftJson\n```\n\nIf you need **full database encryption** (powered by SQLCipher), use the encrypted variant instead of the standard package:\n\n```bash\ndotnet add package TychoDB.Encrypted\n```\n\n## Encryption\n\nTychoDB supports full database encryption via [SQLCipher](https://www.zetetic.net/sqlcipher/). Encryption is provided through a separate NuGet package that replaces the standard SQLite driver with the SQLCipher-backed variant.\n\n### Installing the Encrypted Package\n\nInstead of the standard `TychoDB` package, install `TychoDB.Encrypted`:\n\n```bash\ndotnet add package TychoDB.Encrypted\n```\n\n\u003e **Note:** `TychoDB` and `TychoDB.Encrypted` are mutually exclusive — only reference one of them in a given project.\n\n### Creating an Encrypted Database\n\nPass a `password` to the `Tycho` constructor. When a password is provided the underlying SQLCipher driver automatically encrypts the entire database file using AES-256.\n\n```csharp\nusing TychoDB;\n\nvar jsonSerializer = new SystemTextJsonSerializer();\n\nusing var db = new Tycho(\n        dbPath: \"./data\",\n        jsonSerializer: jsonSerializer,\n        password: \"your-strong-password\")\n    .Connect();\n```\n\nAll subsequent reads and writes are transparently encrypted/decrypted — the rest of the API is identical to the non-encrypted version.\n\n### Opening an Existing Encrypted Database\n\nSupply the same password that was used when the database was first created:\n\n```csharp\nusing var db = new Tycho(\n        dbPath: \"./data\",\n        jsonSerializer: jsonSerializer,\n        dbName: \"tycho_cache.db\",\n        password: \"your-strong-password\")\n    .Connect();\n```\n\nProviding the wrong password (or no password) will cause a `SqliteException` when the connection is opened.\n\n### Security Considerations\n\n- Store the password securely — use platform secret stores (e.g. Android Keystore, iOS Keychain, Windows DPAPI, or a secrets manager) rather than hard-coding it.\n- The `TychoDB.Encrypted` package pulls in `SQLitePCLRaw.bundle_e_sqlcipher`, which links against the SQLCipher native library. Ensure your target platforms are supported by SQLCipher.\n- Changing the password of an existing database requires re-keying (outside the scope of the TychoDB API); use raw SQLCipher `PRAGMA rekey` if needed.\n\n## Quick Start\n\nHere's a simple example to get you started:\n\n```csharp\nusing TychoDB;\nusing System;\nusing System.Threading.Tasks;\n\n// Define a class to store\npublic class Person\n{\n    public string Id { get; set; }\n    public string Name { get; set; }\n    public int Age { get; set; }\n    public DateTime DateOfBirth { get; set; }\n}\n\npublic class Program\n{\n    public static async Task Main()\n    {\n        // Create a JSON serializer (System.Text.Json implementation)\n        var jsonSerializer = new SystemTextJsonSerializer();\n        \n        // Initialize Tycho and connect to a database\n        using var db = new Tycho(\"./data\", jsonSerializer)\n            .Connect();\n            \n        // Create a person object\n        var person = new Person\n        {\n            Id = \"123\",\n            Name = \"John Doe\",\n            Age = 30,\n            DateOfBirth = new DateTime(1992, 5, 15)\n        };\n            \n        // Write the object to the database\n        await db.WriteObjectAsync(person, x =\u003e x.Id);\n            \n        // Read the object back by its key\n        var retrievedPerson = await db.ReadObjectAsync\u003cPerson\u003e(\"123\");\n            \n        Console.WriteLine($\"Retrieved: {retrievedPerson.Name}, Age: {retrievedPerson.Age}\");\n    }\n}\n```\n\n## Type Registration\n\nTychoDB provides several ways to register your types, which helps with ID selection and comparison:\n\n```csharp\n// Register a type with a specific ID property\ndb.AddTypeRegistration\u003cPerson, string\u003e(x =\u003e x.Id);\n\n// Register using a custom key selector function\ndb.AddTypeRegistrationWithCustomKeySelector\u003cPerson\u003e(x =\u003e $\"{x.Id}_{x.Name}\");\n\n// Register using convention-based ID property detection\ndb.AddTypeRegistration\u003cPerson\u003e();\n```\n\nAfter registration, you can use simplified write/read operations:\n\n```csharp\n// Write without specifying a key selector\nawait db.WriteObjectAsync(person);\n\n// Tycho knows how to extract the ID\nawait db.ReadObjectAsync\u003cPerson\u003e(person);\n```\n\n## Querying Objects\n\nTychoDB offers rich querying capabilities:\n\n### Basic Querying\n\n```csharp\n// Read all objects of a type\nvar allPeople = await db.ReadObjectsAsync\u003cPerson\u003e();\n\n// Read by ID\nvar person = await db.ReadObjectAsync\u003cPerson\u003e(\"123\");\n\n// Check if an object exists\nvar exists = await db.ObjectExistsAsync\u003cPerson\u003e(\"123\");\n\n// Count objects\nvar count = await db.CountObjectsAsync\u003cPerson\u003e();\n```\n\n### Filtering\n\n```csharp\n// Create a filter for people older than 25\nvar filter = FilterBuilder\u003cPerson\u003e\n    .Create()\n    .Filter(FilterType.GreaterThan, x =\u003e x.Age, 25);\n\n// Apply the filter\nvar olderPeople = await db.ReadObjectsAsync\u003cPerson\u003e(filter: filter);\n\n// Chain multiple filters\nvar complexFilter = FilterBuilder\u003cPerson\u003e\n    .Create()\n    .Filter(FilterType.GreaterThan, x =\u003e x.Age, 25)\n    .And()\n    .Filter(FilterType.Contains, x =\u003e x.Name, \"Doe\");\n\n// Get a single object matching the filter\nvar johnDoe = await db.ReadObjectAsync\u003cPerson\u003e(filter: complexFilter);\n\n// Get the first object matching the filter\nvar firstPerson = await db.ReadFirstObjectAsync\u003cPerson\u003e(filter: complexFilter);\n```\n\n### Sorting\n\n```csharp\n// Create a sort builder\nvar sort = SortBuilder\u003cPerson\u003e\n    .Create()\n    .OrderBy(SortDirection.Ascending, x =\u003e x.Age)\n    .OrderBy(SortDirection.Descending, x =\u003e x.Name);\n\n// Apply sorting with optional filtering\nvar sortedPeople = await db.ReadObjectsAsync\u003cPerson\u003e(\n    filter: complexFilter,\n    sort: sort\n);\n\n// Limit the number of results\nvar topFivePeople = await db.ReadObjectsAsync\u003cPerson\u003e(\n    filter: complexFilter,\n    sort: sort,\n    top: 5\n);\n```\n\n### Nested Object Queries\n\n```csharp\npublic class Address \n{\n    public string Street { get; set; }\n    public string City { get; set; }\n    public string Country { get; set; }\n}\n\npublic class Customer\n{\n    public string Id { get; set; }\n    public string Name { get; set; }\n    public Address HomeAddress { get; set; }\n}\n\n// Query nested properties\nvar usCustomers = await db.ReadObjectsAsync\u003cCustomer\u003e(\n    filter: FilterBuilder\u003cCustomer\u003e\n        .Create()\n        .Filter(FilterType.Equals, x =\u003e x.HomeAddress.Country, \"USA\")\n);\n\n// Extract nested objects\nvar addresses = await db.ReadObjectsAsync\u003cCustomer, Address\u003e(\n    x =\u003e x.HomeAddress\n);\n\n// Extract nested objects with keys\nvar addressesWithCustomerIds = await db.ReadObjectsWithKeysAsync\u003cCustomer, Address\u003e(\n    x =\u003e x.HomeAddress\n);\n\n// Extract specific property from nested objects\nvar countries = await db.ReadObjectsAsync\u003cCustomer, string\u003e(\n    x =\u003e x.HomeAddress.Country\n);\n```\n\n## Partitioning\n\nPartitions allow you to organize your data logically:\n\n```csharp\n// Write objects to different partitions\nawait db.WriteObjectAsync(activePerson, x =\u003e x.Id, \"active_users\");\nawait db.WriteObjectAsync(inactivePerson, x =\u003e x.Id, \"inactive_users\");\n\n// Read from a specific partition\nvar activeUsers = await db.ReadObjectsAsync\u003cPerson\u003e(partition: \"active_users\");\n\n// Count objects in a partition\nvar inactiveCount = await db.CountObjectsAsync\u003cPerson\u003e(partition: \"inactive_users\");\n\n// Delete all objects in a partition\nvar deletedCount = await db.DeleteObjectsAsync(\"inactive_users\");\n```\n\n## BLOB Storage\n\nFor binary data:\n\n```csharp\n// Store a binary file\nusing var fileStream = File.OpenRead(\"document.pdf\");\nawait db.WriteBlobAsync(fileStream, \"doc_123\", \"documents\");\n\n// Check if a blob exists\nvar exists = await db.BlobExistsAsync(\"doc_123\", \"documents\");\n\n// Read a blob\nusing var blobStream = await db.ReadBlobAsync(\"doc_123\", \"documents\");\n// Use the stream...\n\n// Delete a blob\nawait db.DeleteBlobAsync(\"doc_123\", \"documents\");\n\n// Delete all blobs in a partition\nvar result = await db.DeleteBlobsAsync(\"documents\");\nConsole.WriteLine($\"Deleted {result.Count} blobs\");\n```\n\n## Indexing\n\nCreate indexes to improve query performance:\n\n```csharp\n// Create a simple index on a property\ndb.CreateIndex\u003cPerson\u003e(x =\u003e x.Age, \"age_index\");\n\n// Create an index asynchronously\nawait db.CreateIndexAsync\u003cPerson\u003e(x =\u003e x.Name, \"name_index\");\n\n// Create a composite index on multiple properties\ndb.CreateIndex\u003cPerson\u003e(\n    new Expression\u003cFunc\u003cPerson, object\u003e\u003e[] \n    {\n        x =\u003e x.Age,\n        x =\u003e x.Name\n    }, \n    \"age_name_index\"\n);\n\n// Create an index on a nested property\ndb.CreateIndex\u003cCustomer\u003e(x =\u003e x.HomeAddress.Country, \"country_index\");\n```\n\n## Connection Management\n\nTychoDB offers options for connection management:\n\n```csharp\n// Create a database with persistent connection (default)\nvar db = new Tycho(\n    dbPath: \"./data\",\n    jsonSerializer: serializer,\n    persistConnection: true\n);\n\n// Connect explicitly\ndb.Connect();\n\n// Or connect asynchronously\nawait db.ConnectAsync();\n\n// Disconnect when needed\ndb.Disconnect();\n\n// Or disconnect asynchronously\nawait db.DisconnectAsync();\n```\n\n## Advanced Features\n\n### Batch Operations\n\n```csharp\n// Write multiple objects at once\nvar people = GetManyPeople(); // Returns List\u003cPerson\u003e\nawait db.WriteObjectsAsync(people, x =\u003e x.Id);\n\n// Delete multiple objects with a filter\nvar deletedCount = await db.DeleteObjectsAsync\u003cPerson\u003e(\n    filter: FilterBuilder\u003cPerson\u003e\n        .Create()\n        .Filter(FilterType.LessThan, x =\u003e x.Age, 18)\n);\n\n// Delete all objects\nawait db.DeleteObjectsAsync();\n```\n\n### Database Maintenance\n\n```csharp\n// Optimize database performance and reduce size\ndb.Cleanup(shrinkMemory: true, vacuum: true);\n```\n\n## LINQ Support\n\nTychoDB offers comprehensive LINQ support for more natural and familiar querying in C#. The LINQ interface lets you write type-safe queries with IntelliSense support and compile-time checking.\n\n### Basic Querying with LINQ\n\n```csharp\n// Start a LINQ query for a specific type\nvar query = db.Query\u003cPerson\u003e();\n\n// Apply filters\nvar activeUsers = await db.Query\u003cPerson\u003e()\n    .Where(p =\u003e p.IsActive)\n    .ToListAsync();\n\n// Use multiple conditions\nvar seniorActiveUsers = await db.Query\u003cPerson\u003e()\n    .Where(p =\u003e p.IsActive \u0026\u0026 p.Age \u003e 65)\n    .ToListAsync();\n\n// String operations\nvar gmailUsers = await db.Query\u003cPerson\u003e()\n    .Where(p =\u003e p.Email.EndsWith(\"@gmail.com\"))\n    .ToListAsync();\n```\n\n### Sorting and Paging\n\n```csharp\n// Order results\nvar orderedByAge = await db.Query\u003cPerson\u003e()\n    .OrderBy(p =\u003e p.Age)\n    .ToListAsync();\n\n// Order descending\nvar newestFirst = await db.Query\u003cPerson\u003e()\n    .OrderByDescending(p =\u003e p.RegistrationDate)\n    .ToListAsync();\n\n// Multiple ordering criteria\nvar sortedPeople = await db.Query\u003cPerson\u003e()\n    .OrderBy(p =\u003e p.LastName)\n    .ThenBy(p =\u003e p.FirstName)\n    .ToListAsync();\n\n// Limit results (pagination)\nvar topFive = await db.Query\u003cPerson\u003e()\n    .OrderByDescending(p =\u003e p.Points)\n    .Take(5)\n    .ToListAsync();\n```\n\n### Single Result Operations\n\n```csharp\n// Get first matching result or default\nvar person = await db.Query\u003cPerson\u003e()\n    .Where(p =\u003e p.Id == \"abc123\")\n    .FirstOrDefaultAsync();\n\n// Get single matching result or default (throws if multiple matches)\nvar uniquePerson = await db.Query\u003cPerson\u003e()\n    .Where(p =\u003e p.Email == \"unique@example.com\")\n    .SingleOrDefaultAsync();\n```\n\n### Aggregation Operations\n\n```csharp\n// Count results\nint activeCount = await db.Query\u003cPerson\u003e()\n    .Where(p =\u003e p.IsActive)\n    .CountAsync();\n\n// Check existence\nbool hasInactivePeople = await db.Query\u003cPerson\u003e()\n    .Where(p =\u003e !p.IsActive)\n    .AnyAsync();\n```\n\n### Working with Partitions\n\n```csharp\n// Query within a specific partition\nvar europeUsers = await db.Query\u003cPerson\u003e(\"europe\")\n    .Where(p =\u003e p.Age \u003e 18)\n    .ToListAsync();\n```\n\n### Complex Queries\n\n```csharp\n// Complex multi-condition queries\nvar result = await db.Query\u003cPerson\u003e()\n    .Where(p =\u003e p.IsActive \u0026\u0026 p.Age \u003e 25)\n    .Where(p =\u003e p.Email.EndsWith(\"@gmail.com\") || p.Points \u003e= 150)\n    .OrderByDescending(p =\u003e p.Points)\n    .Take(10)\n    .ToListAsync();\n```\n\n### Saving Collections with LINQ Extensions\n\n```csharp\n// Save a collection of objects\nvar people = new List\u003cPerson\u003e\n{\n    new Person { Id = \"1\", Name = \"John Doe\", Age = 30 },\n    new Person { Id = \"2\", Name = \"Jane Smith\", Age = 25 },\n    new Person { Id = \"3\", Name = \"Bob Johnson\", Age = 40 }\n};\n\n// Save all objects with a single call\nawait db.SaveAllAsync(people);\n\n// Save to a specific partition\nawait db.SaveAllAsync(people, \"active_users\");\n```\n\n## Performance Considerations\n\n- Use batch operations when dealing with multiple objects\n- Create indexes for frequently queried properties\n- Use the appropriate serializer for your needs (MessagePack for best performance)\n- Consider partitioning for large datasets\n- Use connection pooling for multi-threaded applications\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Acknowledgments\n\n- SQLite for providing the underlying database engine\n- The .NET community for support and inspiration\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheeightbot%2Ftychodb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftheeightbot%2Ftychodb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheeightbot%2Ftychodb/lists"}