![]() |
VOOZH | about |
dotnet add package Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table --version 10.0.8
NuGet\Install-Package Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table -Version 10.0.8
<PackageReference Include="Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table" Version="10.0.8" />
<PackageVersion Include="Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table" Version="10.0.8" />Directory.Packages.props
<PackageReference Include="Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table" />Project file
paket add Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table --version 10.0.8
#r "nuget: Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table, 10.0.8"
#:package Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table@10.0.8
#addin nuget:?package=Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table&version=10.0.8Install as a Cake Addin
#tool nuget:?package=Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table&version=10.0.8Install as a Cake Tool
Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table adds an Azure Table Storage adapter for Repository Framework.
This package is not a column-per-property mapper. Each repository item is stored as:
PartitionKeyRowKeyTimestampValue payload containing the modelThat design makes key-based lookups simple, but most query behavior stays client-side.
dotnet add package Rystem.RepositoryFramework.Infrastructure.Azure.Storage.Table
The storage implementation writes one internal ITableEntity per repository item.
Value columnPartitionKey and RowKey come from the configured table key readerInsertAsync delegates to UpdateAsync, so inserts behave like upsertsThis package is best when Azure Table Storage is mainly your key-addressable backing store.
Available on all three Repository Framework patterns:
WithTableStorageAsync(...)WithTableStorage(...)Supported for:
IRepositoryBuilder<T, TKey>ICommandBuilder<T, TKey>IQueryBuilder<T, TKey>The async overloads are the real implementations. The sync overloads just block on them.
Available overloads:
WithTableStorage<T, TKey, TConnectionService>(...)WithTableStorage<T, TKey, TConnectionService, TKeyReader>(...)These are useful when table clients are resolved per tenant or per request.
This mirrors the integration tests.
await builder.Services.AddRepositoryAsync<AppUser, AppUserKey>(async repositoryBuilder =>
{
await repositoryBuilder.WithTableStorageAsync(tableStorageBuilder =>
{
tableStorageBuilder.Settings.ConnectionString = builder.Configuration["ConnectionStrings:Storage"];
tableStorageBuilder.Settings.TableName = "appusers";
tableStorageBuilder
.WithTableStorageKeyReader<TableStorageKeyReader>()
.WithPartitionKey(x => x.Id, x => x.Id)
.WithRowKey(x => x.Username)
.WithTimestamp(x => x.CreationTime);
}, name: "tablestorage");
});
Example custom key reader from the tests:
internal sealed class TableStorageKeyReader : ITableStorageKeyReader<AppUser, AppUserKey>
{
public (string PartitionKey, string RowKey) Read(
AppUserKey key,
TableStorageSettings<AppUser, AppUserKey> settings)
=> (key.Id.ToString(), string.Empty);
public AppUserKey Read(
AppUser entity,
TableStorageSettings<AppUser, AppUserKey> settings)
=> new(entity.Id);
}
This is the other pattern used in the integration tests.
builder.Services.AddRepository<AppUser, AppUserKey>(repositoryBuilder =>
{
repositoryBuilder
.WithTableStorage<AppUser, AppUserKey, TableStorageConnectionService, TableStorageKeyReader>(
name: "tablestorage2");
});
The connection service returns a ready-to-use TableClientWrapper<T, TKey>.
internal sealed class TableStorageConnectionService
: IConnectionService<AppUser, AppUserKey, TableClientWrapper<AppUser, AppUserKey>>
{
public TableClientWrapper<AppUser, AppUserKey> GetConnection(
string entityName,
string? factoryName = null)
{
return new TableClientWrapper<AppUser, AppUserKey>
{
Client = new TableClient("<connection-string>", entityName.ToLower()),
Settings = new TableStorageSettings<AppUser, AppUserKey>
{
PartitionKey = "Id",
RowKey = "Username",
Timestamp = "CreationTime",
PartitionKeyFromKeyFunction = x => x.Id.ToString(),
PartitionKeyFunction = x => x.Id.ToString(),
RowKeyFunction = x => x.Username,
TimestampFunction = x => x.CreationTime
}
};
}
}
TableStorageConnectionSettings exposes:
| Property | Notes |
|---|---|
ConnectionString |
Used when present. If both this and EndpointUri are set, connection string wins. |
EndpointUri |
Table service endpoint for managed identity mode. |
ManagedIdentityClientId |
Null means system-assigned identity. |
TableName |
Defaults to typeof(T).Name. |
ClientOptions |
Passed to Azure Tables SDK. |
Builder overload defaults:
SingletonScopedITableStorageRepositoryBuilder<T, TKey> exposes:
| Method | What it configures |
|---|---|
WithPartitionKey(model, key) |
Model property and key-side extractor for PartitionKey. |
WithRowKey(model, key) |
Model property and key-side extractor for RowKey. |
WithRowKey(model) |
Only the entity-side row key extractor. |
WithTimestamp(model) |
The model property name used for timestamp-aware query translation. |
WithTableStorageKeyReader<T>() |
Custom ITableStorageKeyReader<T, TKey>. |
The default key reader reconstructs keys like this:
PartitionKeyFromKeyFunction(key) and RowKeyFromKeyFunction(key)That leads to an important caveat:
WithRowKey(x => x.Prop) does not configure RowKeyFromKeyFunctionRowKey name used for query translationSo the one-argument WithRowKey(...) overload is not enough for many real key-based scenarios. In practice, when TKey is not directly representable by the partition key alone, prefer a custom ITableStorageKeyReader<T, TKey>.
When you use WithTableStorageAsync(...) or WithTableStorage(...), the package creates the table during registration by calling CreateTableIfNotExistsAsync(...).
When you use WithTableStorage<T, TKey, TConnectionService>(...), table creation is your responsibility inside the connection service.
TableStorageRepository<T, TKey>.BootstrapAsync() currently returns true and does nothing.
So for this package, provisioning is tied to direct registration, not to WarmUpAsync().
QueryAsync(...) supports Azure-side filtering only in a narrow set of cases.
What it tries to push down:
Where expressionPartitionKey, RowKey, or TimestampEverything else is effectively local:
OrderBy / ThenByThe flow is:
Value into modelsTop and Skip are partially applied during table enumeration before the final local filter/order phaseCount, Sum, Min, Max, and Average materialize items and run in memoryBatchAsync(...) is just a sequential loop, not an Azure Tables transactional batchInsertAsync calls UpdateAsync, so it overwrites existing rows instead of failing on duplicatesUpdateAsync uses UpsertEntityAsync(..., TableUpdateMode.Replace)GetAsync catches missing-row exceptions and returns defaultDeleteAsync directly calls DeleteEntityAsync(...)If you need strict create-only behavior, build that check above the repository.
The optional name parameter is a Repository Framework factory name, not a table name.
It is used to:
In connection-service mode, the repository calls:
connectionService.GetConnection(typeof(T).Name, name)
So the service receives the CLR model name, not the configured TableName.
await builder.Services.AddCommandAsync<User, UserKey>(async commandBuilder =>
{
await commandBuilder.WithTableStorageAsync(tableStorageBuilder =>
{
tableStorageBuilder.Settings.ConnectionString = builder.Configuration["ConnectionStrings:Storage"];
tableStorageBuilder
.WithPartitionKey(x => x.TenantId, x => x.TenantId)
.WithRowKey(x => x.Id, x => x.Id);
});
});
await builder.Services.AddQueryAsync<User, UserKey>(async queryBuilder =>
{
await queryBuilder.WithTableStorageAsync(tableStorageBuilder =>
{
tableStorageBuilder.Settings.ConnectionString = builder.Configuration["ConnectionStrings:Storage"];
tableStorageBuilder
.WithPartitionKey(x => x.TenantId, x => x.TenantId)
.WithRowKey(x => x.Id, x => x.Id);
});
});
Use it when you want:
Avoid it when you need rich server-side querying over many model properties, because that is not what the current implementation is optimized for.
| 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 | 136 | 3/26/2026 |
| 10.0.6 | 433,556 | 3/3/2026 |
| 10.0.5 | 133 | 2/22/2026 |
| 10.0.4 | 129 | 2/9/2026 |
| 10.0.3 | 147,920 | 1/28/2026 |
| 10.0.1 | 209,365 | 11/12/2025 |
| 9.1.3 | 335 | 9/2/2025 |
| 9.1.2 | 764,966 | 5/29/2025 |
| 9.1.1 | 97,996 | 5/2/2025 |
| 9.0.32 | 186,733 | 4/15/2025 |
| 9.0.31 | 5,836 | 4/2/2025 |
| 9.0.30 | 88,868 | 3/26/2025 |
| 9.0.29 | 9,056 | 3/18/2025 |
| 9.0.28 | 293 | 3/17/2025 |
| 9.0.27 | 299 | 3/16/2025 |
| 9.0.26 | 317 | 3/13/2025 |
| 9.0.25 | 52,176 | 3/9/2025 |
| 9.0.21 | 753 | 3/6/2025 |
| 9.0.20 | 19,613 | 3/6/2025 |