MilliOrm.PostgreSql 1.0.0

dotnet add package MilliOrm.PostgreSql --version 1.0.0
                    
NuGet\Install-Package MilliOrm.PostgreSql -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MilliOrm.PostgreSql" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MilliOrm.PostgreSql" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="MilliOrm.PostgreSql" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MilliOrm.PostgreSql --version 1.0.0
                    
#r "nuget: MilliOrm.PostgreSql, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MilliOrm.PostgreSql@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MilliOrm.PostgreSql&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=MilliOrm.PostgreSql&version=1.0.0
                    
Install as a Cake Tool

MilliOrm.PostgreSql – Lightweight ORM for PostgreSql

MilliOrm.PostgreSql is a lightweight, fast ORM for PostgreSql designed for developers who want simplicity, performance, and clean mapping without the heavy abstractions of Entity Framework.
It provides LINQ‑style filtering, attribute‑based entity mapping, dependency‑injection support, bulk operations, and raw SQL execution — all in a minimal and intuitive API.


Features

  • Built for PostgreSql
  • Fast – optimized SQL generation with minimal overhead
  • Simple – small API surface, easy to learn
  • Optional attribute-based mapping[PrimaryKey], [AutoIncrement], [TableName]
  • Bulk operations – high‑performance bulk insert & update
  • Raw SQL support
  • Built-in dependency injection
  • LINQ expression filters

Installation

dotnet add package MilliOrm.PostgreSql

Registering MilliOrm.PostgreSql (Dependency Injection)

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMilliOrm(
    builder.Configuration.GetConnectionString("DefaultConnection"),
    pluralizeTableNames: false
);

This registers IDatabase with scoped lifetime and automatically configures the ORM.

Usage in class:

public class CustomerService
{
    private readonly IDatabase _db;

    public CustomerService(IDatabase db)
    {
        _db = db;
    }
}

Entity Mapping

[TableName("dbo.Customers")]
public class Customer
{
    [PrimaryKey]
    [AutoIncrement]
    public int Id { get; set; }

    public string Name { get; set; }
    public bool IsActive { get; set; }
    public DateTime CreatedAt { get; set; }
}

Supported attributes

Attribute Purpose
PrimaryKey Marks primary key column. Optional - required for update
AutoIncrement Identity (ID) auto-generated by Postgre. Optional - required for insert if available
TableName("Schema.Table") Overrides table name. Optional - required if table name is different from class name or schema is used

Supported expressions

  • Logical operators such as &&, ||, !
  • Greater than, less than, equal to: >, <, >=, <=, ==, !=
  • Null handling 'x == null' and 'x != null'
  • Auto-expanded bool check (e.g. c => c.IsActive becomes c.IsActive = true)
  • DateTime comparisons
  • String methods: StartsWith, EndsWith, Contains, Equals
  • Contains methods for arrays and lists: list.Contains(x.Property), works for lists, arrays, enumerables of primitives

Note: for date time comparissons initialize datetime value outside of the expression, that is use:

var cutoff = DateTime.UtcNow.AddDays(-7);
var recent = await db.GetAsync<Customer>(c => c.CreatedAt >= cutoff);

instead of:

var recent = await db.GetAsync<Customer>(c => c.CreatedAt >= DateTime.UtcNow.AddDays(-7));

NOT Supported expressions

  • Any method outside string or collection .Contains
  • Mathematical expressions
  • Indexers
  • MemberInit / New expressions
  • Navigation properties (ORM is not relational)

API Usage Examples

Querying Data

var customers = await db.GetAsync<Customer>();

var active = await db.GetAsync<Customer>(
    c => c.IsActive,
    orderBy: c => c.Name
);

var first100 = await db.GetAsync<Customer>(
    c => c.IsActive,
    top: 100,
    orderBy: c => c.CreatedAt,
    ascending: true
);

var first100WithSkip = await db.GetAsync<Customer>(
    c => c.IsActive,
    top: 100,
    orderBy: c => c.CreatedAt,
    ascending: true,
    skip: 100
);

var first = await db.GetFirstAsync<Customer>(c => c.Id > 0);

More complex example:

var currentDay = DateTime.Now;
var employees = await _database
    .GetAsync<Employee>(where: x => x.Name.Contains("Joe") && !x.IsDeleted &&
                                    (x.EmploymentEnd > currentDay || x.EmploymentEnd == null),
                        top: 10,
                        orderBy: x => x.Id
                        );

Generated SQL:

SELECT *
FROM "Employee"
WHERE (( "Name" ILIKE '%' || 'Joe' || '%' AND NOT ("IsDeleted" = 1) ) AND 
        ( "EmploymentEnd" > DATE '2025-12-02' OR "EmploymentEnd" IS NULL )
)
ORDER BY "Id" ASC
LIMIT 10;

Insert Data

var customer = new Customer {
    Name = "John",
    CreatedAt = DateTime.UtcNow,
    IsActive = true
};

var id = await db.AddAsync(customer);

Insert multiple (up to 1000 rows):

