Reformat/cleanup
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m10s

This commit is contained in:
Joseph Doherty
2026-02-21 07:53:53 -05:00
parent c6f6d9329a
commit 7ebc2cb567
160 changed files with 7258 additions and 7262 deletions

View File

@@ -1,37 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.CBDDC.Core;
using Serilog.Context;
using ZB.MOM.WW.CBDDC.Core.Cache;
using ZB.MOM.WW.CBDDC.Core.Diagnostics;
using ZB.MOM.WW.CBDDC.Core.Sync;
using ZB.MOM.WW.CBDDC.Core.Storage;
using ZB.MOM.WW.CBDDC.Network;
using ZB.MOM.WW.CBDDC.Persistence.BLite;
using Microsoft.Extensions.DependencyInjection; // For IServiceProvider if needed
using Serilog.Context;
using ZB.MOM.WW.CBDDC.Sample.Console;
using ZB.MOM.WW.CBDDC.Core.Network;
using ZB.MOM.WW.CBDDC.Core.Sync;
using ZB.MOM.WW.CBDDC.Network;
using ZB.MOM.WW.CBDDC.Network.Security;
// For IServiceProvider if needed
namespace ZB.MOM.WW.CBDDC.Sample.Console;
public class ConsoleInteractiveService : BackgroundService
{
private readonly ILogger<ConsoleInteractiveService> _logger;
private readonly SampleDbContext _db;
private readonly ICBDDCNode _node;
private readonly IHostApplicationLifetime _lifetime;
// Auxiliary services for status/commands
private readonly IDocumentCache _cache;
private readonly IOfflineQueue _queue;
private readonly ICBDDCHealthCheck _healthCheck;
private readonly ISyncStatusTracker _syncTracker;
private readonly IServiceProvider _serviceProvider;
private readonly IPeerNodeConfigurationProvider _configProvider;
private readonly SampleDbContext _db;
private readonly ICBDDCHealthCheck _healthCheck;
private readonly IHostApplicationLifetime _lifetime;
private readonly ILogger<ConsoleInteractiveService> _logger;
private readonly ICBDDCNode _node;
private readonly IOfflineQueue _queue;
private readonly IServiceProvider _serviceProvider;
private readonly ISyncStatusTracker _syncTracker;
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleInteractiveService"/> class.
/// Initializes a new instance of the <see cref="ConsoleInteractiveService" /> class.
/// </summary>
/// <param name="logger">The logger used by the interactive service.</param>
/// <param name="db">The sample database context.</param>
@@ -72,7 +68,7 @@ public class ConsoleInteractiveService : BackgroundService
{
var config = await _configProvider.GetConfiguration();
System.Console.WriteLine($"--- Interactive Console ---");
System.Console.WriteLine("--- Interactive Console ---");
System.Console.WriteLine($"Node ID: {config.NodeId}");
PrintHelp();
@@ -85,7 +81,7 @@ public class ConsoleInteractiveService : BackgroundService
continue;
}
var input = System.Console.ReadLine();
string? input = System.Console.ReadLine();
if (string.IsNullOrEmpty(input)) continue;
try
@@ -118,42 +114,53 @@ public class ConsoleInteractiveService : BackgroundService
System.Console.WriteLine(" [n]ew (auto), [s]pam (5x), [c]ount, [t]odos");
System.Console.WriteLine(" [h]ealth, cac[h]e");
System.Console.WriteLine(" [r]esolver [lww|merge], [demo] conflict");
}
}
private async Task HandleInput(string input)
{
var config = await _configProvider.GetConfiguration();
if (input.StartsWith("n"))
{
var ts = DateTime.Now.ToString("HH:mm:ss.fff");
var user = new User { Id = Guid.NewGuid().ToString(), Name = $"User-{ts}", Age = new Random().Next(18, 90), Address = new Address { City = "AutoCity" } };
var user = new User
{
Id = Guid.NewGuid().ToString(), Name = $"User-{ts}", Age = new Random().Next(18, 90),
Address = new Address { City = "AutoCity" }
};
await _db.Users.InsertAsync(user);
await _db.SaveChangesAsync();
System.Console.WriteLine($"[+] Created {user.Name} with Id: {user.Id}...");
}
else if (input.StartsWith("s"))
{
for (int i = 0; i < 5; i++)
for (var i = 0; i < 5; i++)
{
var ts = DateTime.Now.ToString("HH:mm:ss.fff");
var user = new User { Id = Guid.NewGuid().ToString(), Name = $"User-{ts}", Age = new Random().Next(18, 90), Address = new Address { City = "SpamCity" } };
var user = new User
{
Id = Guid.NewGuid().ToString(), Name = $"User-{ts}", Age = new Random().Next(18, 90),
Address = new Address { City = "SpamCity" }
};
await _db.Users.InsertAsync(user);
System.Console.WriteLine($"[+] Created {user.Name} with Id: {user.Id}...");
await Task.Delay(100);
}
await _db.SaveChangesAsync();
}
else if (input.StartsWith("c"))
{
var userCount = _db.Users.FindAll().Count();
var todoCount = _db.TodoLists.FindAll().Count();
int userCount = _db.Users.FindAll().Count();
int todoCount = _db.TodoLists.FindAll().Count();
System.Console.WriteLine($"Collection 'Users': {userCount} documents");
System.Console.WriteLine($"Collection 'TodoLists': {todoCount} documents");
}
else if (input.StartsWith("p"))
{
var alice = new User { Id = Guid.NewGuid().ToString(), Name = "Alice", Age = 30, Address = new Address { City = "Paris" } };
var bob = new User { Id = Guid.NewGuid().ToString(), Name = "Bob", Age = 25, Address = new Address { City = "Rome" } };
var alice = new User
{ Id = Guid.NewGuid().ToString(), Name = "Alice", Age = 30, Address = new Address { City = "Paris" } };
var bob = new User
{ Id = Guid.NewGuid().ToString(), Name = "Bob", Age = 25, Address = new Address { City = "Rome" } };
await _db.Users.InsertAsync(alice);
await _db.Users.InsertAsync(bob);
await _db.SaveChangesAsync();
@@ -162,17 +169,19 @@ public class ConsoleInteractiveService : BackgroundService
else if (input.StartsWith("g"))
{
System.Console.Write("Enter user Id: ");
var id = System.Console.ReadLine();
string? id = System.Console.ReadLine();
if (!string.IsNullOrEmpty(id))
{
var u = _db.Users.FindById(id);
System.Console.WriteLine(u != null ? $"Got: {u.Name}, Age {u.Age}, City: {u.Address?.City}" : "Not found");
System.Console.WriteLine(u != null
? $"Got: {u.Name}, Age {u.Age}, City: {u.Address?.City}"
: "Not found");
}
}
else if (input.StartsWith("d"))
{
System.Console.Write("Enter user Id to delete: ");
var id = System.Console.ReadLine();
string? id = System.Console.ReadLine();
if (!string.IsNullOrEmpty(id))
{
await _db.Users.DeleteAsync(id);
@@ -183,8 +192,8 @@ public class ConsoleInteractiveService : BackgroundService
else if (input.StartsWith("l"))
{
var peers = _node.Discovery.GetActivePeers();
var handshakeSvc = _serviceProvider.GetService<ZB.MOM.WW.CBDDC.Network.Security.IPeerHandshakeService>();
var secureIcon = handshakeSvc != null ? "🔒" : "🔓";
var handshakeSvc = _serviceProvider.GetService<IPeerHandshakeService>();
string secureIcon = handshakeSvc != null ? "🔒" : "🔓";
System.Console.WriteLine($"Active Peers ({secureIcon}):");
foreach (var p in peers)
@@ -203,7 +212,7 @@ public class ConsoleInteractiveService : BackgroundService
{
var health = await _healthCheck.CheckAsync();
var syncStatus = _syncTracker.GetStatus();
var handshakeSvc = _serviceProvider.GetService<ZB.MOM.WW.CBDDC.Network.Security.IPeerHandshakeService>();
var handshakeSvc = _serviceProvider.GetService<IPeerHandshakeService>();
System.Console.WriteLine("=== Health Check ===");
System.Console.WriteLine($"Database: {(health.DatabaseHealthy ? "" : "")}");
@@ -216,17 +225,18 @@ public class ConsoleInteractiveService : BackgroundService
if (health.Errors.Any())
{
System.Console.WriteLine("Errors:");
foreach (var err in health.Errors.Take(3)) System.Console.WriteLine($" - {err}");
foreach (string err in health.Errors.Take(3)) System.Console.WriteLine($" - {err}");
}
}
else if (input.StartsWith("ch") || input == "cache")
{
var stats = _cache.GetStatistics();
System.Console.WriteLine($"=== Cache Stats ===\nSize: {stats.Size}\nHits: {stats.Hits}\nMisses: {stats.Misses}\nRate: {stats.HitRate:P1}");
System.Console.WriteLine(
$"=== Cache Stats ===\nSize: {stats.Size}\nHits: {stats.Hits}\nMisses: {stats.Misses}\nRate: {stats.HitRate:P1}");
}
else if (input.StartsWith("r") && input.Contains("resolver"))
{
var parts = input.Split(' ');
string[] parts = input.Split(' ');
if (parts.Length > 1)
{
var newResolver = parts[1].ToLower() switch
@@ -240,7 +250,7 @@ public class ConsoleInteractiveService : BackgroundService
{
// Note: Requires restart to fully apply. For demo, we inform user.
System.Console.WriteLine($"⚠️ Resolver changed to {parts[1].ToUpper()}. Restart node to apply.");
System.Console.WriteLine($" (Current session continues with previous resolver)");
System.Console.WriteLine(" (Current session continues with previous resolver)");
}
else
{
@@ -262,7 +272,7 @@ public class ConsoleInteractiveService : BackgroundService
System.Console.WriteLine($"📋 {list.Name} ({list.Items.Count} items)");
foreach (var item in list.Items)
{
var status = item.Completed ? "✓" : " ";
string status = item.Completed ? "✓" : " ";
System.Console.WriteLine($" [{status}] {item.Task}");
}
}
@@ -281,8 +291,8 @@ public class ConsoleInteractiveService : BackgroundService
Name = "Shopping List",
Items = new List<TodoItem>
{
new TodoItem { Task = "Buy milk", Completed = false },
new TodoItem { Task = "Buy bread", Completed = false }
new() { Task = "Buy milk", Completed = false },
new() { Task = "Buy bread", Completed = false }
}
};
@@ -325,24 +335,20 @@ public class ConsoleInteractiveService : BackgroundService
System.Console.WriteLine($" List: {merged.Name}");
foreach (var item in merged.Items)
{
var status = item.Completed ? "✓" : " ";
string status = item.Completed ? "✓" : " ";
System.Console.WriteLine($" [{status}] {item.Task}");
}
var resolver = _serviceProvider.GetRequiredService<IConflictResolver>();
var resolverType = resolver.GetType().Name;
string resolverType = resolver.GetType().Name;
System.Console.WriteLine($"\n Resolution Strategy: {resolverType}");
if (resolverType.Contains("Recursive"))
{
System.Console.WriteLine(" → Items merged by 'id', both edits preserved");
}
else
{
System.Console.WriteLine(" → Last write wins, Node B changes override Node A");
}
}
System.Console.WriteLine("\n✓ Demo complete. Run 'todos' to see all lists.\n");
}
}
}

View File

@@ -1,33 +1,26 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.CBDDC.Core;
using ZB.MOM.WW.CBDDC.Core.Storage;
using ZB.MOM.WW.CBDDC.Core.Cache;
using ZB.MOM.WW.CBDDC.Core.Sync;
using ZB.MOM.WW.CBDDC.Core.Diagnostics;
using ZB.MOM.WW.CBDDC.Core.Resilience;
using ZB.MOM.WW.CBDDC.Network;
using ZB.MOM.WW.CBDDC.Network.Security;
using ZB.MOM.WW.CBDDC.Persistence.BLite;
using ZB.MOM.WW.CBDDC.Sample.Console;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using ZB.MOM.WW.CBDDC.Core.Network;
using ZB.MOM.WW.CBDDC.Core.Sync;
using ZB.MOM.WW.CBDDC.Network;
using ZB.MOM.WW.CBDDC.Persistence.BLite;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
// Local User/Address classes removed in favor of Shared project
class Program
internal class Program
{
static async Task Main(string[] args)
private static async Task Main(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
// Configuration
builder.Configuration.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
.AddJsonFile("appsettings.json", true, true);
// Logging
builder.Logging.ClearProviders();
@@ -38,39 +31,36 @@ class Program
.Enrich.WithProperty("Application", "CBDDC.Sample.Console")
.WriteTo.Console());
var randomPort = new Random().Next(1000, 9999);
int randomPort = new Random().Next(1000, 9999);
// Node ID
string nodeId = args.Length > 0 ? args[0] : ("node-" + randomPort);
string nodeId = args.Length > 0 ? args[0] : "node-" + randomPort;
int tcpPort = args.Length > 1 ? int.Parse(args[1]) : randomPort;
// Conflict Resolution Strategy (can be switched at runtime via service replacement)
var useRecursiveMerge = args.Contains("--merge");
if (useRecursiveMerge)
{
builder.Services.AddSingleton<IConflictResolver, RecursiveNodeMergeConflictResolver>();
}
bool useRecursiveMerge = args.Contains("--merge");
if (useRecursiveMerge) builder.Services.AddSingleton<IConflictResolver, RecursiveNodeMergeConflictResolver>();
IPeerNodeConfigurationProvider peerNodeConfigurationProvider = new StaticPeerNodeConfigurationProvider(
new PeerNodeConfiguration
{
NodeId = nodeId,
TcpPort = tcpPort,
AuthToken = "Test-Cluster-Key",
AuthToken = "Test-Cluster-Key"
//KnownPeers = builder.Configuration.GetSection("CBDDC:KnownPeers").Get<List<KnownPeerConfiguration>>() ?? new()
});
builder.Services.AddSingleton<IPeerNodeConfigurationProvider>(peerNodeConfigurationProvider);
builder.Services.AddSingleton(peerNodeConfigurationProvider);
// Database path
var dataPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data");
string dataPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data");
Directory.CreateDirectory(dataPath);
var databasePath = Path.Combine(dataPath, $"{nodeId}.blite");
string databasePath = Path.Combine(dataPath, $"{nodeId}.blite");
// Register CBDDC Services using Fluent Extensions with BLite, SampleDbContext, and SampleDocumentStore
builder.Services.AddCBDDCCore()
.AddCBDDCBLite<SampleDbContext, SampleDocumentStore>(sp => new SampleDbContext(databasePath))
.AddCBDDCNetwork<StaticPeerNodeConfigurationProvider>(); // useHostedService = true by default
.AddCBDDCBLite<SampleDbContext, SampleDocumentStore>(sp => new SampleDbContext(databasePath))
.AddCBDDCNetwork<StaticPeerNodeConfigurationProvider>(); // useHostedService = true by default
builder.Services.AddHostedService<ConsoleInteractiveService>(); // Runs the Input Loop
@@ -86,12 +76,7 @@ class Program
private class StaticPeerNodeConfigurationProvider : IPeerNodeConfigurationProvider
{
/// <summary>
/// Gets or sets the current peer node configuration.
/// </summary>
public PeerNodeConfiguration Configuration { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="StaticPeerNodeConfigurationProvider"/> class.
/// Initializes a new instance of the <see cref="StaticPeerNodeConfigurationProvider" /> class.
/// </summary>
/// <param name="configuration">The initial peer node configuration.</param>
public StaticPeerNodeConfigurationProvider(PeerNodeConfiguration configuration)
@@ -100,12 +85,17 @@ class Program
}
/// <summary>
/// Occurs when the peer node configuration changes.
/// Gets or sets the current peer node configuration.
/// </summary>
public PeerNodeConfiguration Configuration { get; }
/// <summary>
/// Occurs when the peer node configuration changes.
/// </summary>
public event PeerNodeConfigurationChangedEventHandler? ConfigurationChanged;
/// <summary>
/// Gets the current peer node configuration.
/// Gets the current peer node configuration.
/// </summary>
/// <returns>A task that returns the current configuration.</returns>
public Task<PeerNodeConfiguration> GetConfiguration()
@@ -114,7 +104,7 @@ class Program
}
/// <summary>
/// Raises the configuration changed event.
/// Raises the configuration changed event.
/// </summary>
/// <param name="newConfig">The new configuration value.</param>
protected virtual void OnConfigurationChanged(PeerNodeConfiguration newConfig)
@@ -122,5 +112,4 @@ class Program
ConfigurationChanged?.Invoke(this, newConfig);
}
}
}
}

