![]() |
VOOZH | about |
dotnet add package Rystem.RepositoryFramework.Infrastructure.InMemory --version 10.0.8
NuGet\Install-Package Rystem.RepositoryFramework.Infrastructure.InMemory -Version 10.0.8
<PackageReference Include="Rystem.RepositoryFramework.Infrastructure.InMemory" Version="10.0.8" />
<PackageVersion Include="Rystem.RepositoryFramework.Infrastructure.InMemory" Version="10.0.8" />Directory.Packages.props
<PackageReference Include="Rystem.RepositoryFramework.Infrastructure.InMemory" />Project file
paket add Rystem.RepositoryFramework.Infrastructure.InMemory --version 10.0.8
#r "nuget: Rystem.RepositoryFramework.Infrastructure.InMemory, 10.0.8"
#:package Rystem.RepositoryFramework.Infrastructure.InMemory@10.0.8
#addin nuget:?package=Rystem.RepositoryFramework.Infrastructure.InMemory&version=10.0.8Install as a Cake Addin
#tool nuget:?package=Rystem.RepositoryFramework.Infrastructure.InMemory&version=10.0.8Install as a Cake Tool
In-memory storage for the Rystem repository ecosystem, designed for local development, sample apps, integration tests, random data population, and resilience simulations.
This package sits on top of Rystem.RepositoryFramework.Abstractions and is usually the easiest concrete provider to start with when you want repository behavior without a real external database.
dotnet add package Rystem.RepositoryFramework.Infrastructure.InMemory
The current package metadata in src/Repository/RepositoryFramework.Infrastructure.InMemory/RepositoryFramework.Infrastructure.InMemory.csproj is:
Rystem.RepositoryFramework.Infrastructure.InMemory10.0.6net10.0| Area | Purpose |
|---|---|
WithInMemory(...) extensions |
Register in-memory storage on repository, command, or query builders |
InMemoryStorage<T, TKey> |
Concrete storage implementation used by the registration extensions |
IRepositoryInMemoryBuilder<T, TKey> |
Builder used for seeding and behavior configuration |
RepositoryBehaviorSettings<T, TKey> |
Per-method latency and exception configuration |
MethodBehaviorSetting / ExceptionOdds |
Delay ranges and simulated failure probabilities |
| Warm-up population hooks | Deferred seeding from random generation, injected data, or JSON |
System.Population.Randombuilder.Services.AddRepository<User, string>(repositoryBuilder =>
{
repositoryBuilder.WithInMemory();
});
var app = builder.Build();
await app.Services.WarmUpAsync();
app.Run();
WithInMemory() is available on:
IRepositoryBuilder<T, TKey>ICommandBuilder<T, TKey>IQueryBuilder<T, TKey>WarmUpAsync() is only required when you use the population helpers, but many repository-based apps already call it during startup for consistency across providers.
All three overloads share the same signature and the same defaults.
.WithInMemory(
Action<IRepositoryInMemoryBuilder<T, TKey>>? inMemoryBuilder = null,
string? name = null,
ServiceLifetime lifetime = ServiceLifetime.Singleton)
| Parameter | Default | Description |
|---|---|---|
inMemoryBuilder |
null |
Optional callback for seeding and behavior settings |
name |
null |
Named factory key used by DI and warm-up resolution |
lifetime |
Singleton |
DI lifetime for the registered storage service |
This provider uses SetStorageAndBuildOptions(...) from the abstractions layer under the hood.
InMemoryStorage<T, TKey> stores data in a static ConcurrentDictionary<string, Entity<T, TKey>> keyed by KeySettings<TKey>.AsString(key).
That has a few practical consequences:
T and TKeyGetAsync and QueryAsyncNamed registrations help you resolve different DI entries, but they do not create isolated backing stores for the same T and TKey pair.
For example, these two registrations resolve separately through IFactory<IRepository<User, string>>, but they still share the same underlying static dictionary:
builder.Services.AddRepository<User, string>(repositoryBuilder =>
{
repositoryBuilder.WithInMemory(name: "default");
repositoryBuilder.WithInMemory(name: "secondary");
});
So in this provider, name is primarily a DI-selection and warm-up-routing concept, not a data-partitioning mechanism.
The in-memory builder exposes three seeding styles:
PopulateWithRandomData(...)PopulateWithDataInjection(...)PopulateWithJsonData(...)These methods do not insert data immediately during service registration. They register warm-up actions, and those actions run when WarmUpAsync() is called on the built service provider.
PopulateWithRandomData(...) uses System.Population.Random and returns IPopulationBuilder<Entity<T, TKey>>, which is why the lambda examples target x.Value and x.Key.
builder.Services.AddRepository<NonPlusSuperUser, NonPlusSuperUserKey>(repositoryBuilder =>
{
repositoryBuilder.WithInMemory(inMemoryBuilder =>
{
inMemoryBuilder
.PopulateWithRandomData(120, 5)
.WithPattern(x => x.Value!.Email, @"[a-z]{5,10}@gmail\.com");
});
});
var app = builder.Build();
await app.Services.WarmUpAsync();
The two numeric parameters are:
numberOfElements: how many root entities to generatenumberOfElementsWhenEnumerableIsFound: how many items to generate for enumerable membersBecause random population returns an IPopulationBuilder<Entity<T, TKey>>, you can use the population features from the core ecosystem.
Real tests in the repository use helpers such as:
WithPattern(...)WithImplementation(...)WithValue(...)WithAutoIncrement(...)repositoryBuilder.WithInMemory(inMemoryBuilder =>
{
inMemoryBuilder
.PopulateWithRandomData(90, 8)
.WithAutoIncrement(x => x.Value!.Id, 0)
.WithPattern(x => x.Value!.Email, @"[a-z]{5,10}@gmail\.com")
.WithImplementation<IInnerInterface, MyInnerInterfaceImplementation>(x => x.Value!.Inner!)
.WithValue(x => x.Value!.Enabled, () => true);
});
Use PopulateWithDataInjection(...) when you already have a list of entities.
var users = new List<User>
{
new() { Id = "alice", Name = "Alice" },
new() { Id = "bob", Name = "Bob" }
};
builder.Services.AddRepository<User, string>(repositoryBuilder =>
{
repositoryBuilder.WithInMemory(inMemoryBuilder =>
{
inMemoryBuilder.PopulateWithDataInjection(x => x.Id, users);
});
});
The key selector should point to a property that can be read from each entity.
Use PopulateWithJsonData(...) when the source is a JSON array of T.
var json = """
[
{ "Id": "alice", "Name": "Alice" },
{ "Id": "bob", "Name": "Bob" }
]
""";
builder.Services.AddRepository<User, string>(repositoryBuilder =>
{
repositoryBuilder.WithInMemory(inMemoryBuilder =>
{
inMemoryBuilder.PopulateWithJsonData(x => x.Id, json);
});
});
If the JSON cannot be deserialized into IEnumerable<T>, the builder simply leaves the store unchanged.
PopulateWithRandomData(...), PopulateWithDataInjection(...), and PopulateWithJsonData(...) all seed through warm-up actionsname is providedWithInMemory() without population does not need warm-up to function, because InMemoryStorage<T, TKey>.BootstrapAsync() is currently a no-opOnce registered, the provider behaves like any other repository implementation from the consumer side.
var result = await repository.InsertAsync(1, new Animal { Id = 1, Name = "Eagle" });
var item = await repository.GetAsync(1);
var exists = await repository.ExistAsync(1);
var page = await repository
.Where(x => x.Id > 0)
.OrderByDescending(x => x.Id)
.PageAsync(1, 2);
var batch = repository.CreateBatchOperation();
for (var i = 0; i < 10; i++)
batch.AddInsert(i, new Animal { Id = i, Name = i.ToString() });
await batch.ExecuteAsync().ToListAsync();
This exact style is covered by the in-memory all-methods tests in the repository.
The provider can simulate latency and failure probability per repository method family through RepositoryBehaviorSettings<T, TKey>.
builder.Services.AddRepository<User, string>(repositoryBuilder =>
{
repositoryBuilder.WithInMemory(inMemoryBuilder =>
{
inMemoryBuilder.Settings.AddForCommandPattern(new MethodBehaviorSetting
{
MillisecondsOfWait = new Range(50, 200)
});
inMemoryBuilder.Settings.AddForQueryPattern(new MethodBehaviorSetting
{
MillisecondsOfWait = new Range(5, 20)
});
});
});
| Method | Intended scope |
|---|---|
AddForRepositoryPattern(setting) |
All methods through RepositoryMethods.All fallback |
AddForCommandPattern(setting) |
Insert, Update, Delete, Batch |
AddForQueryPattern(setting) |
Get, Query, Exist, Operation |
AddForInsert(setting) |
Insert |
AddForUpdate(setting) |
Update |
AddForDelete(setting) |
Delete |
AddForBatch(setting) |
Batch |
AddForGet(setting) |
Get |
AddForQuery(setting) |
Query |
AddForExist(setting) |
Exist |
AddForCount(setting) |
Operation |
MethodBehaviorSetting| Property | Type | Meaning |
|---|---|---|
MillisecondsOfWait |
Range |
Inclusive random delay added before the operation |
MillisecondsOfWaitWhenException |
Range |
Additional delay added on the simulated-failure path |
ExceptionOdds |
List<ExceptionOdds> |
Candidate exceptions and their probabilities |
ExceptionOdds| Property | Type | Meaning |
|---|---|---|
Percentage |
double |
Probability from > 0 to 100 |
Exception |
Exception? |
Exception object associated with that probability |
Before the options are finalized, the builder validates the configured percentages:
0 and less than or equal to 100100Invalid configurations throw during options building, not later at query time.
The provider does not simulate failures in the exact same way for every method.
These methods throw the configured simulated exception when the probability path is hit:
GetAsyncQueryAsyncOperationAsyncThese methods do not throw the configured exception. Instead, the simulated failure path returns a failed State<T, TKey>:
InsertAsyncUpdateAsyncDeleteAsyncExistAsyncSo for writes and ExistAsync, you should check state.IsOk rather than expect an exception.
BatchAsync executes insert/update/delete operations one by one and yields a result for each operation. It is not transactional and does not roll back previous operations.
Also, as currently implemented, BatchAsync does not read the dedicated RepositoryMethods.Batch behavior setting directly. It inherits behavior from the individual insert/update/delete calls it performs.
That means AddForBatch(...) exists in the options API, but it is not consumed by InMemoryStorage<T, TKey> today.
The same extension exists for command-only and query-only registrations.
builder.Services.AddCommand<Order, Guid>(commandBuilder =>
{
commandBuilder.WithInMemory();
});
builder.Services.AddQuery<Product, int>(queryBuilder =>
{
queryBuilder.WithInMemory(inMemoryBuilder =>
{
inMemoryBuilder.PopulateWithRandomData(50, 3);
});
});
Because the concrete in-memory storage implements the full repository contract internally, warm-up seeding still works even when the public DI surface is query-only or command-only.
The sample API under src/Repository/RepositoryFramework.Test/RepositoryFramework.WebApi/Program.cs registers repositories like this:
builder.Services.AddRepository<SuperUser, string>(repositoryBuilder =>
{
repositoryBuilder.WithInMemory(inMemoryBuilder =>
{
inMemoryBuilder
.PopulateWithRandomData(120, 5)
.WithPattern(x => x.Value!.Email, @"[a-z]{5,10}@gmail\.com");
});
});
var app = builder.Build();
await app.Services.WarmUpAsync();
The population tests configure different delays for reads and writes:
repositoryBuilder.WithInMemory(inMemoryBuilder =>
{
inMemoryBuilder.Settings.AddForCommandPattern(new MethodBehaviorSetting
{
MillisecondsOfWait = new Range(100, 250)
});
inMemoryBuilder.Settings.AddForQueryPattern(new MethodBehaviorSetting
{
MillisecondsOfWait = new Range(10, 40)
});
});
The exception tests configure a full distribution across several exceptions:
repositoryBuilder.WithInMemory(inMemoryBuilder =>
{
inMemoryBuilder.Settings.AddForRepositoryPattern(new MethodBehaviorSetting
{
ExceptionOdds = new List<ExceptionOdds>
{
new() { Exception = new Exception("Normal Exception"), Percentage = 10.352 },
new() { Exception = new Exception("Big Exception"), Percentage = 49.1 },
new() { Exception = new Exception("Great Exception"), Percentage = 40.548 }
}
});
});
| Package | Purpose |
|---|---|
Rystem.RepositoryFramework.Abstractions |
Core contracts, query model, and registration builders |
Rystem.RepositoryFramework.Api.Server |
Expose repositories as HTTP endpoints |
Rystem.RepositoryFramework.Api.Client |
Call repository APIs from .NET or TypeScript |
If you are continuing through the repository area, this is usually the next package to understand after src/Repository/RepositoryFramework.Abstractions/README.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.8 | 5,533 | 5/13/2026 |
| 10.0.7 | 128 | 3/26/2026 |
| 10.0.6 | 433,504 | 3/3/2026 |
| 10.0.5 | 132 | 2/22/2026 |
| 10.0.4 | 129 | 2/9/2026 |
| 10.0.3 | 147,931 | 1/28/2026 |
| 10.0.1 | 209,140 | 11/12/2025 |
| 9.1.3 | 317 | 9/2/2025 |
| 9.1.2 | 764,501 | 5/29/2025 |
| 9.1.1 | 97,880 | 5/2/2025 |
| 9.0.32 | 186,788 | 4/15/2025 |
| 9.0.31 | 5,898 | 4/2/2025 |
| 9.0.30 | 88,874 | 3/26/2025 |
| 9.0.29 | 9,070 | 3/18/2025 |
| 9.0.28 | 285 | 3/17/2025 |
| 9.0.27 | 311 | 3/16/2025 |
| 9.0.26 | 304 | 3/13/2025 |
| 9.0.25 | 52,171 | 3/9/2025 |
| 9.0.21 | 342 | 3/6/2025 |
| 9.0.20 | 19,596 | 3/6/2025 |