Skip to content

Commit 7dede66

Browse files
committed
- fix test containers in ci
1 parent e014bfe commit 7dede66

3 files changed

Lines changed: 244 additions & 0 deletions

File tree

.github/workflows/release-ci.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,34 @@ jobs:
2020
contents: read
2121
packages: write
2222

23+
services:
24+
sqlserver:
25+
image: mcr.microsoft.com/mssql/server:2022-latest
26+
env:
27+
ACCEPT_EULA: "Y"
28+
MSSQL_SA_PASSWORD: "Pa55w0rd!"
29+
ports:
30+
- 1433:1433
31+
options: >-
32+
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Pa55w0rd!' -No -Q 'SELECT 1'"
33+
--health-interval 10s
34+
--health-timeout 5s
35+
--health-retries 10
36+
37+
postgres:
38+
image: postgres:16-alpine
39+
env:
40+
POSTGRES_USER: postgres
41+
POSTGRES_PASSWORD: Pa55w0rd
42+
POSTGRES_DB: postgres
43+
ports:
44+
- 5455:5432
45+
options: >-
46+
--health-cmd pg_isready
47+
--health-interval 10s
48+
--health-timeout 5s
49+
--health-retries 5
50+
2351
steps:
2452
- name: Checkout
2553
uses: actions/checkout@v4
@@ -66,7 +94,29 @@ jobs:
6694
- name: Build
6795
run: dotnet build ActiveForge.sln --configuration Release --no-restore
6896

97+
- name: Start MongoDB replica set
98+
run: |
99+
docker run -d --name mongo \
100+
-p 27017:27017 \
101+
mongo:7 --replSet rs0 --bind_ip_all
102+
# Wait for mongod to accept connections
103+
for i in $(seq 1 30); do
104+
docker exec mongo mongosh --quiet --eval "db.adminCommand('ping')" \
105+
&& break || sleep 2
106+
done
107+
# Initialise the single-node replica set
108+
docker exec mongo mongosh --quiet --eval \
109+
"rs.initiate({_id:'rs0',members:[{_id:0,host:'127.0.0.1:27017'}]})"
110+
# Wait until the node becomes PRIMARY
111+
for i in $(seq 1 20); do
112+
STATUS=$(docker exec mongo mongosh --quiet --eval "rs.status().myState")
113+
[ "$STATUS" = "1" ] && break || sleep 2
114+
done
115+
69116
- name: Test
117+
env:
118+
SS_ADMIN_CONNSTR: "Server=localhost,1433;Database=master;User Id=sa;Password=Pa55w0rd!;TrustServerCertificate=True"
119+
PG_ADMIN_CONNSTR: "Host=localhost;Port=5455;Database=postgres;Username=postgres;Password=Pa55w0rd"
70120
run: dotnet test ActiveForge.sln --configuration Release --no-build --framework net8.0
71121

72122
- name: Pack — Core

ActiveForge.sln

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{45231C
4242
ProjectSection(SolutionItems) = preProject
4343
.gitignore = .gitignore
4444
.github\workflows\master-build.yml = .github\workflows\master-build.yml
45+
.github\workflows\master-codeql.yml = .github\workflows\master-codeql.yml
46+
.github\workflows\release-ci.yml = .github\workflows\release-ci.yml
47+
.github\workflows\release-codeql.yml = .github\workflows\release-codeql.yml
4548
EndProjectSection
4649
EndProject
4750
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{497629F0-306C-4BC9-99D1-B5C6EA2B2E6F}"

docs/v1.0.0 release-notes.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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 | [![NuGet](https://badge.fury.io/nu/ActiveForge.Core.svg)](https://badge.fury.io/nu/ActiveForge.Core) |
12+
| `ActiveForge.SqlServer` | SQL Server provider via `Microsoft.Data.SqlClient` | [![NuGet](https://badge.fury.io/nu/ActiveForge.SqlServer.svg)](https://badge.fury.io/nu/ActiveForge.SqlServer) |
13+
| `ActiveForge.PostgreSQL` | PostgreSQL provider via Npgsql | [![NuGet](https://badge.fury.io/nu/ActiveForge.PostgreSQL.svg)](https://badge.fury.io/nu/ActiveForge.PostgreSQL) |
14+
| `ActiveForge.MongoDB` | MongoDB provider with BSON mapping and aggregation pipeline join support | [![NuGet](https://badge.fury.io/nu/ActiveForge.MongoDB.svg)](https://badge.fury.io/nu/ActiveForge.MongoDB) |
15+
| `ActiveForge.SQLite` | SQLite provider via `Microsoft.Data.Sqlite`, including in-memory database support | [![NuGet](https://badge.fury.io/nu/ActiveForge.SQLite.svg)](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

Comments
 (0)