View File

@@ -5,21 +5,25 @@ This sample demonstrates the core features of CBDDC, a distributed peer-to-peer
## Features Demonstrated
### 🔑 Primary Keys & Auto-Generation
- Automatic GUID generation for entities
- Convention-based key detection (`Id` property)
- `[PrimaryKey]` attribute support
### 🎯 Generic Type-Safe API
- `Collection<T>()` for compile-time type safety
- Keyless `Put(entity)` with auto-key extraction
- IntelliSense-friendly operations
### 🔍 LINQ Query Support
- Expression-based queries
- Paging and sorting
- Complex predicates (>, >=, ==, !=, nested properties)
### 🌐 Network Synchronization
- UDP peer discovery
- TCP synchronization
- Automatic conflict resolution (Last-Write-Wins)
@@ -35,16 +39,19 @@ dotnet run
### Multi-Node (Peer-to-Peer)
Terminal 1:
```bash
dotnet run -- --node-id node1 --tcp-port 5001 --udp-port 6001
```
Terminal 2:
```bash
dotnet run -- --node-id node2 --tcp-port 5002 --udp-port 6002
```
Terminal 3:
```bash
dotnet run -- --node-id node3 --tcp-port 5003 --udp-port 6003
```
@@ -53,20 +60,20 @@ Changes made on any node will automatically sync to all peers!
## Available Commands
| Command | Description |
|---------|-------------|
| `p` | Put Alice and Bob (auto-generated IDs) |
| `g` | Get user by ID (prompts for ID) |
| `d` | Delete user by ID (prompts for ID) |
| `n` | Create new user with auto-generated ID |
| `s` | Spam 5 users with auto-generated IDs |
| `c` | Count total documents |
| `f` | Demo various Find queries |
| `f2` | Demo Find with paging (skip/take) |
| `a` | Demo auto-generated primary keys |
| `t` | Demo generic typed API |
| `l` | List active peers |
| `q` | Quit |
| Command | Description |
|---------|----------------------------------------|
| `p` | Put Alice and Bob (auto-generated IDs) |
| `g` | Get user by ID (prompts for ID) |
| `d` | Delete user by ID (prompts for ID) |
| `n` | Create new user with auto-generated ID |
| `s` | Spam 5 users with auto-generated IDs |
| `c` | Count total documents |
| `f` | Demo various Find queries |
| `f2` | Demo Find with paging (skip/take) |
| `a` | Demo auto-generated primary keys |
| `t` | Demo generic typed API |
| `l` | List active peers |
| `q` | Quit |
## Example Session

