SimpleRelm
SimpleRelm is a lightweight, attribute-based ORM / data access layer for C# and .NET Framework developers who want a small, predictable alternative to heavyweight ORMs.
It sits close to ADO.NET, but gives you:
- Strongly-typed table/column access via attributes and expressions
- A simple context model (
RelmContext/RelmQuickContext) - Helper methods for the common database shapes you actually use
- A clean, explicit pattern for transactions and error handling
It’s especially aimed at .NET Framework apps and services that need something quick and focused, but it also works from modern .NET projects.
⚠️ Status: SimpleRelm is under active development. APIs and examples may evolve as features are added and real-world scenarios are covered. Feedback, issues, and PRs are welcome.
Documentation: https://jdaugherty-bdl.github.io/SimpleRelm/index.html
Features
Lightweight ORM
Thin abstraction on top of ADO.NET to keep things transparent and predictable.POCO-friendly mapping
Map query results directly into your own C# classes without invasive attributes or base classes.Explicit transactions
You stay in control of when transactions begin, commit, and roll back — no hidden magic.using-friendly API
Designed to be used in ausingblock so connections/transactions are cleaned up correctly.Framework & Core friendly
Built with traditional .NET Framework apps in mind, but usable from modern .NET (Core) projects as well.Actively evolving
New features and refinements are being added as the library is used in real projects.
Key concepts
SimpleRelm revolves around a few core pieces:
Models
Inherit fromRelmModeland decorate with attributes such as:[RelmTable("example_models")][RelmColumn]/[RelmColumn("actual_column_name")][RelmForeignKey(...)]
using SimpleRelm.Attributes; using SimpleRelm.Models; [RelmTable("example_models")] internal class ExampleModel : RelmModel { [RelmColumn] public string GroupInternalId { get; set; } // group_InternalId [RelmColumn] public string ModelName { get; set; } // model_name [RelmColumn] public int ModelIndex { get; set; } // model_index [RelmColumn("bool_column")] public bool IsBoolColumn { get; set; } // bool_column [RelmForeignKey( ForeignKey: nameof(ExampleGroup.InternalId), LocalKey: nameof(GroupInternalId))] public virtual ExampleGroup Group { get; set; } }Contexts
You create your own context classes that inherit from:RelmContext– eager: preloads table metadata up-frontRelmQuickContext– lazy: loads metadata on first use
and expose your datasets as
IRelmDataSetproperties:internal class ExampleContext : RelmContext { public ExampleContext( bool autoOpenConnection = true, bool autoOpenTransaction = false, bool allowUserVariables = false, bool convertZeroDateTime = false, int lockWaitTimeoutSeconds = 0) : base("name=ExampleContextDatabase", autoOpenConnection: autoOpenConnection, autoOpenTransaction: autoOpenTransaction, allowUserVariables: allowUserVariables, convertZeroDateTime: convertZeroDateTime, lockWaitTimeoutSeconds: lockWaitTimeoutSeconds) { } public IRelmDataSet ExampleModels { get; set; } public IRelmDataSet ExampleGroups { get; set; } }Helpers & interfaces
TheRelmHelperstatic class andIRelmContext/IRelmQuickContextinterfaces provide the main API surface:RelmHelper.GetDalTable()RelmHelper.GetColumnName(x => x.SomeProperty)RelmHelper.StandardConnectionWrapper(...)RelmHelper.DoDatabaseWork(...)RelmHelper.GetDataObject(...) / GetDataObjects(...)RelmHelper.GetLastInsertId(...)RelmHelper.GetIdFromInternalId(...)
Most of these also exist as instance methods on
IRelmContextandIRelmQuickContext(relmContext.DoDatabaseWork(...), etc.).
Features
From the Quickstart examples, SimpleRelm currently provides:
Attribute-based mapping
[RelmTable],[RelmColumn],[RelmForeignKey]give you strongly-typed access to table and column names without scattering strings everywhere.RelmHelper.GetDalTable()andRelmHelper.GetColumnName(...)use those attributes.
Two context modes
RelmContext– reads the database and preloads datasets/metadata when created (slightly heavier startup, faster subsequent operations).RelmQuickContext– lazy-loads metadata as needed (faster startup, first operations may be slower).
Standard connection wrapper
RelmHelper.StandardConnectionWrapper(...)lets you run a lambda with a rawconnectionandtransactionwithout manually wiring up boilerplate.
Data access helpers
DoDatabaseWorkfor command/query execution (with or without parameters, with or without return values).GetDataObject/GetDataObjectsfor object-shaped results.- Additional examples show DataRow/DataTable/DataList usage.
Identity helpers
GetLastInsertIdto retrieve the last auto-increment ID.GetIdFromInternalIdto resolve IDs from internal GUIDs.
Bulk operations
BulkTableWriterExamplesdemonstrates writing multiple rows efficiently via the bulk writer helpers.
Explicit transaction handling
autoOpenTransaction: trueplus atry/catchpattern where you explicitly callRollbackTransactions()on failure.
Concrete usage for all of these lives under:
examples/SimpleRelm.Quickstart
Getting started
1. Add SimpleRelm to your solution
Right now the library is consumed as a project reference:
- Clone this repository.
- Add the
SimpleRelmproject to your solution. - Add a reference from your application to the
SimpleRelmproject.
(When/if a NuGet package is published, this can become a simple dotnet add package SimpleRelm step.)
2. Define a model and context
Use attributes on your models and expose IRelmDataSet properties on a context that inherits from RelmContext or RelmQuickContext.
The Quickstart project includes:
Models/ExampleModel.csModels/ExampleGroup.csContexts/ExampleContext.csContexts/ExampleQuickContext.cs
These show the expected pattern end-to-end.
Usage patterns
Using RelmContext vs RelmQuickContext
The Quickstart Program.cs demonstrates both:
// Relm Context: preloads datasets and metadata
using (var relmContext = new ExampleContext())
{
var identityExamples = new Examples.Identity.IdentityExamples();
var dataRowExamples = new Examples.Data.DataRowExamples();
var dataTableExamples = new Examples.Data.DataTableExamples();
var dataObjectExamples = new Examples.Data.DataObjectExamples();
var dataListExamples = new Examples.Data.DataListExamples();
identityExamples.RunExamples(relmContext);
dataRowExamples.RunExamples(relmContext);
dataTableExamples.RunExamples(relmContext);
dataObjectExamples.RunExamples(relmContext);
dataListExamples.RunExamples(relmContext);
}
// Relm Quick Context: lazy-loads metadata on first use
using (var relmQuickContext = new ExampleQuickContext())
{
var identityExamples = new Examples.Identity.IdentityExamples();
var dataRowExamples = new Examples.Data.DataRowExamples();
var dataTableExamples = new Examples.Data.DataTableExamples();
var dataObjectExamples = new Examples.Data.DataObjectExamples();
var dataListExamples = new Examples.Data.DataListExamples();
identityExamples.RunExamples(relmQuickContext);
dataRowExamples.RunExamples(relmQuickContext);
dataTableExamples.RunExamples(relmQuickContext);
dataObjectExamples.RunExamples(relmQuickContext);
dataListExamples.RunExamples(relmQuickContext);
}
Recommended transaction pattern
When you want SimpleRelm to manage a transaction for you, pass autoOpenTransaction: true and explicitly roll back on error:
using (var relmContext = new ExampleContext(autoOpenTransaction: true))
{
try
{
var databaseWorkExamples = new Examples.Data.DatabaseWorkExamples();
var bulkTableWriteExamples = new Examples.BulkWriter.BulkTableWriterExamples();
databaseWorkExamples.RunExamples(relmContext);
bulkTableWriteExamples.RunExamples(relmContext);
// On success, the transaction is allowed to complete normally.
}
catch (Exception ex)
{
// On failure, explicitly roll back any open transactions.
relmContext.RollbackTransactions();
Console.WriteLine($"An error occurred: {ex.Message}");
throw;
}
}
The same pattern is also shown with ExampleQuickContext in the Quickstart.
If you just need quick, one-off access to a connection/transaction without a full context, you can use:
using static SimpleRelm.Quickstart.Enums.ConnectionStrings;
var result = RelmHelper.StandardConnectionWrapper(
ConnectionStringTypes.ExampleContextDatabase,
(connection, transaction) =>
{
// Use the connection and transaction as needed
return true;
},
ExceptionHandler: (exception, st) =>
{
Console.WriteLine($"An error occurred: {exception.Message}");
});
For more detailed examples (DataRow/DataTable/DataObject/DataList, identity helpers, bulk writer, etc.), see the files under:
examples/SimpleRelm.Quickstart/Examples