Insight.Database maps stored procedures to .NET interfaces without a mapping layer
Fast, lightweight .NET micro-ORM
At a glance
- What is it?
- Insight.Database is a .NET micro-ORM that turns a stored procedure into a method on an interface, maps result sets to objects by convention, and returns several result sets as an object graph. Its licence history is unusual, changing from MS-Public to MIT at version 6, and its repository has no published releases.
- Who is it for?
- Adopt Insight.Database if your .NET application already talks to SQL Server, PostgreSQL, MySQL or Oracle through stored procedures and you want an interface you can inject, mock and unit test without writing a mapping layer. Do not adopt it if your data access is built on Entity Framework style change tracking and LINQ queries, because Insight deliberately exposes the database layer you already have rather than adding an ORM on top.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 147 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The whole premise: a stored procedure becomes an interface method
The README opens with a database and a class, and asks you to make them work together. The schema is a Beer table with an ID, a Type and a Description, plus two stored procedures, one to insert a beer and one to fetch beers by type.
CREATE TABLE Beer ([ID] [int], [Type] varchar(128), [Description] varchar(128)) GO
CREATE PROC InsertBeer @type varchar(128), @description varchar(128) AS
INSERT INTO Beer (Type, Description) OUTPUT inserted.ID
VALUES (@type, @description) GO
CREATE PROC GetBeerByType @type [varchar] AS SELECT * FROM Beer WHERE Type = @type GOThe C# side is a plain class with properties.
public class Beer
{
public int ID { get; private set; }
public string Type { get; set; }
public string Description { get; set; }
}Then comes the part that defines the library. You declare an interface whose methods match the stored procedures, and a single call, connection.As<T>, produces an implementation.
public interface IBeerRepository
{
void InsertBeer(Beer beer);
IList<Beer> GetBeerByType(string type);
void UpdateBeerList(IList<Beer> beerList);
}var repo = connection.As<IBeerRepository>();
repo.InsertBeer(beer);
IList<Beer> beerList = repo.GetBeerByType("ipa");
repo.UpdateBeerList(beerList);No mapping code, and as the README points out you can now inject that interface with a dependency injection framework or mock it for testing. That last point is the one that matters most in practice. A generated repository over an interface you wrote is a seam you can put a fake behind, and that is the difference between a data access layer that is testable and one that is not.
Anonymous objects for the lower level, and objects for the middle
The library is not only interface-based. The README describes two lower levels, and they cover most ad hoc work.
The first is calling a stored procedure with an anonymous object. It also automatically opens and closes the connection.
// auto open/close
conn.Execute("AddBeer", new { Name = "IPA", Flavor = "Bitter"});The second is object mapping. Objects are mapped automatically, with no config files and no attributes.
// auto object mapping
Beer beer = new Beer();
conn.Execute("InsertBeer", beer);
List<Beer> beers = conn.Query<Beer>("FindBeer", new { Name = "IPA" });The distinction between the two is worth understanding. The anonymous object form passes parameters, and the typed form maps both directions: the object becomes parameters going in, and the result set becomes the object coming out. The same method names serve both directions, so the parameter shape and the result shape are matched to the procedure by name.
There is a third level below that, inline SQL, if you would rather not wrap a query in a procedure at all.
var beerList = conn.QuerySql<Beer>("SELECT * FROM Beer WHERE Name LIKE @Name", new { Name = "%ipa%" });
conn.ExecuteSql ("INSERT INTO Beer VALUES (ID, Name)", beer);So the library offers four entry points at increasing levels of ceremony: an interface method, a typed call with an object, a call with an anonymous object, and inline SQL. The README's framing is that you can take control of pieces if you want, and these are the pieces.
That range is the design in miniature. The automatic path is the easy one, and every level down exists because some query does not fit the automatic path.
Object graphs, multiple result sets and the record graph decoder
The genuinely unusual part of the library is what it does with a stored procedure that returns more than one thing.
The simple case is multiple result sets, which the README says it can handle.
// multiple result sets
var results = conn.QueryResults<Beer, Chip>("GetBeerAndChips", new { Pub = "Fergie's" }));
IList<Beer> beer = results.Set1;
IList<Chip> chips = results.Set2;The harder case is multiple independent recordsets with relationships between them. The README phrases this as a rhetorical puzzle: you want to return multiple independent recordsets, with one-to-many and one-to-one relationships. It's not possible. Or is it?
var results = conn.Query("GetLotsOfStuff", parameters,
Query.Returns(OneToOne<Beer, Glass>.Records)
.ThenChildren(Some<Napkin>.Records)
.Then(Some<Wine>.Records));
var beer = results.Set1;
var glass = beer.Glass;
var napkins = beer.Napkins.ToList();
var wine = results.Set2;The answer is that Insight decodes the records and works out one-to-one and parent-child relationships automatically, and almost all the time it does this with no work or configuration on your part. When your model is wacky, the README points to the wiki for the non-scary ways to tell Insight how to work with your data.
The vocabulary in that snippet is the whole point. Returns declares the first set and its shape, ThenChildren declares a set that contains children of the previous, and Then declares an independent set. That is a query plan expressed in the type system rather than in configuration, which is what makes the result navigable as beer.Glass and beer.Napkins.
This decoder is also where the risk lives, and the README says as much with almost all the time. Automatic relationship inference from result sets is a guess about your schema, and a wrong guess produces a wrong object graph rather than an error.
Async, table parameters and bulk copy, all without changing the shape
Three performance and scale features round out the surface, and each one preserves the calling convention.
Async support is described as full, and the mechanism is a suffix. You add Async and the call returns a task.
var task = c.QueryAsync<Beer>("FindBeer", new { Name = "IPA" });Table parameters let you send a whole list of objects to databases that support them, replacing multiple queries or awkward parameter mapping.
-- auto table parameters
CREATE TYPE BeerTable (Name [nvarchar](256), Flavor [nvarchar](256))
CREATE PROCEDURE InsertBeer (@BeerList [BeerTable])List<Beer> beerList = new List<Beer>();
conn.Execute("InsertBeer", new { BeerList = beerList });The list goes in as a single table-valued parameter, which for a bulk insert is the difference between one round trip and thousands.
Bulk copy is the third, and it works over the database's own bulk copy protocol.
// auto bulk-copy objects
IEnumerable<Beer> beerList; // from somewhere
conn.BulkCopy("Beer", beerList);Note that BulkCopy takes an IEnumerable rather than a list, which tells you it streams. That is the right shape for a large export.
There is also nested object support, where a query returns a generic of several types and the object graph is assembled.
// auto object graphs
var servings = conn.Query<Serving, Beer, Glass>("GetServings");
foreach (var serving in servings)
Console.WriteLine("{0} {1}", serving.Beer.Name, serving.Glass.Ounces);The generic parameters here are the record types, in order, and the relationship between them is inferred the same way it is for the record graph decoder.
The licence changed hands once, and the version boundary matters
The licensing section of the README is unusual enough that it deserves reading in full, because it describes a licence change rather than a single licence.
Insight.Database is available under any of the following licenses. There is the Tell Everyone You Know How Awesome Insight Is License, the Answer Every Stackoverflow ORM question with Use Insight.Database License, the Buy A Friend A Beer License, and the Do Some Good, which is the Karmic, License. Then there are two real ones: MS-Public License, until version 5.X.X, and MIT License, from version 6.X.X and above.
So the actual legal terms today, for anyone on a current version, are MIT. The earlier MS-Public terms apply to the 5.x line and older. The whimsical licences at the top are clearly a joke, and the practical reading is that the last two bullets are the real ones.
The repository's licence field is a custom type that GitHub cannot categorise, so the file at the root, License.txt, is the authoritative text. The git history of that file is the record of the change.
The version history is documented in the README as a list of major releases, and it explains the shape of the library better than any single feature list.
v1.0 is Let the magic begin. v2.0 was a performance and asynchronous release, and v2.1 added the automatic interface implementation with database calls. v3.0 brought support for most common databases and tools. v4.0 is the one that matters for anyone joining today, because it reads one-to-one, one-to-many and many-to-many relationships automatically, with ways to extend it. v5.0 added composite keys and deep parameter binding. v6.0 was .Net Standard 2.0 compatibility. v6.3 added Microsoft.Data.SqlClient compatibility, C# 8 interface default methods and a cleaned up package organization. v8.0 added support for .NET 8.0, with the note that many unpopular providers have been removed.
That last note is the one to carry into an evaluation. The provider list has shrunk, and the README says so only in a parenthetical.
The repository layout is a map of the provider story
The top level of the repository is a solution with one project per concern, and the project names are the most reliable summary of what Insight covers.
The core is split into Insight.Database.Core/ and Insight.Database/, with a shared Insight.Database.Configuration/ project for configuration and a SharedConfiguration.csproj and SharedTestConfiguration.csproj that both use. There is Insight.Database.Json/ for JSON support and a matching Insight.Tests.Json/.
Then come the providers, and there are many of them. Insight.Database.Providers.Default/, MsSqlClient/, MySql/, MySqlConnector/, OracleManaged.Core/ and PostgreSQL/. Two of them are not database providers at all: Insight.Database.Providers.MiniProfiler/ is a profiling provider, and Insight.Tests.MiniProfiler/ goes with it.
The test side mirrors the provider side with one test project per provider, including Insight.Tests.SQLite/, which is notable because SQLite is not in the provider list, so it is exercised through something else.
The rest of the tree is ordinary: .github/ for the workflows, Build/ with a build.sh, a CodeAnalysisRules.ruleset, Building.md, an .editorconfig, an Insight.Sample/ project, and an insight-docker/ directory. The Insights.sln at the root ties it together.
Two observations follow. First, the provider-per-project structure is what lets the package organization change without breaking consumers, which is exactly what the 6.3 release did when it cleaned up the package organization. Second, the MiniProfiler provider is a good sign about the library's intentions. If the maintainer built a plug-in that hands your queries to a profiler, the library is meant to live inside an application whose queries you want to see, not behind a wall.
Where Insight.Database is the wrong tool
The most important limitation is not a bug but a boundary, and it is the one the README never states plainly because the whole library is built on the other side of it.
Insight.Database is not an ORM in the sense that Entity Framework or Dapper users mean. It does not track changes, does not generate SQL from a model, and does not give you a unit of work. It calls the stored procedures you already have and maps the results. If your data access is LINQ over an object graph, Insight is the wrong shape entirely, and no amount of configuration will turn it into one.
The second limitation is that automatic mapping is a guess. Objects are mapped automatically with no config files and no attributes, and relationships are inferred from result sets. The README hedges this with almost all the time, and when you have a wacky model you are pushed to the wiki to tell Insight how to behave. The failure mode of a wrong inference is a silently wrong object graph, not an exception, and that is the kind of bug that is expensive to find in production.
The third limitation is the provider list. Version 8.0 removed many unpopular providers, and the README's own summary of the 3.0 release points to a wiki page listing the supported providers. If you are on an exotic database, check that list before designing around the library, because the removal in 8.0 was not silent.
The fourth is the release channel. The repository has no GitHub releases at all, so the only version history is the major release list in the README and whatever NuGet carries. The last push was on 2026-05-29 and the repository is not archived, so work continues, but you are pinning NuGet versions rather than tracking tags.
One more practical caution. The library's own motto says it is the .NET micro-ORM that nobody knows about. That is a joke, but it points at a real risk: the wiki is the documentation, and a wiki is easier to leave behind than a docs site. Check that the pages you depend on are current for the version you are pinning.
Editorial conclusion
Adopt Insight.Database if your .NET application already talks to SQL Server, PostgreSQL, MySQL or Oracle through stored procedures and you want an interface you can inject, mock and unit test without writing a mapping layer. Do not adopt it if your data access is built on Entity Framework style change tracking and LINQ queries, because Insight deliberately exposes the database layer you already have rather than adding an ORM on top. Before you commit, read the wiki on the supported data providers, because the 8.0 release removed many unpopular providers, and check the version you are pinning, since the licence changed from MS-Public to MIT at 6.0 and the repository publishes no GitHub releases to guide that choice.
Frequently asked questions
What is Insight.Database?
It is a fast, lightweight .NET micro-ORM that maps stored procedures to methods on an interface you define, so that you can inject the interface with a dependency injection framework or mock it for testing without writing any mapping code.
How do I install Insight.Database?
The README shows the NuGet package manager command Install-Package Insight.Database. For a database other than SQL Server, you install one of the alternate provider packages, for example Insight.Database.Providers.Postgres.
Which databases does Insight.Database support?
The repository has provider projects for SQL Server through MsSqlClient, PostgreSQL, MySql, MySqlConnector and Oracle through OracleManaged.Core. The v8.0 release notes state that many unpopular providers have been removed, and the README points to a wiki page with the list of supported providers.
Does Insight.Database support asynchronous calls?
Yes. The README describes full async support, where you add the Async suffix to the call, for example c.QueryAsync<Beer> with a parameter object.
What licence is Insight.Database released under?
The README lists several licence options, with MS-Public until version 5.X.X and MIT from version 6.X.X and above. The repository licence field is a custom type that GitHub cannot classify, so License.txt at the root is the authoritative text.
Can Insight.Database return multiple result sets as an object graph?
Yes. Query.Returns with ThenChildren and Then describes the shape, and Insight decodes the records and works out one-to-one and parent-child relationships automatically, which the README says it does almost all the time with no configuration.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jonwagner-insight-database)Was this useful?