View File

@@ -2,28 +2,13 @@
using BLite.Core.Metadata;
using BLite.Core.Storage;
using ZB.MOM.WW.CBDDC.Persistence.BLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
public partial class SampleDbContext : CBDDCDocumentDbContext
public class SampleDbContext : CBDDCDocumentDbContext
{
/// <summary>
/// Gets the users collection.
/// </summary>
public DocumentCollection<string, User> Users { get; private set; } = null!;
/// <summary>
/// Gets the todo lists collection.
/// </summary>
public DocumentCollection<string, TodoList> TodoLists { get; private set; } = null!;
/// <summary>
/// Initializes a new instance of the SampleDbContext class using the specified database file path.
/// Initializes a new instance of the SampleDbContext class using the specified database file path.
/// </summary>
/// <param name="databasePath">The file system path to the database file. Cannot be null or empty.</param>
public SampleDbContext(string databasePath) : base(databasePath)
@@ -31,8 +16,8 @@ public partial class SampleDbContext : CBDDCDocumentDbContext
}
/// <summary>
/// Initializes a new instance of the SampleDbContext class using the specified database file path and page file
/// configuration.
/// Initializes a new instance of the SampleDbContext class using the specified database file path and page file
/// configuration.
/// </summary>
/// <param name="databasePath">The file system path to the database file. Cannot be null or empty.</param>
/// <param name="config">The configuration settings for the page file. Cannot be null.</param>
@@ -40,6 +25,16 @@ public partial class SampleDbContext : CBDDCDocumentDbContext
{
}
/// <summary>
/// Gets the users collection.
/// </summary>
public DocumentCollection<string, User> Users { get; private set; } = null!;
/// <summary>
/// Gets the todo lists collection.
/// </summary>
public DocumentCollection<string, TodoList> TodoLists { get; private set; } = null!;
/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -52,4 +47,4 @@ public partial class SampleDbContext : CBDDCDocumentDbContext
.ToCollection("TodoLists")
.HasKey(t => t.Id);
}
}
}

