|
| 1 | +# ActiveForge ORM — v1.0.0 |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Packages |
| 8 | + |
| 9 | +| Package | Description | NuGet | |
| 10 | +|---------|-------------|-------| |
| 11 | +| `ActiveForge.Core` | Provider-agnostic core — entities, fields, query terms, LINQ, transactions, UoW, DI | [](https://badge.fury.io/nu/ActiveForge.Core) | |
| 12 | +| `ActiveForge.SqlServer` | SQL Server provider via `Microsoft.Data.SqlClient` | [](https://badge.fury.io/nu/ActiveForge.SqlServer) | |
| 13 | +| `ActiveForge.PostgreSQL` | PostgreSQL provider via Npgsql | [](https://badge.fury.io/nu/ActiveForge.PostgreSQL) | |
| 14 | +| `ActiveForge.MongoDB` | MongoDB provider with BSON mapping and aggregation pipeline join support | [](https://badge.fury.io/nu/ActiveForge.MongoDB) | |
| 15 | +| `ActiveForge.SQLite` | SQLite provider via `Microsoft.Data.Sqlite`, including in-memory database support | [](https://badge.fury.io/nu/ActiveForge.SQLite) | |
| 16 | + |
| 17 | +```shell |
| 18 | +dotnet add package ActiveForge.Core |
| 19 | +dotnet add package ActiveForge.SqlServer # SQL Server |
| 20 | +dotnet add package ActiveForge.PostgreSQL # PostgreSQL |
| 21 | +dotnet add package ActiveForge.MongoDB # MongoDB |
| 22 | +dotnet add package ActiveForge.SQLite # SQLite / in-memory testing |
| 23 | +``` |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +## What's included |
| 28 | + |
| 29 | +### Entities & Mapping |
| 30 | + |
| 31 | +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. |
| 32 | + |
| 33 | +**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. |
| 34 | + |
| 35 | +**Attributes** control the mapping between entity fields and the underlying store: |
| 36 | + |
| 37 | +| Attribute | Effect | |
| 38 | +|-----------|--------| |
| 39 | +| `[Table]` | Maps the class to a named table / collection | |
| 40 | +| `[Column]` | Maps a field to a named column / BSON field | |
| 41 | +| `[Identity]` | Marks the primary key; auto-populated after insert | |
| 42 | +| `[ReadOnly]` | Included in SELECT but never written | |
| 43 | +| `[NoPreload]` | Excluded from the default SELECT; loaded on demand via `FieldSubset` | |
| 44 | +| `[DefaultValue]` | Pre-populates the field on construction | |
| 45 | +| `[Encrypted]` | Transparent encrypt / decrypt at the ORM layer | |
| 46 | +| `[Sensitive]` | Masks the value in diagnostic output | |
| 47 | + |
| 48 | +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. |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +### Querying |
| 53 | + |
| 54 | +**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`. |
| 55 | + |
| 56 | +**LINQ support** — `conn.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: |
| 57 | + |
| 58 | +```csharp |
| 59 | +conn.Query(new Product(conn)) |
| 60 | + .Where(p => p.Category.Name == "Electronics" && p.Price < 500m) |
| 61 | + .OrderBy(p => p.Category.Name) |
| 62 | + .ThenBy(p => p.Price) |
| 63 | + .Skip(0).Take(20) |
| 64 | + .ToList(); |
| 65 | +``` |
| 66 | + |
| 67 | +**Join type overrides** — class-level join types (`INNER JOIN` / `LEFT OUTER JOIN`) can be overridden per query without changing the entity definition: |
| 68 | + |
| 69 | +```csharp |
| 70 | +conn.Query(new Product(conn)) |
| 71 | + .LeftOuterJoin<Category>() |
| 72 | + .Where(p => p.Category.Name == "Electronics") |
| 73 | + .ToList(); |
| 74 | +``` |
| 75 | + |
| 76 | +**Pagination** — `QueryPage` returns results with total-count metadata. **Lazy streaming** — `LazyQueryAll` yields rows one at a time for memory-efficient processing of large result sets. |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +### Transactions & Unit of Work |
| 81 | + |
| 82 | +ActiveForge provides three complementary ways to manage transactions: |
| 83 | + |
| 84 | +**`With.Transaction`** — wrap an ad-hoc block of work: |
| 85 | +```csharp |
| 86 | +With.Transaction(uow, () => |
| 87 | +{ |
| 88 | + order.Status.SetValue("Shipped"); |
| 89 | + order.Update(RecordLock.UpdateOption.IgnoreLock); |
| 90 | + shipment.Insert(); |
| 91 | +}); |
| 92 | +``` |
| 93 | + |
| 94 | +**`IUnitOfWork`** — fine-grained programmatic control with `CreateTransaction`, `Commit`, and `Rollback`. Nesting is depth-tracked; only the outermost scope commits to the database. |
| 95 | + |
| 96 | +**`[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. |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +### Dependency Injection & Service Proxy |
| 101 | + |
| 102 | +ActiveForge integrates with any .NET DI host (ASP.NET Core, Worker Service, console) through provider-specific `IServiceCollection` extension methods. |
| 103 | + |
| 104 | +**One-call registration:** |
| 105 | +```csharp |
| 106 | +builder.Services |
| 107 | + .AddActiveForgeSqlServer("Server=.;Database=Demo;Integrated Security=True;") |
| 108 | + .AddServices(typeof(Program).Assembly); // auto-scan for IService implementations |
| 109 | +``` |
| 110 | + |
| 111 | +**`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. |
| 112 | + |
| 113 | +**`ActiveForgeServiceFactory`** — available for standalone (non-DI) scenarios where a proxy is needed without a container. |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +### Provider highlights |
| 118 | + |
| 119 | +#### SQL Server |
| 120 | +- Full ADO.NET adapter layer over `Microsoft.Data.SqlClient` 5.2.1 |
| 121 | +- `SELECT @@IDENTITY` for identity retrieval (compatible with `sp_executesql` execution scope) |
| 122 | +- Pessimistic locking support |
| 123 | + |
| 124 | +#### PostgreSQL |
| 125 | +- Full Npgsql 8.0.3 adapter layer |
| 126 | +- `RETURNING` clause for identity retrieval after insert |
| 127 | +- Pessimistic locking with `FOR UPDATE` semantics |
| 128 | + |
| 129 | +#### MongoDB |
| 130 | +- Reflection-based BSON mapping — no serializer attributes or configuration required |
| 131 | +- `QueryTerm` predicates translate to MongoDB `FilterDefinition` (`$eq`, `$in`, `$gte`, `$lte`, regex for `LikeTerm`) |
| 132 | +- Automatic `$lookup` + `$unwind` aggregation pipeline for embedded Record fields (joins) |
| 133 | +- Auto-increment integer IDs via a `__activeforge_counters` collection; `[Identity]` maps to `_id` |
| 134 | +- Multi-document transaction support via `MongoUnitOfWork` |
| 135 | + |
| 136 | +#### SQLite |
| 137 | +- Full `Microsoft.Data.Sqlite` 8.0.0 adapter layer |
| 138 | +- In-memory database support (`Data Source=:memory:`) — ideal for fast integration tests without a server |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +## Target Frameworks |
| 143 | + |
| 144 | +| Package | Frameworks | |
| 145 | +|---------|-----------| |
| 146 | +| `ActiveForge.Core` | `net8.0` · `net9.0` · `net10.0` · `net472` · `netstandard2.0` · `netstandard2.1` | |
| 147 | +| `ActiveForge.SqlServer` | `net8.0` · `net9.0` · `net10.0` · `net472` · `netstandard2.0` · `netstandard2.1` | |
| 148 | +| `ActiveForge.PostgreSQL` | `net8.0` · `net9.0` · `net10.0` _(limited by Npgsql 8)_ | |
| 149 | +| `ActiveForge.SQLite` | `net8.0` · `net9.0` · `net10.0` · `netstandard2.0` · `netstandard2.1` | |
| 150 | +| `ActiveForge.MongoDB` | `net8.0` · `net9.0` · `net10.0` · `net472` · `netstandard2.0` · `netstandard2.1` | |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +## Test coverage |
| 155 | + |
| 156 | +**679 tests — 0 failures** across all five providers. |
| 157 | + |
| 158 | +| Suite | Tests | |
| 159 | +|-------|------:| |
| 160 | +| ActiveForge.Tests (Core) | 340 | |
| 161 | +| ActiveForge.SqlServer.Tests | 80 | |
| 162 | +| ActiveForge.PostgreSQL.Tests | 81 | |
| 163 | +| ActiveForge.MongoDB.Tests | 126 | |
| 164 | +| ActiveForge.SQLite.Tests | 52 | |
| 165 | + |
| 166 | +Each provider suite covers full CRUD, join queries, pagination, transactions, and Unit of Work lifecycle. |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## Documentation |
| 171 | + |
| 172 | +| Guide | | |
| 173 | +|-------|-| |
| 174 | +| [Getting Started](docs/getting-started.md) | Install, connect, define entities, run your first query | |
| 175 | +| [Field Types](docs/field-types.md) | All `TField` types, operators, and conversion behaviour | |
| 176 | +| [Query Builder](docs/query-builder.md) | Composing `WHERE`, `ORDER BY`, pagination, and joins | |
| 177 | +| [Transactions & DI](docs/transactions-and-di.md) | `IUnitOfWork`, `With.Transaction`, `[Transaction]`, service proxies | |
| 178 | +| [LINQ Querying](docs/linq-querying.md) | `conn.Query<T>()`, cross-join predicates, join overrides | |
| 179 | +| [Field Subsets](docs/field-subsets.md) | Partial fetches and partial updates | |
| 180 | +| [Advanced](docs/advanced.md) | Encryption, custom mappers, polymorphism | |
| 181 | +| [Wiki](https://github.com/CodeShayk/ActiveForge/wiki) | Comprehensive reference with examples | |
| 182 | + |
| 183 | +--- |
| 184 | + |
| 185 | +## Contributing |
| 186 | + |
| 187 | +Bug reports, feature requests, and pull requests are welcome — please see [CONTRIBUTING.md](Contribution_Guidelines.md) for guidelines. |
| 188 | + |
| 189 | +--- |
| 190 | + |
| 191 | +**Full commit history:** https://github.com/CodeShayk/ActiveForge/commits/v1.0.0 |
0 commit comments