await db.AddAsync(customers);

Bulk insert:

await db.BulkAddAsync(customers);

Update Data

await db.UpdateAsync(customer);

Update multiple (up to 1000 rows):

await db.UpdateAsync(customers);

Bulk update:

await db.BulkAddAsync(customers);

Update a single column for many rows:

await db.UpdateAsync<Customer>(
    c => c.IsActive, false,
    c => c.CreatedAt < DateTime.UtcNow.AddYears(-1)
);

Delete Data

await db.DeleteAsync<Customer>(c => !c.IsActive);

Raw SQL Support

await db.ExecuteRawCommandAsync("DELETE FROM dbo.Customers WHERE Id = 1");

var list = await db.ExecuteRawQueryAsync<Customer>(
    "SELECT * FROM dbo.Customers"
);

var ids = await db.ExecuteRawQueryScalarAsync<int>(
    "SELECT Id FROM dbo.Customers"
);

Transaction support

MilliOrm.PostgreSql supports native .Net transactions. Example usage:

try
{
    using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
    {
        var Id = await _database.AddAsync(employee);

        var employeeSkill = new EmployeeSkill()
        {
            EmployeeId = Id,
            SkillName = "C# Programming",
            ProficiencyLevel = "Advanced"
        };

        await _database.AddAsync(employeeSkill);
                    
        scope.Complete();
    }
}
catch(Exception e)
{
    //Rollback is automatic if Complete is not called
}

List of all available methods

Task<List<T>> GetAsync<T>(Expression<Func<T, bool>> where = null, int? top = null, Expression<Func<T, object>> orderBy = null, bool ascending = true, int? skip = null) where T : new();
Task<T> GetFirstAsync<T>(Expression<Func<T, bool>> where = null) where T : new();
Task AddAsync<T>(List<T> items);
Task<int?> AddAsync<T>(T item);
Task<int> DeleteAsync<T>(Expression<Func<T, bool>> where = null);
Task UpdateAsync<T>(List<T> items);
Task UpdateAsync<T>(T item);
Task<int> UpdateAsync<T>(Expression<Func<T, object>> column, object value, Expression<Func<T, bool>> where = null);
Task BulkAddAsync<T>(List<T> items);
Task BulkUpdateAsync<T>(List<T> items);
Task<int> ExecuteRawCommandAsync(string sql);
Task<List<T>> ExecuteRawQueryAsync<T>(string sql) where T : new();
Task<List<T>> ExecuteRawQueryScalarAsync<T>(string sql);

Benchmarks

CPU: AMD Ryzen 7 5700U
.NET 8.0 — BenchmarkDotNet v0.15.6
Note that results are based on Dapper Plus and EF Core Bulk Extensions libraries for bulk operation. Bulk operations are included by default in MilliOrm.PostgreSql library.

Method Mean Error StdDev Median
Insert 1000 MilliOrm 7.115 ms 0.3963 ms 1.0915 ms 6.482 ms
Insert 1000 Dapper Plus 5.984 ms 0.5092 ms 1.3324 ms 5.375 ms
Insert 1000 EF Core Bulk ext 6.862 ms 0.1372 ms 0.2772 ms 6.861 ms
Insert 100 000 Milli Orm 264.136 ms 9.3938 ms 25.0739 ms 256.356 ms
Insert 100 000 Dapper Plus 211.111 ms 12.4168 ms 33.5696 ms 194.697 ms
Insert 100 000 EF Core Bulk ext 532.204 ms 47.2096 ms 129.2355 ms 482.631 ms
Method Mean Error StdDev Median
Query First MilliOrm 143.1 us 3.80 us 10.84 us 146.2 us
Query First Dapper 115.5 us 2.17 us 5.80 us 110.0 us
Query First EFCore 137.1 us 1.69 us 1.50 us 136.6 us
Query 1000 MilliOrm 1,341.1 us 26.35 us 41.03 us 1,338.6 us
Query 1000 Dapper 1,211.3 us 24.04 us 37.43 us 1,216.4 us
Query 1000 EFCore 1,235.3 us 24.43 us 61.73 us 1,213.1 us
Query 1 000 000 MilliOrm 1,081,293.3 us 19,557.71 us 18,294.30 us 1,075,451.4 us
Query 1 000 000 Dapper 792,524.0 us 15,131.16 us 19,136.07 us 787,444.6 us
Query 1 000 000 EFCore 1,205,133.4 us 22,437.67 us 22,036.79 us 1,203,073.6 us

MilliOrm.PostgreSql performs close to Dapper and significantly faster than EF Core for large inserts and large result sets.


When to Use MilliOrm.PostgreSql

Use MilliOrm.PostgreSql if you want:

  • You use PostgreSql as your database
  • Lightweight library
  • Minimal abstractions
  • High performance
  • Easier debugging than EF Core
  • Simpler mental model compared to ORMs with change tracking

License

MIT License


Support

If you like the project, star it on GitHub and contribute!

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 205 12/5/2025

Added support for Skip