View File

@@ -1,16 +1,15 @@
using ZB.MOM.WW.CBDDC.Core;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.CBDDC.Core.Network;
using ZB.MOM.WW.CBDDC.Core.Storage;
using ZB.MOM.WW.CBDDC.Core.Sync;
using ZB.MOM.WW.CBDDC.Persistence.BLite;
using Microsoft.Extensions.Logging;
using System.Text.Json;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
/// <summary>
/// Document store implementation for CBDDC Sample using BLite persistence.
/// Extends BLiteDocumentStore to automatically handle Oplog creation via CDC.
/// Document store implementation for CBDDC Sample using BLite persistence.
/// Extends BLiteDocumentStore to automatically handle Oplog creation via CDC.
/// </summary>
public class SampleDocumentStore : BLiteDocumentStore<SampleDbContext>
{
@@ -18,7 +17,7 @@ public class SampleDocumentStore : BLiteDocumentStore<SampleDbContext>
private const string TodoListsCollection = "TodoLists";
/// <summary>
/// Initializes a new instance of the <see cref="SampleDocumentStore"/> class.
/// Initializes a new instance of the <see cref="SampleDocumentStore" /> class.
/// </summary>
/// <param name="context">The sample database context.</param>
/// <param name="configProvider">The peer node configuration provider.</param>
@@ -37,6 +36,16 @@ public class SampleDocumentStore : BLiteDocumentStore<SampleDbContext>
WatchCollection(TodoListsCollection, context.TodoLists, t => t.Id);
}
#region Helper Methods
private static JsonElement? SerializeEntity<T>(T? entity) where T : class
{
if (entity == null) return null;
return JsonSerializer.SerializeToElement(entity);
}
#endregion
#region Abstract Method Implementations
/// <inheritdoc />
@@ -49,12 +58,10 @@ public class SampleDocumentStore : BLiteDocumentStore<SampleDbContext>
/// <inheritdoc />
protected override async Task ApplyContentToEntitiesBatchAsync(
IEnumerable<(string Collection, string Key, JsonElement Content)> documents, CancellationToken cancellationToken)
IEnumerable<(string Collection, string Key, JsonElement Content)> documents,
CancellationToken cancellationToken)
{
foreach (var (collection, key, content) in documents)
{
UpsertEntity(collection, key, content);
}
foreach ((string collection, string key, var content) in documents) UpsertEntity(collection, key, content);
await _context.SaveChangesAsync(cancellationToken);
}
@@ -91,10 +98,10 @@ public class SampleDocumentStore : BLiteDocumentStore<SampleDbContext>
protected override Task<JsonElement?> GetEntityAsJsonAsync(
string collection, string key, CancellationToken cancellationToken)
{
return Task.FromResult<JsonElement?>(collection switch
return Task.FromResult(collection switch
{
UsersCollection => SerializeEntity(_context.Users.Find(u => u.Id == key).FirstOrDefault()),
TodoListsCollection => SerializeEntity(_context.TodoLists.Find(t => t.Id == key).FirstOrDefault()),
UsersCollection => SerializeEntity(_context.Users.Find(u => u.Id == key).FirstOrDefault()),
TodoListsCollection => SerializeEntity(_context.TodoLists.Find(t => t.Id == key).FirstOrDefault()),
_ => null
});
}
@@ -111,10 +118,7 @@ public class SampleDocumentStore : BLiteDocumentStore<SampleDbContext>
protected override async Task RemoveEntitiesBatchAsync(
IEnumerable<(string Collection, string Key)> documents, CancellationToken cancellationToken)
{
foreach (var (collection, key) in documents)
{
DeleteEntity(collection, key);
}
foreach ((string collection, string key) in documents) DeleteEntity(collection, key);
await _context.SaveChangesAsync(cancellationToken);
}
@@ -140,25 +144,15 @@ public class SampleDocumentStore : BLiteDocumentStore<SampleDbContext>
{
return await Task.Run(() => collection switch
{
UsersCollection => _context.Users.FindAll()
.Select(u => (u.Id, SerializeEntity(u)!.Value)),
TodoListsCollection => _context.TodoLists.FindAll()
.Select(t => (t.Id, SerializeEntity(t)!.Value)),
_ => Enumerable.Empty<(string, JsonElement)>()
}, cancellationToken);
}
#endregion
#region Helper Methods
private static JsonElement? SerializeEntity<T>(T? entity) where T : class
{
if (entity == null) return null;
return JsonSerializer.SerializeToElement(entity);
}
#endregion
}
UsersCollection => _context.Users.FindAll()
.Select(u => (u.Id, SerializeEntity(u)!.Value)),
TodoListsCollection => _context.TodoLists.FindAll()
.Select(t => (t.Id, SerializeEntity(t)!.Value)),
_ => Enumerable.Empty<(string, JsonElement)>()
}, cancellationToken);
}
#endregion
}

