Rystem.RepositoryFramework.Abstractions 10.0.7

dotnet add package Rystem.RepositoryFramework.Abstractions --version 10.0.7
                    
NuGet\Install-Package Rystem.RepositoryFramework.Abstractions -Version 10.0.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Rystem.RepositoryFramework.Abstractions" Version="10.0.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Rystem.RepositoryFramework.Abstractions" Version="10.0.7" />
                    
Directory.Packages.props
<PackageReference Include="Rystem.RepositoryFramework.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Rystem.RepositoryFramework.Abstractions --version 10.0.7
                    
#r "nuget: Rystem.RepositoryFramework.Abstractions, 10.0.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Rystem.RepositoryFramework.Abstractions@10.0.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Rystem.RepositoryFramework.Abstractions&version=10.0.7
                    
Install as a Cake Addin
#tool nuget:?package=Rystem.RepositoryFramework.Abstractions&version=10.0.7
                    
Install as a Cake Tool

What is Rystem?

Rystem.RepositoryFramework.Abstractions

NuGet NuGet Downloads

Core contracts, query primitives, registration builders, and runtime metadata for the Rystem repository ecosystem.

This is the foundation for the whole Repository Framework area documented in src/Repository/README.md.

It defines:

  • repository, command, and query contracts
  • DI registration builders for repository and CQRS setups
  • key abstractions and key serialization rules
  • query/filter primitives used by every storage provider
  • business hooks and translation builders
  • runtime metadata used by the API packages and diagnostics

Resources


Installation

dotnet add package Rystem.RepositoryFramework.Abstractions

The current package metadata in src/Repository/RepositoryFramework.Abstractions/RepositoryFramework.Abstractions.csproj is:

  • package id: Rystem.RepositoryFramework.Abstractions
  • version: 10.0.6
  • target framework: net10.0

This package builds on top of Rystem.DependencyInjection.


Package architecture

At a high level, this package is organized around these areas.

Area Purpose
Pattern interfaces Low-level storage contracts such as IRepositoryPattern<T, TKey>
Consumer interfaces DI-facing contracts such as IRepository<T, TKey>
Service registration builders AddRepository, AddCommand, AddQuery, and their async variants
Query model QueryBuilder<T, TKey>, IFilterExpression, paging, aggregate operations
Keys and state models IKey, IDefaultKey, Key<T1,...>, Entity<T, TKey>, State<T, TKey>
Business and translation hooks interceptors, examples, exposure rules, filter translation
Runtime metadata RepositoryFrameworkRegistry and RepositoryFrameworkService

What this package provides

  • Core storage contracts: IRepositoryPattern<T, TKey>, ICommandPattern<T, TKey>, IQueryPattern<T, TKey>
  • Consumer contracts for DI: IRepository<T, TKey>, ICommand<T, TKey>, IQuery<T, TKey>
  • Fluent registration builders: AddRepository, AddCommand, AddQuery, plus async variants
  • Storage wiring primitives: SetStorage, SetStorageWithOptions, SetStorageAndBuildOptions, SetStorageAndBuildOptionsAsync, SetStorageAndServiceConnection
  • Key helpers: IKey, IDefaultKey, Key<T1> through Key<T1, T2, T3, T4, T5>, and KeySettings<TKey>
  • Query/filter model: QueryBuilder<T, TKey>, FilterExpression, SerializableFilter, metadata, paging, and aggregate operations
  • Business hooks and translation builders for cross-cutting logic and model remapping
  • Runtime registry data used by diagnostics and API exposure packages

Mental model

This package does not ship a database provider by itself. Instead, it gives you a stable abstraction layer for three concerns:

  1. Implement storage behavior behind repository contracts.
  2. Register those implementations in DI, optionally with named factories.
  3. Reuse the same query/filter/key model across in-memory, EF, blob, table, Cosmos, API client, and custom providers.

Most other packages in src/Repository are adapters built on top of these abstractions.


Core interfaces

Implementation interfaces

Implement one of these in your storage class.

// Full repository (read + write)
public interface IRepositoryPattern<T, TKey> : ICommandPattern<T, TKey>, IQueryPattern<T, TKey>
    where TKey : notnull { }

