ActiveForge consists of a core library and one or more provider packages. Reference the core plus the provider for your database:
SQL Server
<ItemGroup>
<ProjectReference Include="..\ActiveForge\ActiveForge.csproj" />
<ProjectReference Include="..\ActiveForge.SqlServer\ActiveForge.SqlServer.csproj" />
</ItemGroup>PostgreSQL
<ItemGroup>
<ProjectReference Include="..\ActiveForge\ActiveForge.csproj" />
<ProjectReference Include="..\ActiveForge.PostgreSQL\ActiveForge.PostgreSQL.csproj" />
</ItemGroup>MongoDB
<ItemGroup>
<ProjectReference Include="..\ActiveForge\ActiveForge.csproj" />
<ProjectReference Include="..\ActiveForge.MongoDB\ActiveForge.MongoDB.csproj" />
</ItemGroup>SQLite
<ItemGroup>
<ProjectReference Include="..\ActiveForge\ActiveForge.csproj" />
<ProjectReference Include="..\ActiveForge.SQLite\ActiveForge.SQLite.csproj" />
</ItemGroup>If consuming published NuGet packages:
<!-- SQL Server -->
<PackageReference Include="ActiveForge" Version="1.0.0" />
<PackageReference Include="ActiveForge.SqlServer" Version="1.0.0" />
<!-- PostgreSQL -->
<PackageReference Include="ActiveForge" Version="1.0.0" />
<PackageReference Include="ActiveForge.PostgreSQL" Version="1.0.0" />
<!-- MongoDB -->
<PackageReference Include="ActiveForge" Version="1.0.0" />
<PackageReference Include="ActiveForge.MongoDB" Version="1.0.0" />
<!-- SQLite -->
<PackageReference Include="ActiveForge" Version="1.0.0" />
<PackageReference Include="ActiveForge.SQLite" Version="1.0.0" />SqlServerConnection, PostgreSQLConnection, MongoDataConnection, and SQLiteConnection all live in the ActiveForge namespace, so no extra using directive is required beyond using ActiveForge;.
Every database entity extends Record (or IdentityRecord for auto-increment PK tables).
Fields are declared as public fields of a TField subtype — not properties.
[Table("Customers")]
public class Customer : IdentityRecord
{
[Column("FirstName")] public TString FirstName = new TString();
[Column("LastName")] public TString LastName = new TString();
[Column("Email")] public TString Email = new TString();
[Column("Balance")] public TDecimal Balance = new TDecimal();
[Column("Active")] public TBool Active = new TBool();
public Customer() { }
public Customer(DataConnection conn) : base(conn) { }
}The [Table] attribute names the SQL table.
The [Column] attribute names the SQL column.
IdentityRecord adds an ID field (TPrimaryKey) with [Identity] — auto-populated after INSERT.
Every field starts as null (IsNull() == true). Setting a value clears the null state.
var c = new Customer();
c.FirstName.IsNull(); // true — never assigned
c.FirstName.SetValue("Alice");
c.FirstName.IsNull(); // false — has a value
string name = c.FirstName; // implicit conversion to string → "Alice"Fields support implicit conversion to and from their underlying CLR type:
TString s = "hello"; // string → TString
string t = new TString("x"); // TString → string
TInt n = 42; // int → TInt
int m = new TInt(7); // TInt → intSqlServerConnection is the SQL Server implementation:
using ActiveForge;
var conn = new SqlServerConnection(connectionString, new BaseFactory());
conn.Connect();
// ... use the connection ...
conn.Disconnect();Pass the connection to each entity at construction time, or set it via Target later.
Every CRUD/query call on the entity is delegated to the bound connection.
var c = new Customer(conn);
c.FirstName.SetValue("Alice");
c.LastName.SetValue("Smith");
c.Email.SetValue("alice@example.com");
c.Balance.SetValue(100m);
c.Active.SetValue(true);
bool ok = c.Insert();
// c.ID is now populated with the generated primary key
Console.WriteLine($"New customer ID: {(int)c.ID.GetValue()}");var c = new Customer(conn);
c.ID.SetValue(42);
bool found = c.Read();
if (found)
Console.WriteLine($"Found: {c.FirstName} {c.LastName}");c.Balance.SetValue(250m);
c.Update(RecordLock.UpdateOption.IgnoreLock);
// To update all fields unconditionally:
c.UpdateAll();
// To update only fields that changed since last read:
c.UpdateChanged();RecordLock.UpdateOption values:
ReleaseLock— release the optimistic lock after updateRetainLock— keep the lock (for subsequent updates in same transaction)IgnoreLock— skip all locking (simplest, use for non-concurrent tables)
// Delete by primary key (object must have ID set)
c.Delete();
// Delete by query (removes multiple rows)
var template = new Customer(conn);
var term = new EqualTerm(template, template.Active, false);
template.Delete(term);See query-builder.md for the full query API.
var template = new Customer(conn);
// All active customers
var activeFilter = new EqualTerm(template, template.Active, true);
var results = conn.QueryAll(template, activeFilter, null, 0, null);
// Count
int count = conn.QueryCount(template, activeFilter);
// First match
bool found = conn.QueryFirst(template, activeFilter, null, null);
// Page of 20, starting at record 40
var page = conn.QueryPage(template, activeFilter, null, 40, 20, null);A typical repository-style usage:
public class CustomerRepository
{
private readonly DataConnection _conn;
public CustomerRepository(DataConnection conn) => _conn = conn;
public Customer GetById(int id)
{
var c = new Customer(_conn);
c.ID.SetValue(id);
return c.Read() ? c : null;
}
public RecordCollection GetActive()
{
var template = new Customer(_conn);
var term = new EqualTerm(template, template.Active, true);
return _conn.QueryAll(template, term, null, 0, null);
}
public void Save(Customer c)
{
if (c.ID.IsNull()) c.Insert();
else c.Update(RecordLock.UpdateOption.IgnoreLock);
}
public void Delete(Customer c) => c.Delete();
}