SoftDeleteRecovery source
This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at source.md, and llms.txt lists every page.The source of the SoftDeleteRecovery sample.
Program.cs
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Orleans.Hosting;
using Orleans.Lattice;
// =============================================================================
// SoftDeleteRecovery
// -----------------------------------------------------------------------------
// Demonstrates that DeleteTreeAsync is a SOFT delete: the tree is immediately
// made inaccessible (reads and writes throw) but its data is retained for a
// configurable grace window. During that window RecoverTreeAsync brings the
// tree - and all its data - back. Contrast that with PurgeTreeAsync, which
// destroys the data immediately and irreversibly.
//
// To keep the demo fast we set a long soft-delete window (recovery must happen
// BEFORE purge) and drive purge explicitly with PurgeTreeAsync at the end.
// =============================================================================
using var host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.None);
})
.UseOrleans(silo =>
{
silo.UseLocalhostClustering();
silo.AddMemoryGrainStorageAsDefault();
silo.UseInMemoryReminderService();
silo.AddLattice((s, name) => s.AddMemoryGrainStorage(name));
// Keep the soft-delete window comfortably long so the reminder-driven
// purge never fires during this short demo. Recovery is only possible
// while the tree is inside this window.
silo.ConfigureLattice(o => o.SoftDeleteDuration = TimeSpan.FromHours(72));
})
.Build();
Console.Write("Silo starting...");
await host.StartAsync();
Console.WriteLine(" ready.");
Console.WriteLine();
var grainFactory = host.Services.GetRequiredService<IGrainFactory>();
var tree = grainFactory.GetGrain<ILattice>("customers");
static byte[] Value(string s) => Encoding.UTF8.GetBytes(s);
static string Text(byte[] b) => Encoding.UTF8.GetString(b);
// Try a read and report whether the tree is currently accessible.
static async Task<bool> IsAccessibleAsync(ILattice tree)
{
try
{
await tree.GetAsync("customer:0");
return true;
}
catch (InvalidOperationException)
{
return false;
}
}
// --- Step 1: populate the tree ---------------------------------------------
const int keyCount = 5;
Console.WriteLine($"Seeding tree 'customers' with {keyCount} keys...");
for (var i = 0; i < keyCount; i++)
{
await tree.SetAsync($"customer:{i}", Value($"name-{i}"));
}
Console.WriteLine($" accessible = {await IsAccessibleAsync(tree)}, count = {await tree.CountAsync()}");
Console.WriteLine();
// --- Step 2: soft-delete ----------------------------------------------------
// The tree is marked deleted on every shard. Reads/writes now throw
// InvalidOperationException immediately, but the data still exists in storage.
Console.WriteLine("Soft-deleting the tree (DeleteTreeAsync)...");
await tree.DeleteTreeAsync();
var accessibleAfterDelete = await IsAccessibleAsync(tree);
Console.WriteLine($" accessible after delete = {accessibleAfterDelete} (expected False)");
Console.Write(" attempting a read... ");
try
{
await tree.GetAsync("customer:0");
Console.WriteLine("unexpectedly succeeded");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"blocked: {ex.Message}");
}
Console.WriteLine();
// --- Step 3: recover within the window --------------------------------------
// Because we are still inside the soft-delete window (nothing has been purged),
// RecoverTreeAsync restores the tree with all its original data intact.
Console.WriteLine("Recovering the tree (RecoverTreeAsync)...");
await tree.RecoverTreeAsync();
var accessibleAfterRecover = await IsAccessibleAsync(tree);
var recoveredCount = await tree.CountAsync();
var sample = await tree.GetAsync("customer:3");
Console.WriteLine($" accessible after recover = {accessibleAfterRecover} (expected True)");
Console.WriteLine($" count after recover = {recoveredCount} (expected {keyCount})");
Console.WriteLine($" customer:3 = \"{Text(sample!)}\" (data intact)");
Console.WriteLine();
// --- Step 4: contrast with purge (permanent) --------------------------------
// PurgeTreeAsync bypasses the grace window and destroys the data now. After a
// delete+purge, recovery is impossible - the tree is gone for good.
Console.WriteLine("Now permanently destroying the tree (DeleteTreeAsync + PurgeTreeAsync)...");
await tree.DeleteTreeAsync();
await tree.PurgeTreeAsync();
Console.Write(" attempting RecoverTreeAsync after purge... ");
try
{
await tree.RecoverTreeAsync();
Console.WriteLine("unexpectedly succeeded");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"refused: {ex.Message}");
}
Console.WriteLine();
Console.WriteLine("Done: soft-delete blocked access and was reversible; purge was permanent.");
await host.StopAsync();
SoftDeleteRecovery.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Orleans.Lattice.Samples.SoftDeleteRecovery</RootNamespace>
<AssemblyName>Orleans.Lattice.Samples.SoftDeleteRecovery</AssemblyName>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.11" />
<PackageReference Include="Microsoft.Orleans.Server" Version="10.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\lattice\Orleans.Lattice.csproj" />
</ItemGroup>
</Project>