// Write only
public interface ICommandPattern<T, TKey>
    where TKey : notnull
{
    Task<State<T, TKey>> InsertAsync(TKey key, T value, CancellationToken cancellationToken = default);
    Task<State<T, TKey>> UpdateAsync(TKey key, T value, CancellationToken cancellationToken = default);
    Task<State<T, TKey>> DeleteAsync(TKey key, CancellationToken cancellationToken = default);
    IAsyncEnumerable<BatchResult<T, TKey>> BatchAsync(BatchOperations<T, TKey> operations, CancellationToken cancellationToken = default);
}

// Read only
public interface IQueryPattern<T, TKey>
    where TKey : notnull
{
    Task<State<T, TKey>> ExistAsync(TKey key, CancellationToken cancellationToken = default);
    Task<T?> GetAsync(TKey key, CancellationToken cancellationToken = default);
    IAsyncEnumerable<Entity<T, TKey>> QueryAsync(IFilterExpression filter, CancellationToken cancellationToken = default);
    ValueTask<TProperty> OperationAsync<TProperty>(OperationType<TProperty> operation, IFilterExpression filter, CancellationToken cancellationToken = default);
}

Both query and command abstractions also inherit the non-generic bootstrap contract through their base interfaces, so storage implementations can participate in warm-up/startup bootstrap logic.

Consumer interfaces

Inject these into application services instead of the pattern interfaces directly.

IRepository<T, TKey>   // read + write + query extensions
ICommand<T, TKey>      // write only
IQuery<T, TKey>        // read only

The consumer interfaces are what the registration builders expose through DI and named factories.


Basic registration

Repository (read + write)

builder.Services.AddRepository<AppUser, Guid>(repositoryBuilder =>
{
    repositoryBuilder.SetStorage<AppUserStorage>();
});

CQRS split

builder.Services.AddCommand<AppUser, Guid>(commandBuilder =>
{
    commandBuilder.SetStorage<AppUserCommandStorage>();
});

builder.Services.AddQuery<AppUser, Guid>(queryBuilder =>
{
    queryBuilder.SetStorage<AppUserQueryStorage>();
});

Async registration

Use async variants when the builder itself needs asynchronous setup.

await builder.Services.AddRepositoryAsync<AppUser, Guid>(async repositoryBuilder =>
{
    repositoryBuilder.SetStorage<AppUserStorage>();
    await Task.CompletedTask;
});

await builder.Services.AddCommandAsync<AppUser, Guid>(async commandBuilder =>
{
    commandBuilder.SetStorage<AppUserCommandStorage>();
    await Task.CompletedTask;
});

await builder.Services.AddQueryAsync<AppUser, Guid>(async queryBuilder =>
{
    queryBuilder.SetStorage<AppUserQueryStorage>();
    await Task.CompletedTask;
});

Storage registration primitives

All repository builders eventually flow through the storage registration methods in RepositoryBaseBuilder.

SetStorage<TStorage>()

Use this when the storage can be resolved directly from DI.

builder.Services.AddRepository<AppUser, Guid>(repositoryBuilder =>
{
    repositoryBuilder.SetStorage<AppUserStorage>();
});

SetStorageWithOptions<TStorage, TStorageOptions>()

Use this when the storage consumes a simple options object implementing IFactoryOptions.

builder.Services.AddRepository<AppUser, Guid>(repositoryBuilder =>
{
    repositoryBuilder.SetStorageWithOptions<AppUserStorage, AppUserStorageOptions>(options =>
    {
        options.ConnectionString = builder.Configuration["ConnectionStrings:Default"]!;
    });
});

SetStorageAndBuildOptions<TStorage, TStorageOptions, TConnection>()

Use this when the builder must transform fluent configuration into a factory-backed connection/options object.

builder.Services.AddRepository<AppUser, Guid>(repositoryBuilder =>
{
    repositoryBuilder.SetStorageAndBuildOptions<AppUserStorage, AppUserBuilder, AppUserConnection>(options =>
    {
        options.Name = "users";
    });
});

SetStorageAndBuildOptionsAsync<TStorage, TStorageOptions, TConnection>()

Same as the previous one, but the options builder can perform asynchronous work.

await builder.Services.AddRepositoryAsync<AppUser, Guid>(async repositoryBuilder =>
{
    await repositoryBuilder.SetStorageAndBuildOptionsAsync<AppUserStorage, AppUserBuilder, AppUserConnection>(options =>
    {
        options.Name = "users";
    });
});

SetStorageAndServiceConnection<TStorage, TConnectionService, TConnectionClient>()

Use this when the actual connection or client is request-aware or tenant-aware.

public sealed class TenantAwareConnectionService : IConnectionService<Order, Guid, DbConnection>
{
    private readonly ITenantResolver _tenantResolver;

    public TenantAwareConnectionService(ITenantResolver tenantResolver)
        => _tenantResolver = tenantResolver;

    public DbConnection GetConnection(string entityName, string? factoryName = null)
    {
        var connectionString = _tenantResolver.GetConnectionString(entityName, factoryName);
        return new SqlConnection(connectionString);
    }
}

builder.Services.AddRepository<Order, Guid>(repositoryBuilder =>
{
    repositoryBuilder.SetStorageAndServiceConnection<OrderStorage, TenantAwareConnectionService, DbConnection>();
});

Lifetime and naming

  • All SetStorage* methods default to ServiceLifetime.Scoped.
  • Every SetStorage* overload accepts an optional name and serviceLifetime.
  • The name is stored as the repository FactoryName in the runtime registry and is how named factories resolve a specific storage.

Named registrations and factories

One model can be backed by multiple repositories or providers at the same time. The common pattern is to register each one with a name, then resolve it through IFactory<TService>.

This is heavily used in the integration tests and sample web API.

builder.Services.AddRepository<AppUser, AppUserKey>(repositoryBuilder =>
{
    repositoryBuilder.SetStorage<PrimaryAppUserStorage>(name: "primary");
    repositoryBuilder.SetStorage<ArchiveAppUserStorage>(name: "archive");
});
public sealed class AppUserImportService
{
    private readonly IFactory<IRepository<AppUser, AppUserKey>> _repositoryFactory;

    public AppUserImportService(IFactory<IRepository<AppUser, AppUserKey>> repositoryFactory)
        => _repositoryFactory = repositoryFactory;

    public Task<AppUser?> GetFromArchiveAsync(AppUserKey key, CancellationToken cancellationToken = default)
    {
        var repository = _repositoryFactory.Create("archive")!;
        return repository.GetAsync(key, cancellationToken);
    }
}

The same pattern works for:

  • IFactory<IRepository<T, TKey>>
  • IFactory<IQuery<T, TKey>>
  • IFactory<ICommand<T, TKey>>

This makes it easy to switch between environments, fallback providers, multi-tenant routing, cache layers, or migration tooling without changing the domain-facing contract.


Keys

Key handling is more flexible than the old docs suggest. KeySettings<TKey> supports several strategies.

Primitive and framework-native keys

These work out of the box:

  • primitive numeric types
  • string
  • Guid
  • DateTime
  • DateTimeOffset
  • TimeSpan
  • nint
  • nuint
builder.Services.AddRepository<AppUser, Guid>(...);
builder.Services.AddRepository<Product, int>(...);
builder.Services.AddRepository<Session, string>(...);

Composite keys with Key<T1, T2, ...>

You can use the built-in Key<> helpers for common composite-key scenarios.

builder.Services.AddRepository<Order, Key<int, string>>(repositoryBuilder =>
{
    repositoryBuilder.SetStorage<OrderStorage>();
});

var key = new Key<int, string>(42, "region-eu");
var order = await repository.GetAsync(key);

Custom keys with IKey

Implement IKey when you want full control over string serialization.

public sealed class AppUserKey : IKey
{
    public string TenantId { get; set; } = string.Empty;
    public Guid UserId { get; set; }

    public static IKey Parse(string keyAsString)
    {
        var parts = keyAsString.Split('-', 2);
        return new AppUserKey { TenantId = parts[0], UserId = Guid.Parse(parts[1]) };
    }

    public string AsString() => $"{TenantId}-{UserId}";
}

Property-based keys with IDefaultKey

Implement IDefaultKey when you want the framework to serialize all key properties using the default separator.

public sealed class OrderKey : IDefaultKey
{
    public int OrderId { get; set; }
    public string Region { get; set; } = string.Empty;
}

builder.Services.AddDefaultSeparatorForDefaultKeyInterface("$$$");

Plain POCO keys also work

If TKey is not primitive, not IKey, and not IDefaultKey, KeySettings<TKey> still supports it as long as the type exposes properties. In that case it falls back to JSON serialization.

This behavior is covered by the key tests and the class-key repository tests.

public sealed class ClassAnimalKey
{
    public string Area { get; set; } = string.Empty;
    public int Id { get; set; }
    public Guid CorrelationId { get; set; }

    public ClassAnimalKey() { }

    public ClassAnimalKey(string area, int id, Guid correlationId)
        => (Area, Id, CorrelationId) = (area, id, correlationId);
}

builder.Services.AddRepository<ClassAnimal, ClassAnimalKey>(repositoryBuilder =>
{
    repositoryBuilder.SetStorage<ClassAnimalRepository>();
});

The practical rule is simple: if the key can be converted to and from a stable string representation, the abstraction layer can carry it through providers and APIs.


Core models

State<T, TKey>

All command methods return State<T, TKey>.

public class State<T, TKey>
{
    public bool IsOk { get; set; }
    public Entity<T, TKey>? Entity { get; set; }
    public int? Code { get; set; }
    public string? Message { get; set; }
    public bool HasEntity { get; }
}

Useful factory helpers include:

return State.Ok(value, key);
return State.NotOk(value, key);
return State.Default(isOk, value, key);

return await State.OkAsTask(value, key);
return await State.NotOkAsTask(value, key);

Entity<T, TKey>

Query operations return Entity<T, TKey> so callers can keep both the key and the value.

public class Entity<T, TKey>
{
    public TKey? Key { get; set; }
    public T? Value { get; set; }
    public bool HasValue { get; }
    public bool HasKey { get; }

    public State<T, TKey> ToOkState();
    public State<T, TKey> ToNotOkState();
}

Batch models

Use BatchOperations<T, TKey> and BatchResult<T, TKey> when the provider supports grouped write operations.

var batch = new BatchOperations<AppUser, Guid>()
    .AddInsert(Guid.NewGuid(), new AppUser { Name = "Alice" })
    .AddUpdate(existingId, updatedUser)
    .AddDelete(oldId);

await foreach (var result in command.BatchAsync(batch))
{
    // result.Command
    // result.Key
    // result.State
}

The command and repository consumers also expose the fluent helper:

await repository.CreateBatchOperation()
    .AddInsert(key1, value1)
    .AddDelete(key2)
    .ExecuteAsync()
    .ToListAsync();

Fluent query builder

IQuery<T, TKey> and IRepository<T, TKey> expose extension methods that create a QueryBuilder<T, TKey>.

var items = await repository
    .Where(x => x.IsActive)
    .OrderByDescending(x => x.Price)
    .Skip(20)
    .Take(10)
    .ToListAsync();

foreach (var entity in items)
    Console.WriteLine(entity.Value!.Name);

List<Product> values = await repository
    .Where(x => x.IsActive)
    .ToListAsEntityAsync();

Main query methods

Method Description
Where(expr) Filter by entity predicate
WhereKey(expr) Filter by key predicate
Take(n) Limit results
Skip(n) Offset results
OrderBy(expr) Ascending sort
OrderByDescending(expr) Descending sort
ThenBy(expr) Secondary ascending sort
ThenByDescending(expr) Secondary descending sort
AnyAsync(expr?) Returns bool
FirstOrDefaultAsync(expr?) Returns Entity<T, TKey>?
FirstAsync(expr?) Returns Entity<T, TKey>
FirstOrDefaultByKeyAsync(expr?) Returns first entity filtered on key
FirstByKeyAsync(expr?) Returns first entity filtered on key
PageAsync(page, pageSize) Returns Page<T, TKey>
ToListAsync() Returns List<Entity<T, TKey>>
ToListAsEntityAsync() Returns List<T>
QueryAsync() Returns IAsyncEnumerable<Entity<T, TKey>>
QueryAsEntityAsync() Returns IAsyncEnumerable<T>
CountAsync() Aggregate count
SumAsync(expr) Aggregate sum
AverageAsync(expr) Aggregate average
MaxAsync(expr) Aggregate max
MinAsync(expr) Aggregate min
OperationAsync(operation) Custom aggregate operation
AddMetadata(key, value) Attach metadata to the filter

Important behavior notes

  • PageAsync validates that page >= 1 and pageSize >= 1.
  • PageAsync calculates total pages using a separate Count operation.
  • GroupByAsync is not translated into provider-native grouping. It first enumerates QueryAsync() and then groups client-side.
Page<AppUser, AppUserKey> firstPage = await repository
    .Where(x => x.Id > 0)
    .OrderByDescending(x => x.Id)
    .PageAsync(page: 1, pageSize: 20);

Filter model and custom storage integration

IFilterExpression is the portable query description that storage providers receive in QueryAsync and OperationAsync.

This is the bridge between LINQ-like consumer code and your provider implementation.

What it can do

  • carry ordered filter operations such as Where, OrderBy, Skip, and Take
  • serialize itself through Serialize() into a SerializableFilter
  • produce a stable cache/transport key through ToKey()
  • apply the filter directly to in-memory collections or queryables
  • expose filter metadata and parsed values through GetFilters(...)
  • translate mapped expressions through Translate(...)

Typical custom repository usage

For in-memory or adapter-style repositories, the simplest pattern is to let the filter apply itself.

public async IAsyncEnumerable<Entity<AppUser, AppUserKey>> QueryAsync(
    IFilterExpression filter,
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    await Task.Yield();

    foreach (var item in filter.Apply(_items))
    {
        cancellationToken.ThrowIfCancellationRequested();
        yield return Entity.Default(item, new AppUserKey(item.Id));
    }
}

Metadata and caching scenarios

AddMetadata(key, value) is useful when your provider needs extra information that is not part of the entity predicate itself, such as tenant, partition, read consistency, or a custom projection mode.

var query = repository
    .Where(x => x.IsActive)
    .AddMetadata("tenant", "eu-west")
    .AddMetadata("mode", "snapshot");

The metadata becomes part of the filter representation available to the storage layer through IFilterExpression.


Business hooks

Business interceptors let you wrap every repository operation with validation, auditing, policy checks, or side effects.

Register inline during repository setup

builder.Services.AddRepository<AppUser, Guid>(repositoryBuilder =>
{
    repositoryBuilder.SetStorage<AppUserStorage>();

    repositoryBuilder
        .AddBusiness()
        .AddBusinessBeforeInsert<AppUserValidation>()
        .AddBusinessAfterInsert<AppUserAudit>()
        .AddBusinessBeforeDelete<AppUserDeletionGuard>();
});

Register independently

builder.Services
    .AddBusinessForRepository<AppUser, Guid>()
    .AddBusinessBeforeUpdate<AppUserUpdateValidation>()
    .AddBusinessAfterUpdate<AppUserUpdateAudit>();

Scan assemblies

builder.Services.ScanBusinessForRepositoryFramework();
builder.Services.ScanBusinessForRepositoryFramework(typeof(MyAssemblyMarker).Assembly);

ScanBusinessForRepositoryFramework() inspects all before/after business interfaces for the repositories already registered in the framework registry.

Available hook families

Interface Triggered
IRepositoryBusinessBeforeInsert<T, TKey> Before InsertAsync
IRepositoryBusinessAfterInsert<T, TKey> After InsertAsync
IRepositoryBusinessBeforeUpdate<T, TKey> Before UpdateAsync
IRepositoryBusinessAfterUpdate<T, TKey> After UpdateAsync
IRepositoryBusinessBeforeDelete<T, TKey> Before DeleteAsync
IRepositoryBusinessAfterDelete<T, TKey> After DeleteAsync
IRepositoryBusinessBeforeBatch<T, TKey> Before BatchAsync
IRepositoryBusinessAfterBatch<T, TKey> After BatchAsync
IRepositoryBusinessBeforeGet<T, TKey> Before GetAsync
IRepositoryBusinessAfterGet<T, TKey> After GetAsync
IRepositoryBusinessBeforeExist<T, TKey> Before ExistAsync
IRepositoryBusinessAfterExist<T, TKey> After ExistAsync
IRepositoryBusinessBeforeQuery<T, TKey> Before QueryAsync
IRepositoryBusinessAfterQuery<T, TKey> After QueryAsync
IRepositoryBusinessBeforeOperation<T, TKey> Before OperationAsync
IRepositoryBusinessAfterOperation<T, TKey> After OperationAsync