View File

@@ -1,23 +1,22 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
using System.ComponentModel.DataAnnotations;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
public class TodoList
{
/// <summary>
/// Gets or sets the document identifier.
/// Gets or sets the document identifier.
/// </summary>
[Key]
public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the list name.
/// Gets or sets the list name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the todo items in the list.
/// Gets or sets the todo items in the list.
/// </summary>
public List<TodoItem> Items { get; set; } = new();
}
@@ -25,17 +24,17 @@ public class TodoList
public class TodoItem
{
/// <summary>
/// Gets or sets the task description.
/// Gets or sets the task description.
/// </summary>
public string Task { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether the task is completed.
/// Gets or sets a value indicating whether the task is completed.
/// </summary>
public bool Completed { get; set; }
/// <summary>
/// Gets or sets the UTC creation timestamp.
/// Gets or sets the UTC creation timestamp.
/// </summary>
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}

View File

@@ -1,27 +1,27 @@
using System.ComponentModel.DataAnnotations;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
using System.ComponentModel.DataAnnotations;
namespace ZB.MOM.WW.CBDDC.Sample.Console;
public class User
{
/// <summary>
/// Gets or sets the unique user identifier.
/// Gets or sets the unique user identifier.
/// </summary>
[Key]
public string Id { get; set; } = "";
/// <summary>
/// Gets or sets the user name.
/// Gets or sets the user name.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Gets or sets the user age.
/// Gets or sets the user age.
/// </summary>
public int Age { get; set; }
/// <summary>
/// Gets or sets the user address.
/// Gets or sets the user address.
/// </summary>
public Address? Address { get; set; }
}
@@ -29,7 +29,7 @@ public class User
public class Address
{
/// <summary>
/// Gets or sets the city value.
/// Gets or sets the city value.
/// </summary>
public string? City { get; set; }
}
}

