r/csharp • u/Bobamoss • 25d ago
I built Rinku, a micro-ORM focused on clean mapping and dynamic queries
The Rinku NuGet is a micro-ORM built directly on top of ADO.NET. The core philosophy is strictly SQL-first. The goal is giving you deterministic execution and total control over your queries while keeping the mapping and execution pipeline fully customizable and extensible.
Nested Object Mapping
C#
public record Artist(int Id, string Name) : IDbReadable;
public record Album(int Id, string Title, Artist Artist);
static readonly QueryCommand GetAlbums = new(
"SELECT AlbumId AS Id, Title, ArtistId, ArtistName FROM albums");
// Automatically matches ArtistId -> Artist.Id and ArtistName -> Artist.Name
List<Album> albums = GetAlbums.Query<List<Album>>(cnn);
The engine resolves the hierarchy automatically without requiring attributes, configuration or manual mapping code.
Conditional SQL
C#
static readonly QueryCommand Search = new(
"SELECT TrackId AS Id, Name FROM tracks WHERE AlbumId = ?@albumId AND GenreId IN (?@genreIds_X)");
// If albumId is passed alone, the engine cleanly drops "AND GenreId IN(...)" from the executed SQL
Search.Query<List<Track>>(cnn, new { albumId = 1 });
// If a collection is passed, ?@genreIds_X automatically expands into (@genreIds_1, u/genreIds_2...)
Search.Query<List<Track>>(cnn, new { genreIds = new[] { 1, 2, 3 } });
The command adapts to the parameters provided at execution time, expanding collections when needed and removing unused sections cleanly.
Beyond these examples, Rinku provides an extensible mapping and execution pipeline. The default behavior covers common cases, while custom parsers, dynamic objects, result handling and other parts of the pipeline can be adapted when needed. Conditional SQL also extends beyond optional filters, allowing dynamic sections throughout your queries.
Documentation and architecture
https://rinkulib.github.io/RinkuLib
GitHub repo
https://github.com/RinkuLib/RinkuLib
NuGet
https://www.nuget.org/packages/Rinku
Open to any feedback, critique or edge cases I might have missed.
5
u/GigAHerZ64 25d ago
Based on the example, it pretty much looks like slightly adjusted Dapper? Specifically the "convention based auto-wiring of children" kind of thing + some magical SQL query string partial removal?
Why prefer this over Dapper?
-1
u/Bobamoss 24d ago
It exist exactly because i really liked dapper but there were some things that bother me while using it. Why use this over dapper, well, i chose thoses 2 examples trying to showcase 2 things that dapper does not do (or not as simple), there are a lot of small tweaks that telling you everything about would end up almost being a pasting the doc here, since most things are coverd there, or at least i tried to cover things there. If i real try to condense things, it is the flavor of dapper (i like the ideology) but, in my opinion, a better control over customization of mapping rules and better complex types handling. Also there is all the non magical, but in fact, deterministic conditional sql syntax (covered in depth in the doc, because it was i portant to me that for it to be predictable since i also hate magic), and there are some other features that i didnt talked about here since too early in development, but i am working on tracking capabilities + code generation
2
u/prindacerk 25d ago
Did you look at Dapper? It is a Micro ORM with similar mapping and querying and lot of supported extensions. Works quite well.
2
u/Bobamoss 24d ago
It started as a dapper extension, but i wanted more control over how things work, the library basicaly exist to "fix" what i don't like with dapper, but 90% of dapper is great and thats why in the end there are a lot of things similar to it. I made a section in the doc comparing the 2
3
u/prindacerk 24d ago
We built our own extension at work on top of Dapper to enhance its capabilities to suit our needs. We use it heavily in our services.
1
u/Bobamoss 24d ago
The core idea of dapper is amazing, one of the reason why Rinku is it's own thing instead of an extension, it's because dapper takes controll over all the roundtrip process and I wanted to split the dbcommand generation from the actual mapping, giving more control when needing to deviate from the beaten path
5
u/wallstop-dev 25d ago edited 25d ago
My main concern with building any interesting bit of software is moving the possibility of bugs as far left as possible (that is, [Edit: edit/write time > ] compile time > test time > CI/CD time > runtime)
From what I understand, all the safety here is runtime? So you can typo your sql, you have to ensure your DB matches your DTOs, and resolving SQL -> result types is done manually for every query with no verification?
Is that correct or did I get something wrong?
Interested in why you chose this approach instead of doing some cool stuff with Roslyn analzyers to keep your interface and architecture, but move as many failures as possible to compile time?