Skip to content

ingkor/SurrealDB.Client

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

77 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SurrealDB.Client

NuGet License

A modern, production-grade .NET/C# client library for SurrealDB with connection pooling, typed exception handling, and flexible protocol support (HTTP and WebSocket).

Consumer Documentation

For package users, start here:

Document Purpose
docs/consumer/README.md Main entry point - What is SurrealDB.Client?
docs/consumer/GETTING_STARTED.md Installation, connection, first queries
docs/consumer/API_REFERENCE.md Complete API documentation
docs/consumer/EXAMPLES.md Real-world code examples
docs/consumer/SECURITY.md Security best practices
docs/consumer/CHANGELOG.md Version history and upgrade guide

Technical Documentation

Architecture & Design

Document Focus
ARCHITECTURE.md Architectural design and EF Core comparison
DESIGN_DECISIONS.md Key design choices and rationale
STATE_MANAGEMENT.md Entity states, change tracking, session lifecycle
QUERY_COMPOSITION.md IQueryable API, query building, expression translation
LOADING_PATTERNS.md Include/Lazy/Explicit loading patterns
INTERCEPTORS.md Middleware and query interception
QUERY_CACHING.md Multi-level caching strategies
DIAGNOSTICS.md Monitoring, profiling, observability

Enterprise Features

Document Focus
MIGRATIONS.md Schema versioning, migrations, rollback
PLUGINS.md Plugin architecture and extensibility
DATALOADER.md Batch loading, N+1 prevention
EVENT_SOURCING.md Event sourcing, event replay, snapshots

Features

Core Capabilities ✨

  • EF Core-Inspired State Management: ISurrealDbSession with automatic change tracking
  • Entity Change Tracking: Snapshot-based detection with property-level granularity
  • Composable Queries: IQueryable API with deferred execution
  • Protocol Flexibility: HTTP and WebSocket support with runtime selection
  • Real-Time Subscriptions: Live queries with change notifications
  • Transactions: ACID-compliant with isolation level control
  • Optimistic Concurrency: Version tokens for conflict detection
  • Typed Exceptions: Database-agnostic error hierarchy
  • Connection Pooling: Intelligent pooling with health checking
  • Multi-Serializer: System.Text.Json, Newtonsoft.Json, or custom

🌟 Full Feature Overview

Core Features:

  • βœ… Unit of Work pattern (ISurrealDbSession)
  • βœ… Automatic change tracking with snapshots
  • βœ… IQueryable composition (deferred execution)
  • βœ… Include/Lazy/Explicit loading patterns
  • βœ… Advanced interceptors and middleware
  • βœ… Multi-level caching (plan/compiled/result)
  • βœ… Optimistic concurrency tokens
  • βœ… Typed exceptions
  • βœ… Protocol abstraction (HTTP/WebSocket)
  • βœ… Real-time subscriptions
  • βœ… Comprehensive diagnostics

Enterprise Features:

  • βœ… Migrations: Schema versioning with rollback
  • βœ… Security: RLS, field encryption, audit trails, compliance
  • βœ… Plugins: Extensible plugin architecture
  • βœ… DataLoader: Batch loading, N+1 prevention
  • βœ… Event Sourcing: Event store, event replay, snapshots

Unique Competitive Advantages:

  • πŸš€ Real-time subscriptions (not in EF Core)
  • πŸš€ Protocol abstraction (HTTP/WebSocket)
  • πŸš€ Multi-serializer support
  • πŸš€ 99% bandwidth efficiency (change tracking)

πŸš€ Quick Start

⚠️ Requirements

  • SurrealDB 3.0 or newer (required - 1.x and 2.x not supported)
    • Features like UPSERT syntax depend on SurrealDB 3.0+ enhancements
    • Version validation occurs on ConnectAsync() with clear error messaging
    • Update your SurrealDB deployment to 3.0+ before using this client

Installation

dotnet add package SurrealDB.Client

Basic Example

using SurrealDB.Client;

// Create and connect client
var client = new SurrealDbClient("surreal://localhost:8000");
await client.ConnectAsync();

// Authenticate
await client.AuthenticateAsync(new UsernamePasswordAuth("user", "password"));

// Create session (Unit of Work)
using var session = client.CreateSession();

// Create entity
var user = new User { Name = "John", Email = "john@example.com" };
session.Add(user);

// Query with composition
var adults = await session.Set<User>()
    .Where(u => u.Age >= 18)
    .OrderBy(u => u.Name)
    .ToListAsync();

// Modify (automatic change tracking)
user.Email = "newemail@example.com";
await session.SaveChangesAsync();  // Only Email property sent to server

// Real-time subscriptions
using var subscription = await client.SubscribeAsync<User>(
    q => q.Where(u => u.Status == "online")
);

await foreach (var change in subscription.GetChangesAsync())
{
    Console.WriteLine($"Change: {change.Action} - {change.Record.Name}");
}

// Cleanup
await client.DisconnectAsync();

πŸ—οΈ Core Concepts

1. Session (ISurrealDbSession) - Unit of Work

A bounded context managing a coherent set of changes:

using var session = client.CreateSession();

// Track changes
var user = await session.FindAsync<User>("user:1");
user.Email = "new@example.com";  // Automatically tracked

// Atomic commit
await session.SaveChangesAsync();
// Only changed properties sent β†’ bandwidth efficient

2. Entity States

State Meaning SaveChangesAsync() Action
Detached Not tracked None
Added New entity INSERT
Unchanged Loaded, no changes None
Modified Loaded, then changed UPDATE (only changed properties)
Deleted Marked for deletion DELETE