Priority controls ordering. Lower values run first.


Translation mapper

The translation builder is what lets consumer code keep writing filters against the domain model while the provider translates them to a different storage model.

This is covered by the translation tests.

builder.Services.AddRepository<Translatable, string>(repositoryBuilder =>
{
    repositoryBuilder
        .SetStorage<TranslatableRepository>()
        .Translate<ToTranslateSomething>()
            .With(x => x.Foolish, x => x.Folle)
            .With(x => x.Id, x => x.IdccnlValidita)
            .With(x => x.CcnlId, x => x.Idccnl)
            .With(x => x.From, x => x.DataInizio)
            .With(x => x.To, x => x.DataFine)
        .AndTranslate<ToTranslateSomethingElse>()
            .With(x => x.Foolish, x => x.Folle)
            .With(x => x.Id, x => x.IdccnlValidita)
            .With(x => x.CcnlId, x => x.Idccnl);
});

Common helpers include:

  • Translate<TTranslated>()
  • With(domainProperty, translatedProperty)
  • WithKey(...)
  • WithSamePorpertiesName()
  • AndTranslate<TTranslated>()

This is especially useful for EF models, API DTOs, cache projections, or legacy schemas where names do not line up with the public domain model.


API exposure and examples

These settings matter when the repository is later surfaced through Rystem.RepositoryFramework.Api.Server.

Control exposed methods

builder.Services.AddRepository<AppUser, Guid>(repositoryBuilder =>
{
    repositoryBuilder.SetStorage<AppUserStorage>();

    repositoryBuilder.SetExposable(RepositoryMethods.All);
    repositoryBuilder.SetOnlyQueryExposable();
    repositoryBuilder.SetOnlyCommandExposable();
    repositoryBuilder.SetExposable(RepositoryMethods.Get | RepositoryMethods.Query);
    repositoryBuilder.SetNotExposable();
});

RepositoryMethods also includes Bootstrap, so the bootstrap endpoint can be included or hidden when you expose repositories as APIs.

Provide OpenAPI examples

builder.Services.AddRepository<AppUser, Guid>(repositoryBuilder =>
{
    repositoryBuilder.SetStorage<AppUserStorage>();
    repositoryBuilder.SetExamples(
        new AppUser { Name = "Alice", Email = "alice@example.com" },
        Guid.NewGuid());
});

Bootstrap and warm-up lifecycle

If a storage implementation needs startup work such as schema creation, seeding, or client warm-up, implement the bootstrap contract.

public sealed class AppUserStorage : IRepositoryPattern<AppUser, Guid>, IBootstrapPattern
{
    public async ValueTask<bool> BootstrapAsync(CancellationToken cancellationToken = default)
    {
        await EnsureTableExistsAsync(cancellationToken);
        return true;
    }

    // other IRepositoryPattern members omitted
}

Important: bootstrap does not run just because AddRepository was called.

In the real tests and sample web API, bootstrap is executed by calling WarmUpAsync() on the built service provider:

var app = builder.Build();
await app.Services.WarmUpAsync();

That is the practical startup lifecycle to document and rely on.


Post-build callbacks

You can run synchronous or asynchronous logic after the repository builder delegate finishes.

builder.Services.AddRepository<AppUser, Guid>(repositoryBuilder =>
{
    repositoryBuilder.SetStorage<AppUserStorage>();

    repositoryBuilder.AfterBuild = () =>
    {
        Console.WriteLine("AppUser repository registered.");
    };

    repositoryBuilder.AfterBuildAsync = async () =>
    {
        await ValidateSchemaAsync();
    };
});

The important detail is timing: AfterBuild and AfterBuildAsync run after the whole AddRepository or AddCommand or AddQuery builder delegate returns, not after each individual SetStorage* call.


Runtime registry

RepositoryFrameworkRegistry is registered automatically as a singleton. It tracks every repository, command, and query configured through the framework.

public sealed class RepositoryDiagnosticsService
{
    private readonly RepositoryFrameworkRegistry _registry;

    public RepositoryDiagnosticsService(RepositoryFrameworkRegistry registry)
        => _registry = registry;

    public void PrintAll()
    {
        foreach (var (_, service) in _registry.Services)
        {
            Console.WriteLine(
                $"{service.ModelType.Name} {service.Type} -> {service.ImplementationType.Name} [{service.FactoryName}]");
        }
    }
}

RepositoryFrameworkService metadata

Property Description
ModelType The domain model type
KeyType The key type
InterfaceType The DI-facing contract
ImplementationType The storage implementation
ExposedMethods API-visible methods
ServiceLifetime DI lifetime used by registration
Type Repository, Command, or Query
FactoryName Named registration key, empty when default
Policies Authorization policy names used by UI/API tooling

This registry is what later packages use to expose repositories as APIs, create documentation, or inspect configured services.


Package Purpose
Rystem.RepositoryFramework.Infrastructure.InMemory In-memory storage and test-friendly provider
Rystem.RepositoryFramework.Infrastructure.EntityFramework EF Core adapter
Rystem.RepositoryFramework.Api.Server Auto-generate REST APIs from repository registrations
Rystem.RepositoryFramework.Api.Client .NET and TypeScript client adapters

If you are continuing through the repository area, this README is the conceptual base to read before the infrastructure packages.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (14)

Showing the top 5 NuGet packages that depend on Rystem.RepositoryFramework.Abstractions:

Package Downloads
Rystem.RepositoryFramework.Api.Server

Rystem.RepositoryFramework allows you to use correctly concepts like repository pattern, CQRS and DDD. You have interfaces for your domains, auto-generated api, auto-generated HttpClient to simplify connection "api to front-end", a functionality for auto-population in memory of your models, a functionality to simulate exceptions and waiting time from external sources to improve your implementation/business test and load test.

Rystem.RepositoryFramework.Api.Client

Rystem.RepositoryFramework allows you to use correctly concepts like repository pattern, CQRS and DDD. You have interfaces for your domains, auto-generated api, auto-generated HttpClient to simplify connection "api to front-end", a functionality for auto-population in memory of your models, a functionality to simulate exceptions and waiting time from external sources to improve your implementation/business test and load test.

Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Blob

Rystem.RepositoryFramework allows you to use correctly concepts like repository pattern, CQRS and DDD. You have interfaces for your domains, auto-generated api, auto-generated HttpClient to simplify connection "api to front-end", a functionality for auto-population in memory of your models, a functionality to simulate exceptions and waiting time from external sources to improve your implementation/business test and load test.

Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table

Rystem.RepositoryFramework allows you to use correctly concepts like repository pattern, CQRS and DDD. You have interfaces for your domains, auto-generated api, auto-generated HttpClient to simplify connection "api to front-end", a functionality for auto-population in memory of your models, a functionality to simulate exceptions and waiting time from external sources to improve your implementation/business test and load test.

Rystem.RepositoryFramework.Cache

Rystem.RepositoryFramework allows you to use correctly concepts like repository pattern, CQRS and DDD. You have interfaces for your domains, auto-generated api, auto-generated HttpClient to simplify connection "api to front-end", a functionality for auto-population in memory of your models, a functionality to simulate exceptions and waiting time from external sources to improve your implementation/business test and load test.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.7 95 3/26/2026
10.0.6 142,541 3/3/2026
10.0.5 305 2/22/2026
10.0.4 344 2/9/2026
10.0.3 148,077 1/28/2026
10.0.1 209,508 11/12/2025
9.1.3 661 9/2/2025
9.1.2 765,248 5/29/2025
9.1.1 98,189 5/2/2025
9.0.32 186,998 4/15/2025
9.0.31 6,100 4/2/2025
9.0.30 89,074 3/26/2025
9.0.29 9,302 3/18/2025
9.0.28 474 3/17/2025
9.0.27 453 3/16/2025
9.0.26 513 3/13/2025
9.0.25 52,386 3/9/2025
9.0.23 280 3/9/2025
9.0.21 1,007 3/6/2025
9.0.20 19,829 3/6/2025
Loading failed