Skip to content

Release/v1.0.0#1

Merged
NinjaRocks merged 6 commits intomasterfrom
release/v1.0.0
Mar 13, 2026
Merged

Release/v1.0.0#1
NinjaRocks merged 6 commits intomasterfrom
release/v1.0.0

Conversation

@NinjaRocks
Copy link
Member

ActiveForge ORM — v1.0.0

We are excited to announce the first release of ActiveForge — a lightweight, Active Record-style ORM for .NET ported and modernised from a proven .NET 3.5 foundation. ActiveForge brings type-safe persistence, composable querying, LINQ support, nested transactions, and DI-friendly service proxying to .NET 8 through .NET 10, with provider support for SQL Server, PostgreSQL, MongoDB, and SQLite.


Packages

Package Description NuGet
ActiveForge.Core Provider-agnostic core — entities, fields, query terms, LINQ, transactions, UoW, DI NuGet
ActiveForge.SqlServer SQL Server provider via Microsoft.Data.SqlClient NuGet
ActiveForge.PostgreSQL PostgreSQL provider via Npgsql NuGet
ActiveForge.MongoDB MongoDB provider with BSON mapping and aggregation pipeline join support NuGet
ActiveForge.SQLite SQLite provider via Microsoft.Data.Sqlite, including in-memory database support NuGet
dotnet add package ActiveForge.Core
dotnet add package ActiveForge.SqlServer    # SQL Server
dotnet add package ActiveForge.PostgreSQL   # PostgreSQL
dotnet add package ActiveForge.MongoDB      # MongoDB
dotnet add package ActiveForge.SQLite       # SQLite / in-memory testing

What's included

Entities & Mapping

Entities follow the Active Record pattern — state and persistence logic live together in the same class, eliminating the need for a separate repository layer. Entities are defined once and work across all providers without modification.

Type-safe fields replace plain CLR properties with wrapper types (TString, TInt, TDecimal, TBool, TDateTime, TForeignKey, and 20+ more). Each field tracks its own nullability and dirty state, handles value conversion automatically, and participates in change detection for partial updates.

Attributes control the mapping between entity fields and the underlying store:

Attribute Effect
[Table] Maps the class to a named table / collection
[Column] Maps a field to a named column / BSON field
[Identity] Marks the primary key; auto-populated after insert
[ReadOnly] Included in SELECT but never written
[NoPreload] Excluded from the default SELECT; loaded on demand via FieldSubset
[DefaultValue] Pre-populates the field on construction
[Encrypted] Transparent encrypt / decrypt at the ORM layer
[Sensitive] Masks the value in diagnostic output

Additional mapping capabilities: polymorphic mapping (base type resolved to concrete subtype at runtime), custom field mappers for non-standard type conversions, and field subsets for loading or updating only a named subset of columns.


Querying

QueryTerm API — build queries by composing predicate objects: EqualTerm, ContainsTerm, LikeTerm, NullTerm, RangeTerm, GreaterOrEqualTerm, LessOrEqualTerm, and their logical combinations. Sort with OrderAscending / OrderDescending including multi-column CombinedSortOrder.

LINQ supportconn.Query<T>() returns an IQueryable<T> that translates Where, OrderBy, ThenBy, Take, and Skip into native ORM operations. Cross-join predicates and sort are fully supported — filter or order by a field on an embedded (joined) entity directly in the lambda:

conn.Query(new Product(conn))
    .Where(p => p.Category.Name == "Electronics" && p.Price < 500m)
    .OrderBy(p => p.Category.Name)
    .ThenBy(p => p.Price)
    .Skip(0).Take(20)
    .ToList();

Join type overrides — class-level join types (INNER JOIN / LEFT OUTER JOIN) can be overridden per query without changing the entity definition:

conn.Query(new Product(conn))
    .LeftOuterJoin<Category>()
    .Where(p => p.Category.Name == "Electronics")
    .ToList();

PaginationQueryPage returns results with total-count metadata. Lazy streamingLazyQueryAll yields rows one at a time for memory-efficient processing of large result sets.


Transactions & Unit of Work

ActiveForge provides three complementary ways to manage transactions:

With.Transaction — wrap an ad-hoc block of work:

With.Transaction(uow, () =>
{
    order.Status.SetValue("Shipped");
    order.Update(RecordLock.UpdateOption.IgnoreLock);
    shipment.Insert();
});

IUnitOfWork — fine-grained programmatic control with CreateTransaction, Commit, and Rollback. Nesting is depth-tracked; only the outermost scope commits to the database.

[Transaction] attribute — declarative demarcation via Castle DynamicProxy. Applying the attribute to a service method causes the proxy to open the connection, begin the transaction, commit on success, and roll back and close the connection on exception — with no virtual methods or base class coupling required.


Dependency Injection & Service Proxy

ActiveForge integrates with any .NET DI host (ASP.NET Core, Worker Service, console) through provider-specific IServiceCollection extension methods.

One-call registration:

builder.Services
    .AddActiveForgeSqlServer("Server=.;Database=Demo;Integrated Security=True;")
    .AddServices(typeof(Program).Assembly);   // auto-scan for IService implementations

IService marker — implement IService on any class to opt it into auto-discovery. AddServices() scans the supplied assemblies, registers each implementation against its non-system interfaces, and wraps it in a Castle interface proxy that activates [Transaction] and [ConnectionScope] interceptors transparently.

ActiveForgeServiceFactory — available for standalone (non-DI) scenarios where a proxy is needed without a container.


Provider highlights

SQL Server

  • Full ADO.NET adapter layer over Microsoft.Data.SqlClient 5.2.1
  • SELECT @@IDENTITY for identity retrieval (compatible with sp_executesql execution scope)
  • Pessimistic locking support

PostgreSQL

  • Full Npgsql 8.0.3 adapter layer
  • RETURNING clause for identity retrieval after insert
  • Pessimistic locking with FOR UPDATE semantics

MongoDB

  • Reflection-based BSON mapping — no serializer attributes or configuration required
  • QueryTerm predicates translate to MongoDB FilterDefinition ($eq, $in, $gte, $lte, regex for LikeTerm)
  • Automatic $lookup + $unwind aggregation pipeline for embedded Record fields (joins)
  • Auto-increment integer IDs via a __activeforge_counters collection; [Identity] maps to _id
  • Multi-document transaction support via MongoUnitOfWork

SQLite

  • Full Microsoft.Data.Sqlite 8.0.0 adapter layer
  • In-memory database support (Data Source=:memory:) — ideal for fast integration tests without a server

Target Frameworks

Package Frameworks
ActiveForge.Core net8.0 · net9.0 · net10.0 · net472 · netstandard2.0 · netstandard2.1
ActiveForge.SqlServer net8.0 · net9.0 · net10.0 · net472 · netstandard2.0 · netstandard2.1
ActiveForge.PostgreSQL net8.0 · net9.0 · net10.0 (limited by Npgsql 8)
ActiveForge.SQLite net8.0 · net9.0 · net10.0 · netstandard2.0 · netstandard2.1
ActiveForge.MongoDB net8.0 · net9.0 · net10.0 · net472 · netstandard2.0 · netstandard2.1

Test coverage

679 tests — 0 failures across all five providers.

Suite Tests
ActiveForge.Tests (Core) 340
ActiveForge.SqlServer.Tests 80
ActiveForge.PostgreSQL.Tests 81
ActiveForge.MongoDB.Tests 126
ActiveForge.SQLite.Tests 52

Each provider suite covers full CRUD, join queries, pagination, transactions, and Unit of Work lifecycle.


Documentation

Guide
Getting Started Install, connect, define entities, run your first query
Field Types All TField types, operators, and conversion behaviour
Query Builder Composing WHERE, ORDER BY, pagination, and joins
Transactions & DI IUnitOfWork, With.Transaction, [Transaction], service proxies
LINQ Querying conn.Query<T>(), cross-join predicates, join overrides
Field Subsets Partial fetches and partial updates
Advanced Encryption, custom mappers, polymorphism
Wiki Comprehensive reference with examples

Contributing

Bug reports, feature requests, and pull requests are welcome — please see CONTRIBUTING.md for guidelines.


Full commit history: https://github.com/CodeShayk/ActiveForge/commits/v1.0.0

@NinjaRocks NinjaRocks merged commit bced945 into master Mar 13, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant