Kolo.Ledger
0.0.8
dotnet add package Kolo.Ledger --version 0.0.8
NuGet\Install-Package Kolo.Ledger -Version 0.0.8
<PackageReference Include="Kolo.Ledger" Version="0.0.8" />
<PackageVersion Include="Kolo.Ledger" Version="0.0.8" />
<PackageReference Include="Kolo.Ledger" />
paket add Kolo.Ledger --version 0.0.8
#r "nuget: Kolo.Ledger, 0.0.8"
#:package Kolo.Ledger@0.0.8
#addin nuget:?package=Kolo.Ledger&version=0.0.8
#tool nuget:?package=Kolo.Ledger&version=0.0.8
Kolo.Ledger
kolo (Yoruba) — piggy box / savings box
Production-grade double-entry ledger library with HMAC-SHA256 hash-chain integrity, period-end closing, multi-book support, and a fully typed audit trail. Built for .NET 10 on EF Core + PostgreSQL.
| Package | Version |
|---|---|
Kolo.Ledger |
Features
- Double-entry accounting — automatic debit/credit balance tracking with contra-account awareness
- HMAC-SHA256 hash chain — every entry is cryptographically linked to its predecessor, providing tamper-evidence and non-repudiation
- Append-only audit trail —
JournalEntryandJournalLineare immutable after creation;SaveChangesAsyncthrowsImmutableRecordExceptionon any modify or delete - Transaction reversal — creates fully offsetting entries (never deletes originals), preserving audit history
- Multi-book (ledger) architecture — run primary, supplemental, budget, tax, and statutory books side-by-side with cross-book reconciliation
- Period management — lock/unlock accounting periods to prevent post-close adjustments
- Period-end closing — automated closing entries that zero out income statement accounts into retained earnings
- Keyset (cursor-based) pagination — all list queries use
MR.EntityFrameworkCore.KeysetPagination; noSkip/Takeanywhere - Hash-chain integrity verification — full ledger scan that validates every entry's hash, sequence, and normal balance
- Multi-tenancy —
ITenantProvider-based data isolation per organisation - Role-based access control —
IFinanceAuthorizationServicewith granular permissions (finance.journal.post,finance.period.lock, etc.) - Export — built-in JSON and CSV export for audit and reporting
- Export service — built-in JSON/CSV export for journal entries, trial balance, and GL
- Access logging — all sensitive operations are logged to
FinanceAuditEntryfor SOX compliance - EF Core migrations — ready-to-run PostgreSQL schema under the
financeschema
Quickstart
1. Install
dotnet add package Kolo.Ledger
2. Configure services
builder.Services.AddLedger(
connectionString: "Host=localhost;Port=7203;Database=finance");
3. Set the HMAC signing key
The hash chain requires a symmetric key (minimum 32 bytes, Base64-encoded). Pass it via LedgerOptions:
builder.Services.AddLedger(
"Host=localhost;Port=7203;Database=finance",
options =>
{
options.HmacSecretKey = configuration["KoloLedger:HmacSecretKey"];
});
You supply the key from your own configuration or secure store (KMS, vault, etc.). The key persists across restarts — it is never generated at startup.
4. Apply migrations
dotnet ef database update --project Kolo.Ledger
Or apply at startup:
await using var scope = app.Services.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<FinanceDbContext>();
await db.Database.MigrateAsync();
5. Seed the chart of accounts
var db = scope.ServiceProvider.GetRequiredService<FinanceDbContext>();
var accountProvider = scope.ServiceProvider.GetRequiredService<ISystemAccountProvider>();
await ChartOfAccountsSeeder.SeedAsync(db, accountProvider);
6. Inject into your own services
builder.Services.AddScoped<InvoiceService>();
public class InvoiceService
{
private readonly ILedgerService _ledger;
public InvoiceService(ILedgerService ledger) => _ledger = ledger;
public async Task RecordPayment(Guid cashAccountId, Guid revenueAccountId, Guid bookId)
{
var entry = await _ledger.Journal.RecordEntryAsync(new JournalEntryRequest(
Reference: "INV-001",
Description: "Invoice payment received",
BookId: bookId,
Lines:
[
new(cashAccountId, 1000m, LedgerEntryType.Debit),
new(revenueAccountId, 1000m, LedgerEntryType.Credit),
]));
}
}
You can also inject individual service interfaces instead of the full facade:
public class BalanceChecker
{
private readonly IBalanceService _balance;
public BalanceChecker(IBalanceService balance) => _balance = balance;
public async Task<decimal> GetCashBalance(Guid cashAccountId)
=> await _balance.GetBalanceAsync(cashAccountId);
}
Full project setup
A complete example from dotnet new to working ledger:
1. Create a new project
dotnet new webapi -n MyFinanceApp
cd MyFinanceApp
dotnet add package Kolo.Ledger
2. Configure Program.cs
using Kolo.Ledger.Extensions;
using Kolo.Ledger.Seeding;
using Kolo.Ledger.Entities;
var builder = WebApplication.CreateBuilder(args);
// Register ledger services
builder.Services.AddLedger(builder.Configuration.GetConnectionString("FinanceDb")!);
// Register your own services
builder.Services.AddScoped<MyFinanceService>();
// Add controllers
builder.Services.AddControllers();
var app = builder.Build();
// Migrate + seed at startup
await using var scope = app.Services.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<Kolo.Ledger.Data.FinanceDbContext>();
await db.Database.MigrateAsync();
var accountProvider = scope.ServiceProvider.GetRequiredService<Kolo.Ledger.Seeding.ISystemAccountProvider>();
await ChartOfAccountsSeeder.SeedAsync(db, accountProvider);
var book = await BookSeeder.SeedDefaultBookAsync(db);
app.MapControllers();
app.Run();
3. Use in a controller
[ApiController]
[Route("invoices")]
public class InvoicesController : ControllerBase
{
private readonly ILedgerService _ledger;
public InvoicesController(ILedgerService ledger) => _ledger = ledger;
[HttpPost]
public async Task<IActionResult> Post(JournalEntryRequest request)
{
var entry = await _ledger.Journal.RecordEntryAsync(request);
return Ok(entry);
}
[HttpGet("balance/{accountId}")]
public async Task<IActionResult> GetBalance(Guid accountId)
{
var balance = await _ledger.Balance.GetBalanceAsync(accountId);
return Ok(balance);
}
}
4. Add the connection string to appsettings.json
{
"ConnectionStrings": {
"FinanceDb": "Host=localhost;Port=7203;Database=finance"
}
}
5. Run
dotnet run
Configuration
Connection string
// Simplest form
services.AddLedger("Host=localhost;Port=7203;Database=finance");
// With schema override
services.AddLedger("Host=localhost;Port=7203;Database=finance", schemaName: "my_finance");
// With full options
services.AddLedger(options =>
{
options.ConnectionString = "Host=localhost;Port=7203;Database=finance";
options.SchemaName = "finance";
options.MaxPageSize = 200;
options.RetryMaxAttempts = 3;
options.SeedDefaultBook = true;
});
LedgerOptions reference
| Option | Default | Description |
|---|---|---|
SchemaName |
"finance" |
PostgreSQL schema for all ledger tables |
MaxPageSize |
100 |
Maximum items per keyset-paginated page |
HmacSecretKey |
null |
Base64-encoded HMAC-SHA256 signing key (minimum 32 bytes when decoded) |
HmacKeyMinimumLength |
32 |
Minimum bytes for the HMAC signing key |
RetryMaxAttempts |
5 |
Retry attempts for sequence conflict resolution |
RetryBaseDelayMs |
100 |
Base delay (ms) for exponential backoff |
RetryMaxDelayMs |
5000 |
Maximum delay (ms) between retries |
LockTimeoutMs |
5000 |
Lock timeout (ms) for period operations |
IncludeErrorDetails |
true |
Include detailed error messages in exceptions |
SeedDefaultBook |
true |
Auto-seed a default primary book on first use |
HMAC key configuration
Pass the key (minimum 32 bytes, Base64-encoded) through LedgerOptions.HmacSecretKey:
services.AddLedger("Host=localhost;Port=7203;Database=finance", options =>
{
options.HmacSecretKey = configuration["KoloLedger:HmacSecretKey"];
});
You supply the key from your own configuration or secure store (Azure Key Vault, AWS KMS, HashiCorp Vault, etc.). Or provide a custom IHmacKeyProvider implementation:
services.AddSingleton<IHmacKeyProvider>(sp => new MyVaultHmacKeyProvider());
Multi-tenancy
Implement ITenantProvider to scope data by organisation:
services.AddScoped<ITenantProvider, MyTenantProvider>();
public class MyTenantProvider : ITenantProvider
{
private readonly IHttpContextAccessor _http;
public MyTenantProvider(IHttpContextAccessor http) => _http = http;
public Guid? GetOrganisationId()
{
var claim = _http.HttpContext?.User.FindFirst("org_id");
return claim is not null ? Guid.Parse(claim.Value) : null;
}
}
In single-tenant mode, NullTenantProvider is used (no filtering).
Core Concepts
Double-entry accounting
Every journal entry has at least two lines: one debit and one credit. Total debits must equal total credits. The library enforces this at the domain level and will throw UnbalancedTransactionException if violated.
Account types & normal balances
| Account Type | Normal Balance | Contra Normal | Classification |
|---|---|---|---|
Asset |
Debit | Credit (contra-asset) | Balance Sheet |
Liability |
Credit | Debit (contra-liability) | Balance Sheet |
Equity |
Credit | Debit (contra-equity) | Balance Sheet |
Revenue |
Credit | Debit (contra-revenue) | Income Statement |
Expense |
Debit | Credit (contra-expense) | Income Statement |
Use AccountExtensions.CalculateBalance() to determine an account's net position:
var balance = AccountExtensions.CalculateBalance(account.Type, totalDebits, totalCredits);
Books (ledgers)
A Book represents a logical ledger. You can have multiple books for different purposes:
- Primary — the main general ledger
- Supplemental — sub-ledgers for specific departments or entities
- Budget — budget vs actual tracking
- Tax — tax-basis accounting
- Statutory — regulatory reporting
Books can be reconciled against each other via IReconciliationService:
var report = await reconciliationService.ReconcileAsync(
sourceBookId: primaryBookId,
targetBookId: taxBookId);
// report.IsBalanced, report.Differences, report.TotalAbsoluteDifference
The hash chain
Every JournalEntry stores:
SequenceNumber— monotonically increasing (unique per book)PreviousHash— theHashof the preceding entry in the same bookHash—HMAC-SHA256(PreviousHash + EntryData + LineData, key)SignatureVersion— algorithm version for future-proofing
The genesis entry uses "GENESIS" as its PreviousHash. The chain is verified by IVerifyIntegrityCommand, which checks:
- No sequence number gaps
- Every
PreviousHashmatches the predecessor'sHash - Every entry's
Hashrecomputes correctly - Every line's normal balance is correct
Immutability
JournalEntry and JournalLine use init-only setters and private constructors with static factory methods. The FinanceDbContext.SaveChangesAsync override explicitly prevents any modification or deletion of these entity types by throwing ImmutableRecordException.
Usage
Recording entries
public class PurchasingService
{
private readonly ILedgerService _ledger;
public PurchasingService(ILedgerService ledger) => _ledger = ledger;
public async Task RecordPurchase(Guid suppliesExpenseId, Guid cashAccountId, Guid bookId)
{
var entry = await _ledger.Journal.RecordEntryAsync(new JournalEntryRequest(
Reference: "PUR-042",
Description: "Office supplies purchase",
BookId: bookId,
EntryDate: DateTime.UtcNow,
Source: "purchasing",
Lines:
[
new(suppliesExpenseId, 250.00m, LedgerEntryType.Debit),
new(cashAccountId, 250.00m, LedgerEntryType.Credit),
],
CreatedByUserId: "alice",
CreatedByUserName: "Alice Johnson",
CorrelationId: "req-abc-123",
RequestIp: "192.168.1.1",
UserAgent: "ERP/v2.1"));
}
}
Reversing entries
Reversal creates a new entry with opposite debits/credits linked to the original:
public class RefundService
{
private readonly ILedgerService _ledger;
public RefundService(ILedgerService ledger) => _ledger = ledger;
public async Task RefundOrder(Guid originalEntryId)
{
var reversal = await _ledger.Journal.ReverseEntryAsync(
entryId: originalEntryId,
reason: "Customer refund — order cancelled",
createdByUserId: "bob");
}
}
The reversal entry has IsReversal = true, ReversedEntryId pointing to the original, and is timestamped in the audit log.
Trial balance
public class ReportingService
{
private readonly ILedgerService _ledger;
public ReportingService(ILedgerService ledger) => _ledger = ledger;
public async Task PrintTrialBalance(Guid bookId)
{
var trialBalance = await _ledger.Reporting.GetTrialBalanceAsync(
year: 2026, month: 7, bookId: bookId);
Console.WriteLine($"Balanced: {trialBalance.IsBalanced}");
Console.WriteLine($"Total Debits: {trialBalance.TotalDebits}");
Console.WriteLine($"Total Credits: {trialBalance.TotalCredits}");
foreach (var entry in trialBalance.Entries)
Console.WriteLine($"{entry.AccountCode} {entry.AccountName}: {entry.Balance}");
}
}
General ledger
public class GlReportService
{
private readonly ILedgerService _ledger;
public GlReportService(ILedgerService ledger) => _ledger = ledger;
public async Task PrintGl(Guid cashAccountId, Guid bookId)
{
var gl = await _ledger.Reporting.GetGeneralLedgerAsync(new GeneralLedgerRequest(
AccountId: cashAccountId,
DateFrom: new DateTime(2026, 1, 1),
DateTo: new DateTime(2026, 12, 31),
BookId: bookId));
Console.WriteLine($"Opening: {gl.OpeningBalance}, Closing: {gl.ClosingBalance}");
}
}
Listing entries (keyset pagination)
public class EntryLister
{
private readonly ILedgerService _ledger;
public EntryLister(ILedgerService ledger) => _ledger = ledger;
public async Task ListEntries(Guid bookId)
{
var result = await _ledger.Journal.ListEntriesAsync(new JournalEntryFilter
{
BookId = bookId,
PageSize = 50,
Direction = KeysetPaginationDirection.Forward,
});
foreach (var item in result.Items) { /* ... */ }
// Navigate forward
if (result.HasNext)
{
var nextPage = await _ledger.Journal.ListEntriesAsync(new JournalEntryFilter
{
BookId = bookId,
ReferenceCursor = result.NextReference,
Direction = KeysetPaginationDirection.Forward,
PageSize = 50,
});
}
}
}
Period management
public class PeriodManager
{
private readonly ILedgerService _ledger;
public PeriodManager(ILedgerService ledger) => _ledger = ledger;
public async Task ManagePeriods(Guid bookId)
{
// Lock a period to prevent new entries
await _ledger.Periods.LockPeriodAsync(year: 2026, month: 6, lockedByUserId: "admin");
// Unlock (requires finance.period.lock permission)
await _ledger.Periods.UnlockPeriodAsync(year: 2026, month: 6, unlockedByUserId: "admin");
// Close a period — zeroes income statement accounts to retained earnings
await _ledger.Periods.ClosePeriodAsync(year: 2026, month: 6, bookId: bookId,
createdByUserId: "admin");
}
}
Integrity verification
public class AuditService
{
private readonly ILedgerService _ledger;
public AuditService(ILedgerService ledger) => _ledger = ledger;
public async Task VerifyLedgerIntegrity()
{
var integrity = await _ledger.Audit.VerifyIntegrityAsync();
Console.WriteLine($"Valid: {integrity.IsValid}");
Console.WriteLine($"Entries checked: {integrity.TotalEntriesChecked}");
Console.WriteLine($"Errors: {integrity.ErrorCount}");
foreach (var error in integrity.Errors)
Console.WriteLine($"[{error.ErrorType}] Entry {error.SequenceNumber}: {error.Message}");
}
}
Export
public class ExportService
{
private readonly IExportService _export;
public ExportService(IExportService export) => _export = export;
public async Task ExportData(List<JournalEntryDto> entries, TrialBalanceReport trialBalance)
{
string json = await _export.ExportToJsonAsync(entries);
string csv = await _export.ExportToCsvAsync(trialBalance.Entries);
}
}
Typed metadata (accounts, journal lines, journal entries)
Every Account, JournalLine, and JournalEntry supports strongly-typed metadata via extension methods.
Metadata is stored as a single JSONB column per entity — one POCO per entity. If you need multiple data shapes,
compose a single DTO:
public record WalletInfo(string WalletType, string Status);
Account — MetadataJson (JSONB column, string?):
account.SetMetadata(new WalletInfo("main", "active"));
var meta = account.GetMetadata<WalletInfo>();
// meta?.WalletType == "main"
bool has = account.HasMetadata<WalletInfo>(); // true
account.RemoveMetadata<WalletInfo>();
JournalLine — same pattern as Account:
line.SetMetadata(new WalletInfo("main", "active"));
var meta = line.GetMetadata<WalletInfo>();
JournalEntry — serializes/deserializes via the existing Dictionary<string, object?> property.
Free-form dictionary access and typed access operate on the same data:
entry.SetMetadata(new WalletInfo("main", "active"));
// Typed access:
var meta = entry.GetMetadata<WalletInfo>();
// Free-form legacy access still works:
entry.Metadata["walletType"].ToString(); // "main"
// They share the same storage — SetMetadata replaces the entire dictionary.
Design notes:
- One type per entity — compose a DTO if you need multiple data groups
- If you rename a POCO type, existing JSONB data will silently fail to deserialize
- For
JournalEntry,SetMetadata<T>()replaces the entire dictionary; use either the typed API or free-form dict access on a given entry - Journal lines are immutable after save — set metadata before the first
SaveChangesAsync - Metadata is not included in the hash chain (it is not part of the accounting integrity check)
public class CashBalanceService
{
private readonly ILedgerService _ledger;
public CashBalanceService(ILedgerService ledger) => _ledger = ledger;
public async Task<decimal> GetCashBalance(Guid cashAccountId)
=> await _ledger.Balance.GetBalanceAsync(accountId: cashAccountId);
}
Authorization & Permissions
The library includes a built-in permission system accessed via IFinanceAuthorizationService:
| Permission | Constant | Operation |
|---|---|---|
finance.journal.post |
FinancePermissions.JournalPost |
Record journal entries |
finance.journal.reverse |
FinancePermissions.JournalReverse |
Reverse entries |
finance.period.lock |
FinancePermissions.PeriodLock |
Lock/unlock periods |
finance.period.close |
FinancePermissions.PeriodClose |
Close accounting periods |
finance.audit.view |
FinancePermissions.AuditView |
View audit logs and integrity reports |
await authorizationService.AssertPermissionAsync(FinancePermissions.JournalPost, ct);
string userId = authorizationService.CurrentUserId;
Implement IFinanceAuthorizationService or override ICurrentUser for custom authorization logic. The default NullCurrentUser grants all permissions.
Exceptions
| Exception | Cause |
|---|---|
UnbalancedTransactionException |
Debits do not equal credits in a journal entry |
ImmutableRecordException |
Attempted to modify or delete a JournalEntry or JournalLine |
PeriodLockedException |
Attempted to post to a locked accounting period |
SequenceConflictException |
Concurrent sequence number allocation conflict (auto-retried) |
ContraNormalBalanceException |
Zero or negative balance on a normal-balance account that disallows negatives |
InsufficientBalanceException |
Insufficient funds for a debit on a balance-based account |
NegativeBalanceForbiddenException |
Balance would go negative on an account with AllowNegativeBalance = false |
SystemAccountConstraintException |
System account assigned to an organisation or vice versa |
Architecture
Kolo.Ledger/
├── Abstractions/ — Public interfaces for all injectable services
│ ├── IRecordEntryCommand
│ ├── IReverseEntryCommand
│ ├── IGetBalanceQuery
│ ├── IGetTrialBalanceQuery
│ ├── IClosePeriodCommand
│ ├── IVerifyIntegrityCommand
│ ├── IExportService
│ ├── IReconciliationService
│ ├── ITenantProvider
│ ├── IFinanceAuthorizationService
│ └── ICurrentUser
│
├── Configuration/ — EF Core IEntityTypeConfiguration<T> classes
├── Core/ — FinanceConstants, FinancePermissions, LedgerOptions
├── DTOs/ — Request/response types
├── Entities/ — Domain model with init-only properties
├── Exceptions/ — Typed exception hierarchy
├── Extensions/ — DI registration (AddLedger)
├── Features/ — Feature-first commands & queries
│ ├── Accounts/ — GetBalance, GetAccountBalance
│ ├── Audit/ — VerifyIntegrity
│ ├── Books/ — CreateBook, ListBooks, ReconcileBooks
│ ├── Closing/ — ClosePeriod
│ ├── JournalEntries/ — RecordEntry, ReverseEntry, ValidateEntry
│ ├── Periods/ — LockPeriod, UnlockPeriod, EnsurePeriodExists
│ └── Reporting/ — TrialBalance, GeneralLedger, ListJournalEntries
├── Mapper/ — Shared mapping extensions
├── Migrations/ — EF Core PostgreSQL migrations
├── Seeding/ — ChartOfAccountsSeeder, BookSeeder
└── Services/ — AccessLogger, HmacKeyProvider, ExportService
Design principles
- Feature-first folders — each feature (Accounts, JournalEntries, Periods, etc.) is self-contained with its own Commands/, Queries/, and Mapper/ sub-folders
- Action classes — every use case is a single class with one public
ExecuteAsync(...)method; all EF queries live in private methods - No unit of work wrapper — commands interact with
FinanceDbContextdirectly;SaveChangesAsyncis called explicitly - Keyset pagination everywhere — all list queries use cursor-based pagination via
MR.EntityFrameworkCore.KeysetPagination; neverSkip/Take - Explicit DI registration — no Scrutor or assembly scanning; all services registered individually in
ServiceCollectionExtensions
Compliance & Audit
| Framework | Focus | Status |
|---|---|---|
| GAAP (ASC 205-250) | Revenue recognition, balance sheet presentation | 85/100 |
| IFRS (IAS 1, 21, 37) | Financial statement presentation, provisions | 78/100 |
| SOX 404 (ICFR) | Internal controls over financial reporting | 78/100 |
| ACID | Transactional integrity, isolation, durability | 82/100 |
The library provides:
- Append-only journal tables — no deletions or silent edits
- HMAC-SHA256 hash chain — tamper evidence with cryptographic verification
- Full access logging — every sensitive operation recorded in
FinanceAuditEntry - RowVersion concurrency — optimistic locking on all entities
- Transaction reversal — audit-preserving offset entries (never deletes originals)
Migrations & Deployment
The package ships with EF Core migrations for PostgreSQL under the finance schema:
# Generate a new migration
dotnet ef migrations add AddNewFeature --project Kolo.Ledger
# Apply to database
dotnet ef database update --project Kolo.Ledger
# Apply programmatically
var db = scope.ServiceProvider.GetRequiredService<FinanceDbContext>();
await db.Database.MigrateAsync();
Operational requirements
For production PostgreSQL deployment:
fsync=on— committed writes survive power losssynchronous_commit=on— COMMIT returns after WAL flush- WAL archiving to S3/GCS — point-in-time recovery capability
- Hourly backups of the
financeschema - Quarterly restore testing
- RTO < 1 hour, RPO < 1 minute
Seeding
The library includes two seeders for initial setup:
// Seeds system accounts (Cash, Accounts Receivable, Revenue, etc.)
var db = scope.ServiceProvider.GetRequiredService<FinanceDbContext>();
var accountProvider = scope.ServiceProvider.GetRequiredService<ISystemAccountProvider>();
await ChartOfAccountsSeeder.SeedAsync(db, accountProvider);
// Seeds default books (Primary, Supplemental, Tax)
await BookSeeder.SeedDefaultBookAsync(db);
System accounts are defined in SystemAccountDefinition records and are identifiable by IsSystem = true. They don't belong to any organisation.
Development
Build
dotnet build Kolo.Ledger/Kolo.Ledger.csproj
Run tests
dotnet test Kolo.Ledger/tests/Kolo.Ledger.Tests
Generate a migration
export FINANCE_DB_CONNECTION="Host=localhost;Database=finance"
dotnet ef migrations add MigrationName --project Kolo.Ledger
Pack for NuGet
dotnet pack Kolo.Ledger/Kolo.Ledger.csproj -c Release -o ./nupkg
Publish to NuGet
dotnet nuget push ./nupkg/Kolo.Ledger.0.1.0.nupkg \
--api-key $NUGET_API_KEY \
--source https://api.nuget.org/v3/index.json
Package Contents
When installed, Kolo.Ledger provides:
Kolo.Ledger.dll— the compiled libraryKolo.Ledger.xml— IntelliSense XML documentationKolo.Ledger.pdb— symbols (viasnupkg) for debuggingREADME.md— this documentation (visible on nuget.org)
License
MIT
| Product | Versions 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. |
-
net10.0
- Microsoft.EntityFrameworkCore (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- MR.EntityFrameworkCore.KeysetPagination (>= 1.6.0)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 10.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.