SimpleORM.Net.AspNetCore
0.1.2
See the version list below for details.
dotnet add package SimpleORM.Net.AspNetCore --version 0.1.2
NuGet\Install-Package SimpleORM.Net.AspNetCore -Version 0.1.2
<PackageReference Include="SimpleORM.Net.AspNetCore" Version="0.1.2" />
<PackageVersion Include="SimpleORM.Net.AspNetCore" Version="0.1.2" />
<PackageReference Include="SimpleORM.Net.AspNetCore" />
paket add SimpleORM.Net.AspNetCore --version 0.1.2
#r "nuget: SimpleORM.Net.AspNetCore, 0.1.2"
#:package SimpleORM.Net.AspNetCore@0.1.2
#addin nuget:?package=SimpleORM.Net.AspNetCore&version=0.1.2
#tool nuget:?package=SimpleORM.Net.AspNetCore&version=0.1.2
SimpleORM.Net
A lightweight, provider-based ORM for .NET 10 focused on a small repository API, model conventions, batching, transactions, extensions, multi-tenancy, auditing and provider-specific schema management.
Author: Akintunde Morakinyo
Repository: akin2unde/SimpleORM.Net
Packages
SimpleORM.Net— core repository, metadata, query model and transaction abstractions.SimpleORM.Net.SqlServer— SQL Server provider and schema synchronization.SimpleORM.Net.MongoDB— MongoDB provider and index synchronization.SimpleORM.Net.AspNetCore— ASP.NET Core tenant/user integration, error middleware and payload encryption.SimpleORM.Net.Http— typed HTTP request wrapper.
Quick start
builder.Services.AddSimpleOrm(
options =>
{
options.Database = DatabaseType.SqlServer;
options.Connection.Host = "localhost";
options.Connection.Port = 1433;
options.Connection.Database = "CommerceDb";
options.Connection.Username = "sa";
options.Connection.Password = configuration["SimpleOrm:Password"];
options.DefaultStringLength = 50;
options.CodeGeneration.Length = 10;
options.Batch.Save = 100;
options.Batch.Select = 100;
options.Concurrency.Enabled = true; // default
options.AutoMigration = true;
},
typeof(Product).Assembly);
// Provider registration intentionally remains explicit.
builder.Services.AddSimpleOrmSqlServer();
builder.Services.AddSimpleOrmAspNetCore();
For MongoDB, set options.Database = DatabaseType.MongoDb and call AddSimpleOrmMongoDB().
Models and code generation
Every persisted model inherits DBModel. A table/collection is inferred automatically; [DBTable] is only needed to override its database name.
public sealed class Product : DBModel
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public override string GetPrefix() => "PRD";
}
Every model instance can generate a code:
var product = new Product();
var code = product.GenerateCode(); // PRD-XXXXXXXXXX
var shortCode = product.GenerateCode(6); // PRD-XXXXXX
When Code is empty during an insert, the repository calls the model's GenerateCode using the configured/model metadata length. Code is unique by convention.
Optimistic concurrency
SimpleORM protects updates and deletes from lost updates by default. Every DBModel has a Version managed by the ORM. The database mutation matches the version originally loaded and increments it atomically when the write succeeds. No separate pre-read is added to the normal write path.
var product = await repository.GetByCode<Product>("PRD-001", ct);
product!.Price = 120;
product.DataState = DataState.Changed;
await repository.Save(product, ct);
If another request changed the same record after it was loaded, Save throws DBConcurrencyException. For detached/API update models, round-trip the Version value returned by the read; omitting or changing it can correctly produce a conflict once the stored record has advanced. With the optional ASP.NET Core error middleware enabled, this exception is returned as HTTP 409 Conflict.
Concurrency protection is enabled globally by default and can be configured explicitly:
options.Concurrency.Enabled = true;
Models that intentionally use last-write-wins behavior can opt out:
[DisableConcurrencyCheck]
public sealed class TelemetryLog : DBModel
{
public string Message { get; set; } = string.Empty;
}
SQL Server keeps bulk performance by checking Version in the existing staging-table join. MongoDB includes Version in each bulk-write filter. SQL auto-migration seeds existing rows with version 1; older MongoDB documents without a version are treated as version 1 on their first protected mutation.
Repository API
Inject one repository for all models:
public sealed class ProductService(IDataRepository repository)
{
public Task<Product?> Get(string code, CancellationToken ct) =>
repository.GetByCode<Product>(code, ct);
}
Select
var page = await repository.Select<Product>(
skip: 0,
limit: 100,
cancellationToken: ct,
batch: 100);
var active = await repository.Select<Product>(
x => x.Active,
skip: 0,
limit: 100,
cancellationToken: ct);
limit: 0 means fetch all matching records. Physical batches are capped internally at 500.
For joins, selected fields, ordering and richer filtering, pass SearchParam or combine it with an expression.
SelectSingle
var product = await repository.SelectSingle<Product>(
x => x.Code == code,
ct);
Search
Strings are searchable by convention unless [NotSearchable] is applied.
var result = await repository.Search<Product>(
"milk",
cancellationToken: ct);
Count
long total = await repository.Count<Product>(cancellationToken: ct);
long active = await repository.Count<Product>(x => x.Active, cancellationToken: ct);
Save and DataState
One Save API handles insert, update and delete through DataState.
product.DataState = DataState.New;
await repository.Save(product, ct);
product.Price = 2500;
product.DataState = DataState.Changed;
await repository.Save(product, ct);
product.DataState = DataState.Removed;
await repository.Save(product, ct);
Models use soft delete by default. Apply [HardDelete] to models that must be physically deleted.
Batch save places CancellationToken before the optional batch parameter:
await repository.Save(products, ct, batch: 200);
Transactions
Normal saves manage their transaction automatically. Use IDBTransactionManager.Execute when several repository operations must commit or roll back together.
return await transactionManager.Execute<IReadOnlyList<Product>>(
async () =>
{
var savedProducts = await repository.Save(products, ct);
if (inventories.Count > 0)
{
await repository.Save(inventories, ct);
}
return savedProducts;
},
ct);
Nested repository calls reuse the current scoped transaction; they do not independently commit it.
SearchParam, joins and selected fields
SearchParam supports filters, ordering, join type and selected fields. Expression filters can be used alone or merged with a SearchParam.
var result = await repository.Select<Order>(
x => x.Total > 1000,
searchParam,
skip: 0,
limit: 100,
cancellationToken: ct);
Debug queries
Generate a provider-specific query with values embedded for debugging only:
var query = repository.GenerateDebugQuery<Product>(searchParam);
The generated text is for inspection and is never used as the execution path.
Extensions
Apply [Extendable] to models that support dynamic extension definitions. DBModel.Extended contains loaded extension values. Definitions describe field code/name, data type, required state, size and default value.
options.Extensions.RequirePublish = false makes saved definitions immediately available. Set it to true to require explicit publishing.
The sample API contains end-to-end definition, publishing, loading and saving examples.
Ignored properties
Use [Ignore] for model properties that belong to runtime/application state but must never be persisted:
public sealed class Country : DBModel
{
public string Name { get; set; } = string.Empty;
[Ignore]
public string? DisplayLabel { get; set; }
}
Ignored properties are excluded from SQL Server schema generation, selects, inserts, updates, filters, joins, and ordering. MongoDB also omits ignored members from BSON persistence. Existing ignored SQL columns are only physically removed when destructive migrations are enabled.
Multi-tenancy
Enable tenant filtering globally:
options.MultiTenancy.Enabled = true;
options.MultiTenancy.JwtClaim = "tenant";
ASP.NET Core can resolve the tenant from the configured JWT claim. Tenant behavior remains optional.
Use [Global] for shared models that must not require or persist a tenant even when application multi-tenancy is enabled:
[Global]
public sealed class Country : DBModel
{
public string Name { get; set; } = string.Empty;
}
Normal models remain tenant scoped. Global models skip tenant filters on read/update/delete and the inherited Tenant property is excluded from persistence. Typical uses include tenant records themselves and shared reference data such as countries.
Audit and error logging
Audit trails are opt-in globally and can be disabled per model. Error logging middleware is also optional and can persist useful failure context such as request URL and payload information where available.
Stale error logs can be physically removed on a UTC cron schedule:
options.ErrorLog.Enabled = true;
options.ErrorLog.AutoDeleteEnabled = true;
options.ErrorLog.RetentionDays = 60;
options.ErrorLog.CleanupCron = "0 0 1 */3 *"; // every quarter
The cleanup above runs every three months and deletes error-log records whose CreatedAt is older than 60 days. A monthly schedule can use 0 0 1 * *. Cleanup bypasses tenant scoping because retention is system-level maintenance.
Any model can opt into the same retention mechanism:
[AutoDelete(60, "0 0 1 * *")]
public sealed class TemporaryImport : DBModel
{
}
Enum and string conventions
options.DefaultStringLength = 50;
options.EnumStorage = EnumStorage.String;
Individual model attributes can override supported conventions. Password/sensitive return values can use [DefaultOnReturn] so their values are reset after materialization.
Schema management
SQL Server auto-migration synchronizes supported table, column, key and index changes and records migration failures. MongoDB intentionally avoids relational-style migrations and synchronizes indexes instead.
Raw provider queries
IDBQuery is available for advanced provider-specific direct queries and supports dynamic or typed result shapes. Prefer IDataRepository for normal application CRUD.
Bulk-write performance
SQL Server batch inserts use SqlBulkCopy. Batch updates and deletes stage rows into a temporary table and apply one set-based statement per SimpleORM batch, reducing per-row database round-trips. MongoDB continues to use native bulk writes. For performance comparisons, use the same batch size for both providers; 500 is the maximum SimpleORM physical batch size.
See docs/PERFORMANCE-AND-CORRECTNESS-2026-09-04.md for the changes made after the 1,000,000-record Docker benchmark and the next-run guidance.
Sample project
samples/SimpleORM.Net.Sample.Api demonstrates:
- Controller → application service →
IDataRepository - expression and
SearchParamselects SelectSingle,GetByCode, search and count- single and batch saves
DataState- transactions across multiple model types
- extensions and publishing
- debug query generation
- SQL Server/MongoDB provider selection
- ASP.NET Core integration
Build and test
dotnet restore SimpleORM.Net.slnx
dotnet build SimpleORM.Net.slnx -c Release
dotnet test SimpleORM.Net.slnx -c Release
XML documentation and warnings-as-errors are enabled repository-wide.
NuGet publishing
GitHub Actions includes:
.github/workflows/build.yml— restore, build and test pushes/PRs..github/workflows/nuget.yml— build, test, pack and publish tags matchingv*.
Create a GitHub Actions secret named NUGET_API_KEY, then push a version tag such as v0.1.0 to run the publishing workflow.
Roadmap
Future phases are intended to add more providers and provider-neutral data movement between database types without changing application models or the repository CRUD API.
License
MIT. See LICENSE.
| 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
- SimpleORM.Net (>= 0.1.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.