Querying
EntglDb exposes local data via the DocumentStore pattern. Collections are queried through your store’s persistence layer (BLite or EF Core), with changes monitored via CDC and propagated to peers automatically.
The DocumentStore Pattern
All data access goes through a class that extends BLiteDocumentStore<T> (or EfCoreDocumentStore<T>). Collections tracked with WatchCollection<T>() are synced; unregistered collections are local-only.
public class AppDocumentStore : BLiteDocumentStore<AppDbContext>
{
public AppDocumentStore(AppDbContext ctx, IEntglDbSyncManager sync)
: base(ctx, sync)
{
WatchCollection<TodoItem>("todos"); // synced
WatchCollection<Note>("notes"); // synced
// Collections NOT called with WatchCollection are local-only
}
}
Basic CRUD
// Inject your store via DI
public class TodoService
{
private readonly AppDocumentStore _store;
public TodoService(AppDocumentStore store) => _store = store;
// Insert / update (upsert by document Id)
public Task SaveAsync(TodoItem item)
=> _store.UpsertAsync("todos", item.Id, item);
// Read single by key
public Task<TodoItem?> GetAsync(string id)
=> _store.GetAsync<TodoItem>("todos", id);
// Read all
public Task<IEnumerable<TodoItem>> GetAllAsync()
=> _store.QueryAsync<TodoItem>("todos");
// Delete
public Task DeleteAsync(string id)
=> _store.DeleteAsync("todos", id);
}
LINQ-Style Querying (BLite Provider)
When using EntglDb.Persistence.BLite, collections are backed by BLite embedded storage and expose a rich query API via the Find method.
var collection = _store.GetCollection<TodoItem>("todos");
// Equality
var item = await collection.Find(t => t.Id == "abc123");
// Comparisons
var overdue = await collection.Find(t => t.DueDate < DateTime.UtcNow);
// Logical operators (AND / OR)
var urgent = await collection.Find(t => t.Priority == "High" && !t.IsCompleted);
// Contains (string)
var search = await collection.Find(t => t.Title.Contains("meeting"));
// Multiple results (returns IEnumerable<T>)
var completed = await collection.FindMany(t => t.IsCompleted);
Supported Operators
| Operator | Description | Example |
|---|---|---|
== |
Equal | t.Status == "active" |
!= |
Not equal | t.Status != "deleted" |
> |
Greater than | t.Score > 100 |
< |
Less than | t.DueDate < DateTime.UtcNow |
>= |
Greater than or equal | t.Age >= 18 |
<= |
Less than or equal | t.Priority <= 3 |
&& |
AND | t.IsActive && t.IsVerified |
\|\| |
OR | t.Role == "Admin" \|\| t.Role == "Owner" |
.Contains() |
String contains | t.Name.Contains("foo") |
.StartsWith() |
String prefix | t.Code.StartsWith("INV-") |
Serialization & JSON Property Names
EntglDb respects [JsonPropertyName] attributes when translating LINQ expressions to storage queries.
public class TodoItem
{
[JsonPropertyName("id")]
public string Id { get; set; } = "";
[JsonPropertyName("is_completed")]
public bool IsCompleted { get; set; }
[JsonPropertyName("due_date")]
public DateTime? DueDate { get; set; }
}
// The expression below correctly queries json_extract(data, '$.is_completed')
var completed = await collection.FindMany(t => t.IsCompleted);
Pagination & Ordering
// Order and paginate
var page = await collection.FindManySorted(
predicate: t => t.IsCompleted == false,
orderBy: t => t.DueDate,
ascending: true,
skip: 0,
take: 20);
EF Core Provider Querying
When using EntglDb.Persistence.EntityFramework, collections are backed by your configured relational database (PostgreSQL, SQL Server, SQLite). Use standard EF Core LINQ queries via the DbContext:
public class AppDocumentStore : EfCoreDocumentStore<AppDbContext>
{
// WatchCollection registers CDC hooks; querying uses DbContext directly
}
// In your service:
await using var ctx = _contextFactory.CreateDbContext();
var todos = await ctx.Set<TodoItem>()
.Where(t => !t.IsCompleted && t.DueDate < DateTime.UtcNow.AddDays(1))
.OrderBy(t => t.DueDate)
.ToListAsync();
Accessing DocumentMetadata
Every document has associated metadata (sync timestamps, vector clock, ContentHash) accessible via:
var meta = await _store.GetMetadataAsync("todos", item.Id);
Console.WriteLine($"Last modified: {meta.UpdatedAt}");
Console.WriteLine($"ContentHash: {meta.ContentHash}"); // SHA-256, v2.1+
Console.WriteLine($"Modified by: {meta.NodeId}");
The ContentHash (added in v2.1) is a SHA-256 hash of the document’s canonical JSON representation, enabling fast integrity verification without full document comparison.
Sync-Aware Queries
To query changes since a specific vector clock position (useful for building change feeds):
var changes = await _store.GetOplogEntriesAfterAsync("todos", sinceSequence: 1000);
foreach (var entry in changes)
{
Console.WriteLine($"{entry.Operation} {entry.DocumentId} @ seq {entry.Sequence}");
}
Snapshot Queries
The snapshot service generates point-in-time snapshots of all watched collections. To force a snapshot and retrieve it:
// Inject ISnapshotService
await _snapshotService.CreateSnapshotAsync();
var snapshot = await _snapshotService.GetLatestSnapshotAsync("todos");
// snapshot.Documents is IReadOnlyList<T>