View File

@@ -1,41 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Lifter.Core" Version="1.1.0" />
<PackageReference Include="BLite.SourceGenerators" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<ProjectReference Include="..\..\src\ZB.MOM.WW.CBDDC.Core\ZB.MOM.WW.CBDDC.Core.csproj" />
<ProjectReference Include="..\..\src\ZB.MOM.WW.CBDDC.Network\ZB.MOM.WW.CBDDC.Network.csproj" />
<ProjectReference Include="..\..\src\ZB.MOM.WW.CBDDC.Persistence\ZB.MOM.WW.CBDDC.Persistence.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lifter.Core" Version="1.1.0"/>
<PackageReference Include="BLite.SourceGenerators" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<ProjectReference Include="..\..\src\ZB.MOM.WW.CBDDC.Core\ZB.MOM.WW.CBDDC.Core.csproj"/>
<ProjectReference Include="..\..\src\ZB.MOM.WW.CBDDC.Network\ZB.MOM.WW.CBDDC.Network.csproj"/>
<ProjectReference Include="..\..\src\ZB.MOM.WW.CBDDC.Persistence\ZB.MOM.WW.CBDDC.Persistence.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0"/>
<PackageReference Include="Serilog" Version="4.2.0"/>
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<PropertyGroup>
<AssemblyName>ZB.MOM.WW.CBDDC.Sample.Console</AssemblyName>
<RootNamespace>ZB.MOM.WW.CBDDC.Sample.Console</RootNamespace>
<PackageId>ZB.MOM.WW.CBDDC.Sample.Console</PackageId>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<AssemblyName>ZB.MOM.WW.CBDDC.Sample.Console</AssemblyName>
<RootNamespace>ZB.MOM.WW.CBDDC.Sample.Console</RootNamespace>
<PackageId>ZB.MOM.WW.CBDDC.Sample.Console</PackageId>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>
</Project>

View File

@@ -1,51 +1,51 @@
{
"Logging": {
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"System": "Warning",
"CBDDC": "Information",
"ZB.MOM.WW.CBDDC.Network.SyncOrchestrator": "Information",
"ZB.MOM.WW.CBDDC.Core.Storage.OplogCoordinator": "Warning",
"ZB.MOM.WW.CBDDC.Persistence": "Warning"
}
},
"CBDDC": {
"Network": {
"TcpPort": 5001,
"UdpPort": 6000,
"AuthToken": "demo-secret-key",
"ConnectionTimeoutMs": 5000,
"RetryAttempts": 3,
"RetryDelayMs": 1000,
"LocalhostOnly": false
},
"Persistence": {
"DatabasePath": "data/cbddc.db",
"EnableWalMode": true,
"CacheSizeMb": 50,
"EnableAutoBackup": true,
"BackupPath": "backups/",
"BusyTimeoutMs": 5000
},
"Sync": {
"SyncIntervalMs": 5000,
"BatchSize": 100,
"EnableOfflineQueue": true,
"MaxQueueSize": 1000
},
"Logging": {
"LogLevel": "Information",
"LogFilePath": "logs/cbddc.log",
"MaxLogFileSizeMb": 10,
"MaxLogFiles": 5
},
"KnownPeers": [
{
"NodeId": "AspNetSampleNode",
"Host": "localhost",
"Port": 6001
}
]
"Default": "Information",
"Microsoft": "Warning",
"System": "Warning",
"CBDDC": "Information",
"ZB.MOM.WW.CBDDC.Network.SyncOrchestrator": "Information",
"ZB.MOM.WW.CBDDC.Core.Storage.OplogCoordinator": "Warning",
"ZB.MOM.WW.CBDDC.Persistence": "Warning"
}
},
"CBDDC": {
"Network": {
"TcpPort": 5001,
"UdpPort": 6000,
"AuthToken": "demo-secret-key",
"ConnectionTimeoutMs": 5000,
"RetryAttempts": 3,
"RetryDelayMs": 1000,
"LocalhostOnly": false
},
"Persistence": {
"DatabasePath": "data/cbddc.db",
"EnableWalMode": true,
"CacheSizeMb": 50,
"EnableAutoBackup": true,
"BackupPath": "backups/",
"BusyTimeoutMs": 5000
},
"Sync": {
"SyncIntervalMs": 5000,
"BatchSize": 100,
"EnableOfflineQueue": true,
"MaxQueueSize": 1000
},
"Logging": {
"LogLevel": "Information",
"LogFilePath": "logs/cbddc.log",
"MaxLogFileSizeMb": 10,
"MaxLogFiles": 5
},
"KnownPeers": [
{
"NodeId": "AspNetSampleNode",
"Host": "localhost",
"Port": 6001
}
]
}
}