JobTech.Forte.Data
3.0.3
dotnet add package JobTech.Forte.Data --version 3.0.3
NuGet\Install-Package JobTech.Forte.Data -Version 3.0.3
<PackageReference Include="JobTech.Forte.Data" Version="3.0.3" />
<PackageVersion Include="JobTech.Forte.Data" Version="3.0.3" />
<PackageReference Include="JobTech.Forte.Data" />
paket add JobTech.Forte.Data --version 3.0.3
#r "nuget: JobTech.Forte.Data, 3.0.3"
#:package JobTech.Forte.Data@3.0.3
#addin nuget:?package=JobTech.Forte.Data&version=3.0.3
#tool nuget:?package=JobTech.Forte.Data&version=3.0.3
Forte 
Project Overview
Forte (JobTech.Forte.Data) is a C# ORM library — Fast Object Relational Translation Engine — that provides a generic Repository pattern over ADO.NET. It uses convention-based stored procedure mapping and compiled expression trees for high performance, eliminating runtime reflection after the first call for any given type.
Modern .NET projects often reach for a full-featured ORM like Entity Framework Core, NHibernate, or even the lighter Dapper. Each is a solid choice — but each comes with trade-offs that Forte is designed to avoid.
Performance
Forte compiles all property access, object construction, and method invocation into delegates on first use. After the initial call for any given type, there is zero reflection overhead at runtime. Compare this to:
- Entity Framework Core — change tracking, query translation, and the LINQ pipeline add measurable overhead even for simple reads. EF is optimized for developer ergonomics, not raw throughput.
- NHibernate — a powerful but heavyweight framework with session management, proxies, and a significant startup cost.
- Dapper — lightweight and fast, but it relies on
ILGenerator-based emit at runtime. Forte's compiled expression tree approach is equally fast and more transparent.
Forte is purpose-built for high-throughput scenarios where stored procedures are already in use and you need mapping to be as close to hand-rolled ADO.NET speed as possible.
Lightweight and Elegant
The entire library is a single project with one public class (Repository<T>) and a single NuGet dependency (System.Configuration.ConfigurationManager). There is no migration engine, no change tracker, no model builder, no code-generation tooling, and no design-time assembly.
- EF Core ships with dozens of packages (
Microsoft.EntityFrameworkCore,*Design,*SqlServer,*Tools, etc.) and requires aDbContextsubclass, entity configuration, and often migrations. - NHibernate requires mapping files or attribute decoration, a
SessionFactory, and anISessionlifetime to manage. - With Forte, drop in the NuGet package, call
Configure()once, and write a stored procedure that matches the naming convention. That's it.
Simplicity and Extensibility
Convention over configuration means no mapping attributes, no fluent configuration, no XML. If your stored procedure is named SelectCustomer, Forte finds it automatically. Need to customize parameter binding or result mapping? Override a single virtual method in your repository subclass:
// Custom input binding — override just what you need
protected override void BindInput(DbCommand cmd, IDictionary parameters)
{
base.BindInput(cmd, parameters);
cmd.Parameters["@AsOfDate"].Value = DateTime.UtcNow;
}
This is in contrast to EF Core's IEntityTypeConfiguration<T> fluent API or NHibernate's ClassMap<T>, both of which require learning a separate configuration DSL before writing a single query.
Open Source and NuGet Access
Forte is MIT-licensed and published to NuGet as JobTech.Forte.Data. There are no commercial tiers, no licensing restrictions, and no runtime fees. The full source is auditable in a single directory — useful in regulated environments where dependency vetting is required.
| Forte | Dapper | EF Core | NHibernate | |
|---|---|---|---|---|
| Runtime reflection after first call | No | Minimal | Yes | Yes |
| NuGet dependencies | 1 | 1 | 10+ | 10+ |
| Requires stored procedures | Yes | No | No | No |
| Change tracking | No | No | Yes | Yes |
| Migration tooling | No | No | Yes | Yes |
| Learning curve | Low | Low | Medium | High |
Forte is the right choice when your data access layer is already built around stored procedures, you need predictable performance, and you want a thin mapping layer with no magic.
A Short Example
Consider the following simplified console application making use of Forte to retrieve a list of customers.
public class Customer
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
using JobTech.Forte.Data;
using System.Configuration;
class Program
{
// Note: alternatively this can be placed in appsettings if using configuration builder.
var connSettings = new ConnectionStringSettingsCollection()
{
{ new ConnectionStringSettings("Default", "Server=<server>;Database=<db>;Trusted_Connection=True;") }
};
// Call once at startup.
RepositoryService.Configure(connSettings);
static void Main(string[] args)
{
var repository = new Repository<Customer>();
var results = repository.SelectAsync().Result;
foreach (var customer in results)
{
Console.WriteLine("First: " + customer.FirstName);
Console.WriteLine("Last: " + customer.LastName);
}
}
}
...and the SQL
CREATE TABLE Customer
(
ID int IDENTITY(1,1) NOT NULL,
LastName varchar(100) NOT NULL,
FirstName varchar(100) NOT NULL
)
CREATE PROCEDURE SelectCustomer
AS
BEGIN
SELECT ID,
FirstName,
LastName
FROM Customer
END
Deriving from Repository<T>
The example above uses Repository<T> directly, which works for simple cases. In a real application you will almost always want to create your own base class that derives from Repository<T>. This gives you a single place to inject cross-cutting concerns — authentication context, cancellation, audit parameters, logging — shared across every repository in your project.
public class RepositoryDerived<T> : Repository<T>
{
public ClaimsIdentity Identity { get; set; }
public CancellationToken CancelToken { get; set; }
public RepositoryDerived()
{
this.CancelToken = default;
}
// Add a shared output parameter to every SelectAsync call
protected override void BindInput(DbCommand cmd, IDictionary parameters)
{
base.BindInput(cmd, parameters);
var rowCount = cmd.CreateParameter();
rowCount.ParameterName = nameof(rowCount);
rowCount.Direction = ParameterDirection.Output;
rowCount.DbType = DbType.Int32;
cmd.Parameters.Add(rowCount);
}
// Wrap ExecuteAsync to inject identity and return the mutated item
public virtual async Task<IList<T>> ExecuteAsync(T item,
TransactionType transactionType = TransactionType.Insert, IDictionary parameters = null)
{
try
{
parameters ??= new Dictionary<string, object>();
if (this.Identity != null)
{
parameters[nameof(Identity.Name)] = this.Identity.Name;
}
await base.ExecuteAsync(item, transactionType, parameters, this.CancelToken)
.ConfigureAwait(false);
return new List<T> { item };
}
catch (OperationCanceledException)
{
throw;
}
}
}
Key extension points shown above:
| Override | Purpose |
|---|---|
BindInput(DbCommand, IDictionary) |
Appends a shared rowCount output parameter to every SelectAsync call without touching individual repositories |
Custom ExecuteAsync |
Pulls ClaimsIdentity.Name from the instance and passes it as a named parameter, keeping auth concerns out of calling code |
CancelToken property |
Stores a CancellationToken on the instance so callers don't need to thread it through every call site |
Your concrete repositories then derive from this class, not from Repository<T> directly:
public class CustomerRepository : RepositoryDerived<Customer>
{
// Only override what is specific to Customer — everything else is inherited
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net8.0
- System.Configuration.ConfigurationManager (>= 8.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
* Target .NET 8.0 LTS