An HTTP-based lightweight .NET LibSQL ORM client designed for performance and simplicity.
Bunny.LibSQL.Client is a high-performance .NET client for LibSQL that lets you define models, run queries, and use LINQ—without the bloat of heavyweight ORMs. Inspired by EF Core, reimagined for cloud-first applications.
- 🌐 HTTP-based access to LibSQL endpoints
- 🧠 Lightweight ORM
- ⚡ Async operations with
InsertAsync,QueryAsync, and more - 🔗 LINQ query support with
Include()andAutoIncludefor eager loading - 🧱 Auto-migration via
ApplyMigrationsAsync - 🔄 Transaction support with
BeginTransactionAsync,CommitTransactionAsync, andRollbackTransactionAsync - 📦 Plug-and-play class-based DB structure
- 🌀 AI Embedding vector support
Note: This library is currently a Work In Progress (WIP) prototype and not yet intended for production use. While foundational ORM and querying features are available, several important enhancements are still in progress.
We welcome feedback, ideas, and contributions. If you're interested in helping shape the future of this library, feel free to open an issue or pull request!
Install the package via NuGet:
dotnet add package Bunny.LibSql.ClientBelow is a sample application using the LibSql client (Models need to be defined separately). For a full example, you can check the Bunny.LibSql.Client.Demo project (Coming soon).
var dbContextFactory = new LibSqlDbFactory<AppDb>("https://your-libsql-instance.turso.io/", "your_access_key");
var db = dbContextFactory.CreateDbContext();
await db.ApplyMigrationsAsync();
await db.Users.InsertAsync(new User { id = "1", name = "Dejan" });
var users = await db.Users
.Include(u => u.Orders)
.Include<Order>(o => o.Product)
.ToListAsync();
foreach (var user in users)
{
Console.WriteLine($"User: {user.name}");
foreach (var order in user.Orders)
{
Console.WriteLine($" Ordered: {order.Product?.name}");
}
}- 🏗️ Define Your Database
- 📐 Define Your Models
- ⚙️ Initialize & Migrate
- 📥 Manage Records
- 🔍 Query with LINQ
- 🔄 Transactions
- ⚡ Direct SQL Queries
- 🧩 Attributes
- 🧮 Supported Data Types
- 🧪 Sample Program
Start by inheriting from LibSqlDatabase. Use LibSqlTable<T> to define the tables.
public class AppDb(LibSqlClient client) : LibSqlDbContext(client)
{
public AppDb(string dbUrl, string accessKey)
: base(new LibSqlClient(dbUrl, accessKey)) {}
public LibSqlTable<User> Users { get; set; }
public LibSqlTable<Order> Orders { get; set; }
public LibSqlTable<Product> Products { get; set; }
}Your models should use standard C# classes. Use attributes to define relationships. If no Table attribute is provided, the class name will be used as the table name.
[Table("Users")]
public class User
{
[Key]
public int id { get; set; }
[Index]
public string name { get; set; }
[AutoInclude]
public List<Order> Orders { get; set; } = new();
}
[Table("Orders")]
public class Order
{
[Key]
public int id { get; set; }
[ForeignKeyFor(typeof(User))]
public string user_id { get; set; }
[AutoInclude]
[ManyToMany(typeof(ProductOrder))]
public List<Product> Product { get; set; }
}
[Table("ProductOrder")]
public class ProductOrder
{
[Key]
public string id { get; set; }
[ForeignKeyFor(typeof(Order))]
public string order_id { get; set; }
[ForeignKeyFor(typeof(Product))]
public string product_id { get; set; }
}
[Table("Products")]
public class Product
{
[Key]
public string id { get; set; }
public string name { get; set; }
[Unique]
public string product_code { get; set; }
}Initialize your database and automatically sync models with ApplyMigrationsAsync.
var db = new AppDb(dbUrl, accessKey);
await db.ApplyMigrationsAsync();You can easily insert, update, or delete records using InsertAsync, UpdateAsync, and DeleteAsync methods.
Insert records using InsertAsync.
await db.Users.UpdateAsync(new User
{
id = "1",
name = "Alice"
});Insert records using UpdateAsync.
var user = await db.Users.Where(e => e.email == "[email protected]").FirstOrDefaultAsync();
user.email = "[email protected]";
await db.Users.UpdateAsync(user);Delete records using DeleteAsync.
var user = await db.Users.Where(e => e.email == "[email protected]").FirstOrDefaultAsync();
await db.Users.DeleteAsync(user);var users = db.Users
.Where(u => u.name.StartsWith("A"))
.ToListAsync();var usersWithOrders = db.Users
.Include(u => u.Orders)
.Include<Order>(o => o.Product)
.FirstOrDefaultAsync();You can perform aggregate queries such as CountAsync() and SumAsync(...).
var userCount = await db.Users.CountAsync();
var totalPrice = await db.Orders.SumAsync(o => o.price);
⚠️ Important: Always use theAsyncvariants likeToListAsync(),CountAsync(), andSumAsync(...)to execute queries. Skipping the async call will not run the query.
Use transactions to group multiple operations together. If something fails, you can roll back to ensure data consistency.
await db.Client.BeginTransactionAsync();await db.Client.CommitTransactionAsync();await db.Client.RollbackTransactionAsync();await db.Client.BeginTransactionAsync();
try
{
await db.People.InsertAsync(new Person
{
name = "dejan",
lastName = "pelzel",
});
var inserted = await db.People.Where(e => e.name == "dejan").FirstOrDefaultAsync();
Console.WriteLine(inserted.id);
await db.Client.CommitTransactionAsync();
}
catch
{
await db.Client.RollbackTransactionAsync();
throw;
}For raw access, you can use the underlying client directly.
await db.Client.QueryAsync("DELETE FROM Users");var count = await db.Client.QueryScalarAsync<int>("SELECT COUNT(*) FROM Users");The Bunny.LibSQL.Client ORM system uses attributes to define and control table structure, relationships, and query behavior. Here's a summary of the available attributes and their purpose:
| Attribute | Description |
|---|---|
Table |
Specifies a custom table name for the entity. If omitted, class name is used. |
Key |
Marks the property as the primary key of the table. |
Index |
Creates an index on the annotated property for faster lookups. |
ForeignKey |
Defines a relationship to another table by specifying the foreign key property name. |
AutoInclude |
Enables eager loading of the related property automatically during queries. |
Unique |
Marks the field with the UNIQUE constraint, ensuring a unique value in every row. |
Bunny.LibSQL.Client automatically maps common C# types to supported LibSQL column types. These types are used for model properties and are inferred during table creation and querying.
| C# Type | Description | Notes |
|---|---|---|
string |
Textual data | Maps to TEXT |
int |
32-bit integer | Maps to INTEGER |
long |
64-bit integer | Maps to INTEGER |
double |
Double-precision floating point | Maps to REAL |
float |
Single-precision floating point | Maps to REAL |
decimal |
Double-precision floating point | Maps to REAL |
DateTime |
Date and time representation | Stored as INTEGER UNIX timestamp |
bool |
Boolean value | Stored as 0 (false) or 1 (true) |
byte[] |
Binary data (e.g., files, images) | Maps to BLOB |
F32Blob |
Vector F32 blob (e.g. ai embeddings | Maps to F32_BLOB |
⚠️ Note: Nullable variants (e.g.,int?,bool?, etc.) are also supported and will map to nullable columns.