-
Notifications
You must be signed in to change notification settings - Fork 1
08 CRUD.md
// Add a single entity
db.Documents.Add(new Document
{
Id = "doc-001",
Title = "Getting Started Guide",
Embedding = new float[384]
});
// Batch add (atomic semantics: if any validation fails, all are rolled back)
var batch = new List<Document>
{
new() { Id = "doc-002", Title = "Advanced Tutorial", Embedding = new float[384] },
new() { Id = "doc-003", Title = "Best Practices", Embedding = new float[384] },
};
db.Documents.AddRange(batch);
// Async batch add (offloads CPU-intensive computation to thread pool)
await db.Documents.AddRangeAsync(batch, cancellationToken);flowchart TD
START["AddRange(entities)"] --> LOCK["Acquire write lock"]
LOCK --> P1["Phase 1: Pre-validation<br/>(no state modified, exception-safe)"]
P1 --> V1["Validate each entity's key is not null"]
V1 --> V2["Validate key does not duplicate existing data"]
V2 --> V3["Validate keys are unique within the batch (HashSet)"]
V3 --> V4["PrepareVectors() validate vector dimensions"]
V4 --> CHK{"All passed?"}
CHK -- "Any failure" --> ROLLBACK["Throw exception<br/>No data modified ✅"]
CHK -- "All passed" --> P2["Phase 2: Atomic commit<br/>(no more exceptions after this point)"]
P2 --> W1["Assign internal ID (_nextId++)"]
W1 --> W2["Write to _entities + _keyToId"]
W2 --> W3["Write to all _indices"]
W3 --> UNLOCK["Release write lock"]
style ROLLBACK fill:#f8d7da
style P2 fill:#d4edda
Completed within a single write lock, more efficient and atomic than external Remove + Add.
db.Documents.Upsert(new Document
{
Id = "doc-001",
Title = "Updated Getting Started Guide",
Embedding = new float[384]
});
// Key exists -> RemoveCore() + AddCore()
// Key doesn't exist -> AddCore() directly// Remove by entity (matched by primary key, not by reference comparison)
bool removed = db.Documents.Remove(entity);
// Remove by primary key directly (no entity reference needed)
bool removed = db.Documents.RemoveByKey("doc-001");Internal removal flow (RemoveCore):
- Reverse lookup internal ID via
_keyToId - Remove entity from
_entitiesdictionary - Remove key mapping from
_keyToIddictionary - Remove vector from all
_indices(index implementations handle residual references internally)
// Find by primary key, O(1) complexity (dual dictionary: key -> internal ID -> entity)
Document? doc = db.Documents.Find("doc-001");// By primary key, O(1) complexity (only checks _keyToId dictionary, one fewer lookup than Find)
bool exists = db.Documents.Exists("doc-001");
// By predicate, O(n) worst case (iterates within read lock, short-circuits on first match)
bool hasTutorial = db.Documents.Exists(e => e.Category == "Tutorial");Comparison with other approaches:
| Approach | Complexity | Memory Allocation | Use Case |
|---|---|---|---|
Exists(key) |
O(1) | None | Check by primary key |
Exists(predicate) |
O(n) short-circuit | None | Check by property condition |
Find(key) != null |
O(1) | None | When you also need the entity |
LINQ .Any(predicate)
|
O(n) | O(n) snapshot | Not recommended — creates full snapshot |
Performance Tip:
Exists(predicate)iterates_entities.Valuesdirectly within the read lock, returning immediately on match, with zero snapshot allocation. Compared to LINQ'sAny()which goes throughGetEnumerator()creating an O(n) snapshot array first, this is both faster and allocation-free.
db.Documents.Clear();
// Clears _entities + _keyToId + all indices
// Resets _nextId = 0int count = db.Documents.Count; // Thread-safe (read lock)
// View vector field metadata
foreach (var (name, dimensions) in db.Documents.VectorFields)
Console.WriteLine($"Field: {name}, Dimensions: {dimensions}");QuiverSet<TEntity> implements IEnumerable<TEntity>, supporting foreach loops and LINQ queries.
// foreach to iterate all entities
foreach (var doc in db.Documents)
Console.WriteLine($"{doc.Id}: {doc.Title}");
// LINQ filter + sort
var tutorials = db.Documents
.Where(e => e.Category == "Tutorial")
.OrderBy(e => e.Title)
.ToList();
// LINQ aggregation
var categoryCount = db.Documents
.GroupBy(e => e.Category)
.Select(g => new { Category = g.Key, Count = g.Count() })
.ToList();
// LINQ conditional count
int tutorialCount = db.Documents.Count(e => e.Category == "Tutorial");Thread Safety Semantics:
Enumeration takes a snapshot of entities within a read lock (shallow copy), then releases the lock before yield returning one by one. This means:
- ✅ No lock is held during enumeration, so write operations are not blocked
- ✅ Concurrent writes during enumeration do not affect the already-captured snapshot
- ✅ Any code can safely run inside the
foreachbody with no deadlock risk ⚠️ Each enumeration creates an O(n) snapshot copy — be mindful of performance with large datasets
Performance Tip: If you only need to find by primary key, use
Find(key)(O(1)); if you need vector similarity ordering, useSearch(...).foreach/ LINQ is best suited for full-collection traversal or filtering by non-vector properties.
| # | 章节 |
|---|---|
| 01 | 版本说明 |
| 02 | 产品概述 |
| 03 | 架构概述 |
| 04 | 快速开始 |
| 05 | 核心概念 |
| 06 | 距离度量 |
| 07 | 索引类型 |
| 08 | CRUD 操作 |
| 09 | 向量搜索 |
| 10 | 持久化存储 |
| 11 | 迁移系统 |
| 11a | 模式迁移 |
| 12 | 多向量字段支持 |
| 13 | 线程安全与并发 |
| 14 | 生命周期管理 |
| 15 | 配置选项 |
| 16 | 内部实现细节 |
| 17 | 完整示例 |
| 18 | API 参考速查表 |
| 19 | 使用建议 |