Harden Surreal migration with retry/coverage fixes and XML docs cleanup
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m17s
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m17s
This commit is contained in:
@@ -124,7 +124,9 @@ public class ConsoleInteractiveService : BackgroundService
|
||||
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),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Name = $"User-{ts}",
|
||||
Age = new Random().Next(18, 90),
|
||||
Address = new Address { City = "AutoCity" }
|
||||
};
|
||||
await _db.Users.InsertAsync(user);
|
||||
@@ -138,7 +140,9 @@ public class ConsoleInteractiveService : BackgroundService
|
||||
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),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Name = $"User-{ts}",
|
||||
Age = new Random().Next(18, 90),
|
||||
Address = new Address { City = "SpamCity" }
|
||||
};
|
||||
await _db.Users.InsertAsync(user);
|
||||
@@ -158,9 +162,9 @@ public class ConsoleInteractiveService : BackgroundService
|
||||
else if (input.StartsWith("p"))
|
||||
{
|
||||
var alice = new User
|
||||
{ Id = Guid.NewGuid().ToString(), Name = "Alice", Age = 30, Address = new Address { City = "Paris" } };
|
||||
{ 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" } };
|
||||
{ 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();
|
||||
|
||||
@@ -14,6 +14,11 @@ public class SampleDbContext : IDisposable
|
||||
|
||||
private readonly bool _ownsClient;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SampleDbContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="surrealEmbeddedClient">The embedded SurrealDB client.</param>
|
||||
/// <param name="schemaInitializer">The schema initializer.</param>
|
||||
public SampleDbContext(
|
||||
ICBDDCSurrealEmbeddedClient surrealEmbeddedClient,
|
||||
ICBDDCSurrealSchemaInitializer schemaInitializer)
|
||||
@@ -29,6 +34,10 @@ public class SampleDbContext : IDisposable
|
||||
SchemaInitializer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SampleDbContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="databasePath">The database path used for the embedded store.</param>
|
||||
public SampleDbContext(string databasePath)
|
||||
{
|
||||
string normalizedPath = NormalizeDatabasePath(databasePath);
|
||||
@@ -54,21 +63,41 @@ public class SampleDbContext : IDisposable
|
||||
SchemaInitializer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the embedded SurrealDB client.
|
||||
/// </summary>
|
||||
public ICBDDCSurrealEmbeddedClient SurrealEmbeddedClient { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the schema initializer.
|
||||
/// </summary>
|
||||
public ICBDDCSurrealSchemaInitializer SchemaInitializer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the users collection.
|
||||
/// </summary>
|
||||
public SampleSurrealCollection<User> Users { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the todo lists collection.
|
||||
/// </summary>
|
||||
public SampleSurrealCollection<TodoList> TodoLists { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the operation log entries collection.
|
||||
/// </summary>
|
||||
public SampleSurrealReadOnlyCollection<SampleOplogEntry> OplogEntries { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ensures schema changes are applied before persisting updates.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token.</param>
|
||||
public async Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await SchemaInitializer.EnsureInitializedAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Users.Dispose();
|
||||
@@ -101,11 +130,16 @@ public sealed class SampleSurrealSchemaInitializer : ICBDDCSurrealSchemaInitiali
|
||||
private readonly ICBDDCSurrealEmbeddedClient _client;
|
||||
private int _initialized;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SampleSurrealSchemaInitializer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="client">The embedded SurrealDB client.</param>
|
||||
public SampleSurrealSchemaInitializer(ICBDDCSurrealEmbeddedClient client)
|
||||
{
|
||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task EnsureInitializedAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (Volatile.Read(ref _initialized) == 1) return;
|
||||
@@ -124,6 +158,13 @@ public sealed class SampleSurrealCollection<TEntity> : ISurrealWatchableCollecti
|
||||
private readonly ICBDDCSurrealSchemaInitializer _schemaInitializer;
|
||||
private readonly string _tableName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SampleSurrealCollection{TEntity}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tableName">The backing table name.</param>
|
||||
/// <param name="keySelector">The key selector for entities.</param>
|
||||
/// <param name="surrealEmbeddedClient">The embedded SurrealDB client.</param>
|
||||
/// <param name="schemaInitializer">The schema initializer.</param>
|
||||
public SampleSurrealCollection(
|
||||
string tableName,
|
||||
Func<TEntity, string> keySelector,
|
||||
@@ -139,21 +180,25 @@ public sealed class SampleSurrealCollection<TEntity> : ISurrealWatchableCollecti
|
||||
_schemaInitializer = schemaInitializer ?? throw new ArgumentNullException(nameof(schemaInitializer));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable Subscribe(IObserver<SurrealCollectionChange<TEntity>> observer)
|
||||
{
|
||||
return _changeFeed.Subscribe(observer);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await UpsertAsync(entity, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await UpsertAsync(entity, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteAsync(string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
@@ -164,11 +209,22 @@ public sealed class SampleSurrealCollection<TEntity> : ISurrealWatchableCollecti
|
||||
_changeFeed.PublishDelete(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an entity by identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The entity identifier.</param>
|
||||
/// <returns>The matching entity when found; otherwise <see langword="null"/>.</returns>
|
||||
public TEntity? FindById(string id)
|
||||
{
|
||||
return FindByIdAsync(id).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an entity by identifier asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="id">The entity identifier.</param>
|
||||
/// <param name="cancellationToken">A cancellation token.</param>
|
||||
/// <returns>The matching entity when found; otherwise <see langword="null"/>.</returns>
|
||||
public async Task<TEntity?> FindByIdAsync(string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
@@ -179,11 +235,13 @@ public sealed class SampleSurrealCollection<TEntity> : ISurrealWatchableCollecti
|
||||
return record?.Entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TEntity> FindAll()
|
||||
{
|
||||
return FindAllAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<TEntity>> FindAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await EnsureReadyAsync(cancellationToken);
|
||||
@@ -195,12 +253,14 @@ public sealed class SampleSurrealCollection<TEntity> : ISurrealWatchableCollecti
|
||||
?? [];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TEntity> Find(Func<TEntity, bool> predicate)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(predicate);
|
||||
return FindAll().Where(predicate);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_changeFeed.Dispose();
|
||||
@@ -235,6 +295,12 @@ public sealed class SampleSurrealReadOnlyCollection<TEntity>
|
||||
private readonly ICBDDCSurrealSchemaInitializer _schemaInitializer;
|
||||
private readonly string _tableName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SampleSurrealReadOnlyCollection{TEntity}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tableName">The backing table name.</param>
|
||||
/// <param name="surrealEmbeddedClient">The embedded SurrealDB client.</param>
|
||||
/// <param name="schemaInitializer">The schema initializer.</param>
|
||||
public SampleSurrealReadOnlyCollection(
|
||||
string tableName,
|
||||
ICBDDCSurrealEmbeddedClient surrealEmbeddedClient,
|
||||
@@ -248,11 +314,20 @@ public sealed class SampleSurrealReadOnlyCollection<TEntity>
|
||||
_schemaInitializer = schemaInitializer ?? throw new ArgumentNullException(nameof(schemaInitializer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all entities from the collection.
|
||||
/// </summary>
|
||||
/// <returns>The entities in the collection.</returns>
|
||||
public IEnumerable<TEntity> FindAll()
|
||||
{
|
||||
return FindAllAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all entities from the collection asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token.</param>
|
||||
/// <returns>The entities in the collection.</returns>
|
||||
public async Task<IReadOnlyList<TEntity>> FindAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _schemaInitializer.EnsureInitializedAsync(cancellationToken);
|
||||
@@ -260,6 +335,11 @@ public sealed class SampleSurrealReadOnlyCollection<TEntity>
|
||||
return rows?.ToList() ?? [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns entities that match the provided predicate.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate used to filter entities.</param>
|
||||
/// <returns>The entities that satisfy the predicate.</returns>
|
||||
public IEnumerable<TEntity> Find(Func<TEntity, bool> predicate)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(predicate);
|
||||
@@ -270,30 +350,54 @@ public sealed class SampleSurrealReadOnlyCollection<TEntity>
|
||||
public sealed class SampleEntityRecord<TEntity> : Record
|
||||
where TEntity : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the stored entity payload.
|
||||
/// </summary>
|
||||
[JsonPropertyName("entity")]
|
||||
public TEntity? Entity { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SampleOplogEntry : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the collection name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("collection")]
|
||||
public string Collection { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the entity key.
|
||||
/// </summary>
|
||||
[JsonPropertyName("key")]
|
||||
public string Key { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the operation code.
|
||||
/// </summary>
|
||||
[JsonPropertyName("operation")]
|
||||
public int Operation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the node identifier portion of the timestamp.
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestampNodeId")]
|
||||
public string TimestampNodeId { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the physical time portion of the timestamp.
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestampPhysicalTime")]
|
||||
public long TimestampPhysicalTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logical counter portion of the timestamp.
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestampLogicalCounter")]
|
||||
public int TimestampLogicalCounter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the hash for the operation entry.
|
||||
/// </summary>
|
||||
[JsonPropertyName("hash")]
|
||||
public string Hash { get; set; } = "";
|
||||
}
|
||||
|
||||
@@ -15,6 +15,13 @@ public class SampleDocumentStore : SurrealDocumentStore<SampleDbContext>
|
||||
private const string UsersCollection = "Users";
|
||||
private const string TodoListsCollection = "TodoLists";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SampleDocumentStore"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">Sample database context.</param>
|
||||
/// <param name="configProvider">Peer configuration provider.</param>
|
||||
/// <param name="vectorClockService">Vector clock service.</param>
|
||||
/// <param name="logger">Optional logger.</param>
|
||||
public SampleDocumentStore(
|
||||
SampleDbContext context,
|
||||
IPeerNodeConfigurationProvider configProvider,
|
||||
@@ -35,6 +42,7 @@ public class SampleDocumentStore : SurrealDocumentStore<SampleDbContext>
|
||||
WatchCollection(TodoListsCollection, context.TodoLists, t => t.Id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task ApplyContentToEntityAsync(
|
||||
string collection,
|
||||
string key,
|
||||
@@ -44,6 +52,7 @@ public class SampleDocumentStore : SurrealDocumentStore<SampleDbContext>
|
||||
await UpsertEntityAsync(collection, key, content, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task ApplyContentToEntitiesBatchAsync(
|
||||
IEnumerable<(string Collection, string Key, JsonElement Content)> documents,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -52,6 +61,7 @@ public class SampleDocumentStore : SurrealDocumentStore<SampleDbContext>
|
||||
await UpsertEntityAsync(collection, key, content, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<JsonElement?> GetEntityAsJsonAsync(
|
||||
string collection,
|
||||
string key,
|
||||
@@ -65,6 +75,7 @@ public class SampleDocumentStore : SurrealDocumentStore<SampleDbContext>
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task RemoveEntityAsync(
|
||||
string collection,
|
||||
string key,
|
||||
@@ -73,6 +84,7 @@ public class SampleDocumentStore : SurrealDocumentStore<SampleDbContext>
|
||||
await DeleteEntityAsync(collection, key, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task RemoveEntitiesBatchAsync(
|
||||
IEnumerable<(string Collection, string Key)> documents,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -81,6 +93,7 @@ public class SampleDocumentStore : SurrealDocumentStore<SampleDbContext>
|
||||
await DeleteEntityAsync(collection, key, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<IEnumerable<(string Key, JsonElement Content)>> GetAllEntitiesAsJsonAsync(
|
||||
string collection,
|
||||
CancellationToken cancellationToken)
|
||||
|
||||
Reference in New Issue
Block a user