3. Change Tracking

Automatic snapshot-based change detection:

var user = await session.FindAsync<User>("user:1");
// Snapshot captured

user.Email = "new@test.com";  // Property changed
user.UpdatedAt = DateTime.UtcNow;

var entry = session.ChangeTracker.Entry(user);
var changed = entry.GetModifiedProperties();  // ["Email", "UpdatedAt"]

await session.SaveChangesAsync();
// UPDATE users:1 SET Email = ..., UpdatedAt = ...
// (Only changed properties sent, not entire object)

4. IQueryable Composition

Build complex queries step-by-step without execution:

// Composable extensions
public static IQueryable<User> Active(this IQueryable<User> query)
    => query.Where(u => u.Status == "active");

public static IQueryable<User> Adults(this IQueryable<User> query)
    => query.Where(u => u.Age >= 18);

// Fluent usage - single query to server
var results = await session.Set<User>()
    .Active()
    .Adults()
    .OrderBy(u => u.Name)
    .ToListAsync();

5. Optimistic Concurrency

Prevent silent overwrites:

public class User
{
    public string Id { get; set; }
    [ConcurrencyToken]  // Auto-managed by server
    public long Version { get; set; }
    public string Email { get; set; }
}

try
{
    user.Email = "new@example.com";
    await session.SaveChangesAsync();
}
catch (ConcurrencyException)
{
    // Handle conflict - reload and retry
    await session.Entry(user).ReloadAsync();
}

πŸ“Š Performance

Bandwidth Efficiency Example

Scenario: Update 1 property of 20-property object

Without change tracking:
  Request size: 20KB (entire object)

With change tracking:
  Request size: 200B (only changed property)

Efficiency: 99% bandwidth reduction

Operation Benchmarks

Operation Time Notes
Connection setup <1s Pooled connections
Authentication <500ms Token validation
Simple query <50ms 100 records
Update (1 property) <100ms With change tracking
Batch (100 records) <200ms Single transaction

πŸ§ͺ Testing

Unit Testing

var mockSession = new Mock<ISurrealDbSession>();
var repository = new UserRepository(mockSession.Object);

var user = await repository.GetUserAsync("user:1");
Assert.NotNull(user);

Integration Testing

[Collection("Database")]
public class UserRepositoryTests : IAsyncLifetime
{
    private ISurrealDbSession _session;

    [Fact]
    public async Task CreateAndRetrieve_Succeeds()
    {
        var user = new User { Name = "Test" };
        _session.Add(user);
        await _session.SaveChangesAsync();

        var retrieved = await _session.FindAsync<User>(user.Id);
        Assert.NotNull(retrieved);
    }
}

πŸ”§ Configuration

Basic Setup

var client = new SurrealDbClient("surreal://user:password@localhost:8000");
await client.ConnectAsync();

using var session = client.CreateSession();
// Use session for Unit of Work

Dependency Injection

services.AddSurrealDbClient(options =>
{
    options.ConnectionString = configuration["Database:ConnectionString"];
    options.EnableLogging = true;
    options.PoolSize = 10;
});

πŸŽ“ Usage Patterns

Pattern 1: Query & Modify

using var session = client.CreateSession();

var user = await session.FindAsync<User>("user:1");
user.Email = "newemail@example.com";
await session.SaveChangesAsync();  // Efficient: only Email sent

Pattern 2: Create with Relationships

using var session = client.CreateSession();

var user = new User { Name = "John", Email = "john@example.com" };
session.Add(user);

var order = new Order { UserId = user.Id, Amount = 100 };
session.Add(order);

await session.SaveChangesAsync();  // Atomic transaction

Pattern 3: Bulk Operations

using var session = client.CreateSession();

var users = await session.Set<User>()
    .Where(u => u.Status == "inactive")
    .ToListAsync();

foreach (var user in users)
    user.Status = "active";

await session.SaveChangesAsync();  // Single transaction for all

Pattern 4: Real-Time Subscriptions

using var subscription = await client.SubscribeAsync<User>(
    q => q.Where(u => u.Status == "online")
);

await foreach (var change in subscription.GetChangesAsync())
{
    if (change.Action == ChangeAction.Create)
        Console.WriteLine($"New: {change.Record.Name}");
}

πŸ”„ Migration from Other Clients

From Entity Framework Core

// EF Core
using var context = new AppDbContext();
var users = await context.Users.Where(u => u.Active).ToListAsync();

// SurrealDB.Client - Nearly identical API!
using var session = client.CreateSession();
var users = await session.Set<User>()
    .Where(u => u.Status == "active")
    .ToListAsync();

From Raw SurrealQL

// Raw queries still supported
var results = await client.QueryAsync<User>(
    "SELECT * FROM users WHERE age >= $age",
    new { age = 18 }
);

πŸ›‘οΈ Error Handling

Typed Exception Hierarchy

try
{
    await session.SaveChangesAsync();
}
catch (UniqueConstraintException ex)
{
    logger.LogWarning($"Duplicate: {ex.Details}");
}
catch (ConcurrencyException ex)
{
    await session.Entry(ex.Entity).ReloadAsync();
}
catch (SurrealDbException ex)
{
    logger.LogError($"Error: {ex.Message}");
}

πŸ“– Complete Documentation


πŸ“ License

MIT License - see LICENSE for details

πŸ”— Resources

πŸ™ Acknowledgments

Architecture inspired by Entity Framework Core's proven patterns, adapted for SurrealDB's unique capabilities.

About

No description, website, or topics provided.

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages