Norm.NET Micro ORM

Norm.NET Micro ORM is a one-way lightweight object-relational mapping library.

Micro ORM for .NET/C#.

What is Micro ORM Blog Post

Installation
dotnet add package Norm.net --version 5.4.0
dotnet add package Norm.net --version 5.4.0
NuGet\Install-Package Norm.net -Version 5.4.0
NuGet\Install-Package Norm.net -Version 5.4.0
<PackageReference Include="Norm.net" Version="5.4.0" />
<PackageReference Include="Norm.net" Version="5.4.0" />
#r "nuget: Norm.net, 5.4.0"
#r "nuget: Norm.net, 5.4.0"
Compatibility
netstandard2.1
net5.0 net5.0-windows net6.0 net6.0-android net6.0-ios net6.0-maccatalyst net6.0-macos net6.0-tvos net6.0-windows net7.0 net7.0-android net7.0-ios net7.0-maccatalyst net7.0-macos net7.0-tvos net7.0-windows net8.0 net8.0-android net8.0-ios net8.0-maccatalyst net8.0-macos net8.0-tvos net8.0-windows
netcoreapp3.0 netcoreapp3.1
monoandroid monomac monotouch
tizen60
xamarinios xamarinmac xamarintvos xamarinwatchos
Perfomances
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)634.31.008.261.00
ADO.NET Raw517.60.823.560.43
EF Core 8731.31.1516.241.96
Norm (instances) 569.90.909.581.16
Norm (tuple values) 544.70.862.90.35
Norm (named tuples) 530.40.849.151.11
Norm (anonymous inst.) 549.60.879.761.18
Norm (tuple array) 535.10.848.291.00
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)634.31.008.261.00
ADO.NET Raw517.60.823.560.43
EF Core 8731.31.1516.241.96
Norm (instances) 569.90.909.581.16
Norm (tuple values) 544.70.862.90.35
Norm (named tuples) 530.40.849.151.11
Norm (anonymous inst.) 549.60.879.761.18
Norm (tuple array) 535.10.848.291.00
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)5,664.21.00740.911.00
ADO.NET Raw5,213.10.92295.410.40
EF Core 86,188.41.10493.080.67
Norm (instances) 5,768.01.02594.890.80
Norm (tuple values) 6,068.61.07209.060.28
Norm (named tuples) 5,805.51.03764.461.03
Norm (anonymous inst.) 6,178.61.10695.440.94
Norm (tuple array) 5,441.50.97686.660.93
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)60,626.51.007521.521.00
ADO.NET Raw39,086.70.652968.310.39
EF Core 849,088.00.8148540.65
Norm (instances) 46,987.10.785941.230.79
Norm (tuple values) 48,000.40.802108.960.28
Norm (named tuples) 45,584.70.757655.891.02
Norm (anonymous inst.) 49,914.90.836953.850.92
Norm (tuple array) 41,510.40.696875.110.91
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)591,684.01.0075439.471.00
ADO.NET Raw360,967.30.6230390.180.40
EF Core 8430,804.80.7349150.920.65
Norm (instances) 399,019.80.6860101.440.80
Norm (tuple values) 407,943.40.6921809.590.29
Norm (named tuples) 421,935.60.7177267.791.02
Norm (anonymous inst.) 409,893.50.6970242.370.93
Norm (tuple array) 402,777.60.6869454.650.92
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)649.91.008.261.00
ADO.NET Raw524.00.813.560.43
EF Core 8734.21.1316.251.97
Norm (instances) 568.70.889.581.16
Norm (tuple values) 536.90.832.90.35
Norm (named tuples) 545.00.849.151.11
Norm (anonymous inst.) 575.00.899.771.18
Norm (tuple array) 542.20.848.291.00
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)634.31.008.261.00
ADO.NET Raw517.60.823.560.43
EF Core 8731.31.1516.241.96
Norm (instances) 569.90.909.581.16
Norm (tuple values) 544.70.862.90.35
Norm (named tuples) 530.40.849.151.11
Norm (anonymous inst.) 549.60.879.761.18
Norm (tuple array) 535.10.848.291.00
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)5,777.21.00740.931.00
ADO.NET Raw5,379.70.93295.350.40
EF Core 86,258.61.09493.110.67
Norm (instances) 6,019.71.05594.980.80
Norm (tuple values) 5,865.61.02209.120.28
Norm (named tuples) 6,017.91.05764.491.03
Norm (anonymous inst.) 6,354.11.11695.570.94
Norm (tuple array) 5,522.80.96686.670.93
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)58,930.51.007520.981.00
ADO.NET Raw39,097.30.662969.410.39
EF Core 848,248.90.824854.970.65
Norm (instances) 46,338.40.805942.90.79
Norm (tuple values) 48,488.90.822109.420.28
Norm (named tuples) 44,790.40.767657.11.02
Norm (anonymous inst.) 50,031.20.856954.210.92
Norm (tuple array) 41,447.60.716877.550.91
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)592,008.81.0075409.61.00
ADO.NET Raw367,873.00.6330420.180.40
EF Core 8408,393.70.7049150.920.65
Norm (instances) 414,667.60.7060081.190.80
Norm (tuple values) 408,334.90.7021799.090.29
Norm (named tuples) 425,086.20.7177268.351.02
Norm (anonymous inst.) 431,558.50.7470255.870.93
Norm (tuple array) 385,006.60.6569478.790.92
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)658.51.008.251.00
ADO.NET Raw524.80.803.560.43
EF Core 8733.31.1216.251.97
Norm (instances) 554.70.859.581.16
Norm (tuple values) 537.00.822.90.35
Norm (named tuples) 542.00.839.141.11
Norm (anonymous inst.) 555.90.859.761.18
Norm (tuple array) 521.00.798.291.00
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)634.31.008.261.00
ADO.NET Raw517.60.823.560.43
EF Core 8731.31.1516.241.96
Norm (instances) 569.90.909.581.16
Norm (tuple values) 544.70.862.90.35
Norm (named tuples) 530.40.849.151.11
Norm (anonymous inst.) 549.60.879.761.18
Norm (tuple array) 535.10.848.291.00
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)5,697.91.00740.821.00
ADO.NET Raw5,212.00.92295.620.40
EF Core 86,200.31.09493.180.67
Norm (instances) 5,883.71.04594.950.80
Norm (tuple values) 5,846.51.03209.10.28
Norm (named tuples) 5,719.61.01764.61.03
Norm (anonymous inst.) 6,241.51.10695.510.94
Norm (tuple array) 5,391.80.95686.520.93
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)59,447.51.007521.161.00
ADO.NET Raw38,727.80.662970.950.40
EF Core 849,114.70.834853.490.65
Norm (instances) 45,102.20.765940.40.79
Norm (tuple values) 46,517.70.782111.590.28
Norm (named tuples) 45,105.40.767655.521.02
Norm (anonymous inst.) 48,528.60.826955.490.92
Norm (tuple array) 40,575.90.686877.660.91
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)565,696.91.0075438.921.00
ADO.NET Raw361,561.30.6430411.510.40
EF Core 8462,485.90.8349149.660.65
Norm (instances) 403,805.50.71600990.80
Norm (tuple values) 420,217.30.7521804.710.29
Norm (named tuples) 400,954.00.7177267.931.02
Norm (anonymous inst.) 411,138.90.7370237.30.93
Norm (tuple array) 381,007.90.6769453.710.92
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)643.31.008.231.00
ADO.NET Raw512.80.803.560.43
EF Core 8725.31.1416.241.97
Norm (instances) 570.70.899.581.16
Norm (tuple values) 525.40.822.90.35
Norm (named tuples) 527.30.829.151.11
Norm (anonymous inst.) 541.70.849.771.19
Norm (tuple array) 519.10.818.291.01
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)634.31.008.261.00
ADO.NET Raw517.60.823.560.43
EF Core 8731.31.1516.241.96
Norm (instances) 569.90.909.581.16
Norm (tuple values) 544.70.862.90.35
Norm (named tuples) 530.40.849.151.11
Norm (anonymous inst.) 549.60.879.761.18
Norm (tuple array) 535.10.848.291.00
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)6,180.01.00741.021.00
ADO.NET Raw5,681.00.92295.280.40
EF Core 86,608.41.07493.340.67
Norm (instances) 5,821.90.95594.840.80
Norm (tuple values) 5,829.50.95209.060.28
Norm (named tuples) 6,090.30.99764.541.03
Norm (anonymous inst.) 6,594.01.07695.540.94
Norm (tuple array) 5,339.50.87686.780.93
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)58,674.01.007521.11.00
ADO.NET Raw38,436.20.662970.360.39
EF Core 847,979.20.824854.780.65
Norm (instances) 44,340.50.765940.010.79
Norm (tuple values) 48,177.70.822108.040.28
Norm (named tuples) 45,144.30.787657.481.02
Norm (anonymous inst.) 48,940.20.846957.480.93
Norm (tuple array) 41,034.20.7068760.91
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)578,149.01.0075409.661.00
ADO.NET Raw359,674.20.6430395.050.40
EF Core 8404,648.00.7049150.920.65
Norm (instances) 411,144.20.7260081.190.80
Norm (tuple values) 399,769.80.6921797.020.29
Norm (named tuples) 396,422.10.6977266.661.02
Norm (anonymous inst.) 432,315.60.7570240.870.93
Norm (tuple array) 384,060.60.6669457.090.92
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)629.51.008.271.00
ADO.NET Raw526.30.843.560.43
EF Core 8730.21.1616.251.97
Norm (instances) 557.30.899.581.16
Norm (tuple values) 531.90.852.90.35
Norm (named tuples) 533.50.859.151.11
Norm (anonymous inst.) 540.20.869.771.18
Norm (tuple array) 530.60.848.291.00
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)634.31.008.261.00
ADO.NET Raw517.60.823.560.43
EF Core 8731.31.1516.241.96
Norm (instances) 569.90.909.581.16
Norm (tuple values) 544.70.862.90.35
Norm (named tuples) 530.40.849.151.11
Norm (anonymous inst.) 549.60.879.761.18
Norm (tuple array) 535.10.848.291.00
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)5,584.51.00740.91.00
ADO.NET Raw5,034.80.91295.650.40
EF Core 86,108.41.10493.030.67
Norm (instances) 5,841.81.05594.810.80
Norm (tuple values) 6,590.31.19209.030.28
Norm (named tuples) 5,564.51.00764.631.03
Norm (anonymous inst.) 6,194.71.12695.460.94
Norm (tuple array) 5,309.50.96686.90.93
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)61,134.91.007520.61.00
ADO.NET Raw42,847.00.702969.960.39
EF Core 851,986.00.854855.920.65
Norm (instances) 48,545.40.805940.020.79
Norm (tuple values) 51,634.40.852108.180.28
Norm (named tuples) 47,648.40.787655.761.02
Norm (anonymous inst.) 51,557.80.856954.640.92
Norm (tuple array) 42,906.10.706876.750.91
Library (method)Mean μsRatioAllocated KBAllocated Ratio
Dapper (instances)601,452.61.0075412.971.00
ADO.NET Raw367,371.10.6130389.80.40
EF Core 8426,235.90.7149150.920.65
Norm (instances) 447,736.00.7560087.190.80
Norm (tuple values) 442,534.90.7421800.210.29
Norm (named tuples) 426,983.90.7177275.11.02
Norm (anonymous inst.) 419,724.80.7070237.870.93
Norm (tuple array) 396,666.10.6669492.310.92
see more details on perfomance page
Build database tuples iterator
You can read an array of name and value tuples (string name, value object) - for each record, where the name is the field name, and the value is, well, the value of that record.

Use LINQ expressions to build appropriate data structures such as dictionaries directly from your database and with minimum iterations.
using System.Linq;
using Norm;

//
// Creates enumerable of name and value tuples array.
//
// Database is NOT read until resulting enumeration is iterated.
// name is the column name, and value is the column value.
//
IEnumerable<(string name, value object)[]> enumeration = connection
    .Read("select id, name from table");

//
// Creates list of name and value tuples array.
//
// Database is read on `ToList` iteration.
// name is the column name, and value is the column value.
//
List<(string name, object value)[]> tuples = connection
    .Read("select id, name from table")
    .ToList();

//
// Creates list of name and value dictionaries.
//
// Database is read on `ToList` iteration.
// Dictionary keys are column names, values are column values.
//
List<Dictionary<string, object>> list1 = connection
    .Read("select id, name from table")
    .Select(tuples => 
        tuples.ToDictionary(
                tuple => tuple.name, 
                tuple => tuple.value))
    .ToList();

//
// Creates dictionary where: 
// - The key is the first column value.
// - The value is the last column value.
//
// Database is read on `ToDictionary` iteration.
//
Dictionary<int, string> dict1 = connection
    .Read("select id, name from table")
    .ToDictionary(
        tuples => (int)tuples.First().value, 
        tuples => tuples.Last().value?.ToString());
using System.Linq;
using Norm;

//
// Creates enumerable of name and value tuples array.
//
// Database is NOT read until resulting enumeration is iterated.
// name is the column name, and value is the column value.
//
IEnumerable<(string name, value object)[]> enumeration = connection
    .Read("select id, name from table");

//
// Creates list of name and value tuples array.
//
// Database is read on `ToList` iteration.
// name is the column name, and value is the column value.
//
List<(string name, object value)[]> tuples = connection
    .Read("select id, name from table")
    .ToList();

//
// Creates list of name and value dictionaries.
//
// Database is read on `ToList` iteration.
// Dictionary keys are column names, values are column values.
//
List<Dictionary<string, object>> list1 = connection
    .Read("select id, name from table")
    .Select(tuples => 
        tuples.ToDictionary(
                tuple => tuple.name, 
                tuple => tuple.value))
    .ToList();

//
// Creates dictionary where: 
// - The key is the first column value.
// - The value is the last column value.
//
// Database is read on `ToDictionary` iteration.
//
Dictionary<int, string> dict1 = connection
    .Read("select id, name from table")
    .ToDictionary(
        tuples => (int)tuples.First().value, 
        tuples => tuples.Last().value?.ToString());
documentation
Up to 12 class/record instances from the same command
You can map a maximum of 12 classes, records, or dynamic instances from the same query result.

Mapping is done by the field name (snake case is the default name mapping method) - and from left to right for multiple instance mappings.
using System.Linq;
using Norm;

//
// Single instance mapping by name.
//
var result1 = connection
    .Read<Class1>(query)
    .Single();

//
// Two instances mapping by name.
// Read method yields a tuple that can be deconstructed.
//
var (result1, result2) = connection
    .Read<Class1, Class2>(query)
    .Single();

//
// Two class instances and one dynamic instance mapping by name.
// Read method yields a tuple that can be deconstructed.
//
var (result1, result2, result3) = connection
    .Read<Class1, Class2, dynamic>(query)
    .Single();


//
// Six instances mapping by name.
//
var (result1, result2, result3, result4, result5, result6) = connection
    .Read<Class1, Class2, Class3, Class4, Class5, Class6>(query)
    .Single();

//
// ... up to twelve (12) instances max.
//
using System.Linq;
using Norm;

//
// Single instance mapping by name.
//
var result1 = connection
    .Read<Class1>(query)
    .Single();

//
// Two instances mapping by name.
// Read method yields a tuple that can be deconstructed.
//
var (result1, result2) = connection
    .Read<Class1, Class2>(query)
    .Single();

//
// Two class instances and one dynamic instance mapping by name.
// Read method yields a tuple that can be deconstructed.
//
var (result1, result2, result3) = connection
    .Read<Class1, Class2, dynamic>(query)
    .Single();


//
// Six instances mapping by name.
//
var (result1, result2, result3, result4, result5, result6) = connection
    .Read<Class1, Class2, Class3, Class4, Class5, Class6>(query)
    .Single();

//
// ... up to twelve (12) instances max.
//
documentation
Map to deconstructed tuple values
Instead of classes or records, you can map to multiple, simple single-value value types (such as integers, strings, dates, GUIDs, booleans, etc). In this case, mapping is done by position.

The resulting structure will be an unnamed tuple that can be deconstructed to the same simple value variable values. See examples bellow:
using System.Linq;
using Norm;

//
// Single value.
//
var id = connection
    .Read<int>("select id from table")
    .Single();

//
// Two values mapping by position.
//
var (id, value) = connection
    .Read<int, string>("select id, value from table")
    .Single();

//
// Iterating over three values mapped by position.
//
foreach(var (id, foo, bar) in connection.Read<int, string, string>(
    "select id, foo, bar from table"))
{
    //...
}

//
// Six value mapping by position.
//
var (value1, value2, value3, value4, value5, value6) = connection
    .Read<string, string, string, string, string, string>(@"
        select value1, value2, value3, value4, value5, value6
        from table
    ")
    .Single();

//
// ... up to twelve (12) values max.
//
using System.Linq;
using Norm;

//
// Single value.
//
var id = connection
    .Read<int>("select id from table")
    .Single();

//
// Two values mapping by position.
//
var (id, value) = connection
    .Read<int, string>("select id, value from table")
    .Single();

//
// Iterating over three values mapped by position.
//
foreach(var (id, foo, bar) in connection.Read<int, string, string>(
    "select id, foo, bar from table"))
{
    //...
}

//
// Six value mapping by position.
//
var (value1, value2, value3, value4, value5, value6) = connection
    .Read<string, string, string, string, string, string>(@"
        select value1, value2, value3, value4, value5, value6
        from table
    ")
    .Single();

//
// ... up to twelve (12) values max.
//
documentation
Map to named tuples or multiple named tuples
You can also give meaningful names to your unnamed tuples by using named tuples. Just provide a named tuple type as a generic parameter and map your database values by position.

You can also map to multiple named tuple types in a single command, up to 12 named tuples maximum.
using System.Linq;
using Norm;

//
// You can also map to named tuples and give names to the columns.
// Two value mapping by position.
//
var result = connection
    .Read<(int id, string name)>("select id, value from table")
    .Single();

var id = result.id;
var name = result.name;

//
// Iterating over named tuples mapped by position.
//
var query = "select id, foo, bar from table";
foreach(var item in connection
    .Read<(int id, string foo, string bar)>(query))
{
    WriteLine($"id: {item.id}, foo: {item.foo}, bar: {item.bar}");
}

//
// Mapping two named tuples by position.
//
var query = "select t1.*, t2.* from t1 join t2 using (id)";
foreach (var (t1, t2) in connection.Read<
    (int id, string name), 
    (int id, string name)>(query))
{
        WriteLine($"t1 - id: {t1.id}, name: {t1.name});
        WriteLine($"t2 - id: {t1.id}, name: {t1.name});
}

//
// ... up to twelve (12) named tuples max.
//
using System.Linq;
using Norm;

//
// You can also map to named tuples and give names to the columns.
// Two value mapping by position.
//
var result = connection
    .Read<(int id, string name)>("select id, value from table")
    .Single();

var id = result.id;
var name = result.name;

//
// Iterating over named tuples mapped by position.
//
var query = "select id, foo, bar from table";
foreach(var item in connection
    .Read<(int id, string foo, string bar)>(query))
{
    WriteLine($"id: {item.id}, foo: {item.foo}, bar: {item.bar}");
}

//
// Mapping two named tuples by position.
//
var query = "select t1.*, t2.* from t1 join t2 using (id)";
foreach (var (t1, t2) in connection.Read<
    (int id, string name), 
    (int id, string name)>(query))
{
        WriteLine($"t1 - id: {t1.id}, name: {t1.name});
        WriteLine($"t2 - id: {t1.id}, name: {t1.name});
}

//
// ... up to twelve (12) named tuples max.
//
documentation
Declare tuple type aliases (C# 12 only)
Declare type aliases for your named tuples and us them in your entire project..
//
// C#12
// Global usings for entire project
//
global using TitleDescriptionYear = (string title, string description, int year);
global using IdName = (int id, string name);

//
// Later in your code, you can use the types, intelisense will work
//
using System.Linq;
using Norm;

//
// Iterate over tuples, intelisense enabled
//
foreach (var tuple in connection.Read<TitleDescriptionYear>(
    "select title, description, release_year from film limit 3"))
{
    WriteLine("Title: {0}, Description: {1}, Year: {2}", 
        tuple.title, tuple.description, tuple.year);
}

foreach (var tuple in connection.Read<IdName>(
    "select film_id, title from film limit 3"))
{
    WriteLine("Film Id: {0}, Name: {1}", 
        tuple.id, tuple.name);
}

//
// Build a dictionary from tuples, intelisense enabled
//
var dict = connection
    .Read<IdName>("select film_id, title from film limit 3")
    .ToDictionary(
        tuple => tuple.id,
        tuple => tuple.name);
//
// C#12
// Global usings for entire project
//
global using TitleDescriptionYear = (string title, string description, int year);
global using IdName = (int id, string name);

//
// Later in your code, you can use the types, intelisense will work
//
using System.Linq;
using Norm;

//
// Iterate over tuples, intelisense enabled
//
foreach (var tuple in connection.Read<TitleDescriptionYear>(
    "select title, description, release_year from film limit 3"))
{
    WriteLine("Title: {0}, Description: {1}, Year: {2}", 
        tuple.title, tuple.description, tuple.year);
}

foreach (var tuple in connection.Read<IdName>(
    "select film_id, title from film limit 3"))
{
    WriteLine("Film Id: {0}, Name: {1}", 
        tuple.id, tuple.name);
}

//
// Build a dictionary from tuples, intelisense enabled
//
var dict = connection
    .Read<IdName>("select film_id, title from film limit 3")
    .ToDictionary(
        tuple => tuple.id,
        tuple => tuple.name);
documentation
Map to prototypes and anonymous instances
If you use a non-generic version of the Read method, you can provide a prototype instance as a first parameter.

In that case, Norm will use that prototype to construct resulting instances.

That prototype could also be an anonymous type - initilaized with default values as in examples bellow:
using System.Linq;
using Norm;

//
// You can use existing instances as a bluprint for mapping.
//
class Class1
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// 
// Maps existing instances of Class1 to 
// new instances of the same type by name.
//
var instance1 = new Class1();
var result1 = connection
    .Read(instance1, "select 1 as id, 'foo' as name")
    .Single();

Assert.Equal(1, result1.Id);
Assert.Equal("foo", result1.Name);

//
// You can use anonymous instances prototype 
// to map to anonymous instances.
// Also by name.
//
var result1 = connection
    .Read(
        new {id = default(int), name = default(string)}, 
        "select 1 as id, 'foo' as name")
    .Single();

Assert.Equal(1, result1.Id);
Assert.Equal("foo", result1.Name);

//
// Iterate anonymous instances mapped by name.
//
foreach(var item in connection.Read(new
{
    first = default(int),
    bar = default(string),
    day = default(DateTime?),
    @bool = default(bool?),
    myString = default(string),
}, @"
    select * from (
        values 
        (1, 'foo1', '1977-05-19'::date, true, null),
        (2, 'foo2', '1978-05-19'::date, false, 'bar2'),
        (3, 'foo3', null::date, null, 'bar3')
    ) t(first, bar, day, bool, my_string)
"))
{
    WriteLine("{0}, {1}, {2}, {3}, {4}",
        item.first, 
        item.bar, 
        item.day, 
        item.@bool, 
        item.myString);
}
using System.Linq;
using Norm;

//
// You can use existing instances as a bluprint for mapping.
//
class Class1
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// 
// Maps existing instances of Class1 to 
// new instances of the same type by name.
//
var instance1 = new Class1();
var result1 = connection
    .Read(instance1, "select 1 as id, 'foo' as name")
    .Single();

Assert.Equal(1, result1.Id);
Assert.Equal("foo", result1.Name);

//
// You can use anonymous instances prototype 
// to map to anonymous instances.
// Also by name.
//
var result1 = connection
    .Read(
        new {id = default(int), name = default(string)}, 
        "select 1 as id, 'foo' as name")
    .Single();

Assert.Equal(1, result1.Id);
Assert.Equal("foo", result1.Name);

//
// Iterate anonymous instances mapped by name.
//
foreach(var item in connection.Read(new
{
    first = default(int),
    bar = default(string),
    day = default(DateTime?),
    @bool = default(bool?),
    myString = default(string),
}, @"
    select * from (
        values 
        (1, 'foo1', '1977-05-19'::date, true, null),
        (2, 'foo2', '1978-05-19'::date, false, 'bar2'),
        (3, 'foo3', null::date, null, 'bar3')
    ) t(first, bar, day, bool, my_string)
"))
{
    WriteLine("{0}, {1}, {2}, {3}, {4}",
        item.first, 
        item.bar, 
        item.day, 
        item.@bool, 
        item.myString);
}
documentation
Asynchronous streaming directly from the database
Take advantage of the new IAsyncEnumerable type and asynchronous streaming directly from your database.

All asynchronous methods in Norm will return the IAsyncEnumerable type, so you can take async code to the next level by leveraging System.Linq.Async.
using Norm;

const string query = 
    "select id, name, foo, bar from my_streaming_table";

//
// Asynchronously stream values directly from database
//
await foreach(var (id, name, foo, bar) in 
    connection.ReadAsync<int, string, string, string>(query))
{
    //...
}

//
// Asynchronously stream named tuples directly from database
//
await foreach(var item in 
    connection.ReadAsync<(
        int id, 
        string name, 
        string foo, 
        string bar)>(query))
{
    //...
}

//
// Asynchronously stream to one or more 
// class instances directly from database
//
class Class1
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Class2
{
    public int Foo { get; set; }
    public string Bar { get; set; }
}

// Single class (id and name)
await foreach(var item in 
    connection.ReadAsync<Class1>(query))
{
    //...
}

// Two classes 
// class1: id and name
// class2: foo and bar (up to 12 max)
await foreach(var (r1, r2) in 
    connection.ReadAsync<Class1, Class2>(query))
{
    //...
}

// One normal class (id and name) and one dynamic (up to 12 max)
await foreach(var (x, y) in 
    connection.ReadAsync<Class1, dynamic>(query))
{
    //...
}
using Norm;

const string query = 
    "select id, name, foo, bar from my_streaming_table";

//
// Asynchronously stream values directly from database
//
await foreach(var (id, name, foo, bar) in 
    connection.ReadAsync<int, string, string, string>(query))
{
    //...
}

//
// Asynchronously stream named tuples directly from database
//
await foreach(var item in 
    connection.ReadAsync<(
        int id, 
        string name, 
        string foo, 
        string bar)>(query))
{
    //...
}

//
// Asynchronously stream to one or more 
// class instances directly from database
//
class Class1
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Class2
{
    public int Foo { get; set; }
    public string Bar { get; set; }
}

// Single class (id and name)
await foreach(var item in 
    connection.ReadAsync<Class1>(query))
{
    //...
}

// Two classes 
// class1: id and name
// class2: foo and bar (up to 12 max)
await foreach(var (r1, r2) in 
    connection.ReadAsync<Class1, Class2>(query))
{
    //...
}

// One normal class (id and name) and one dynamic (up to 12 max)
await foreach(var (x, y) in 
    connection.ReadAsync<Class1, dynamic>(query))
{
    //...
}
documentation
Map from multiple queries
You can map from multiple queries by using the Multiple method.

When mapping from multiple queries, all the same rules apply as when mapping from a single query.
using System.Linq;
using Norm;

const string Queires = @"
select 1 as id1, 'foo1' as foo1, 'bar1' as bar1;    // query 1
select 2 as id2, 'foo2' as foo2, 'bar2' as bar2;    // query 2";

//
// declare two record types - Record1 and Record2
//
public record Record1(int Id1, string Foo1, string Bar1);
public record Record2(int Id2, string Foo2, string Bar2);


//
// Use Multiple method to create a disposable mutiple read object.
// Move to next result set with Next method.
//
using var multiple = connection.Multiple(Queires);

var result1 = multiple
    .Read<Record1>()
    .Single();

multiple.Next();

var result2 = multiple
    .Read<Record2>()
    .Single();

//
// Same mapping rules apply as in regular Read methods.
//
using var multiple = connection.Multiple(Queires);

var result1 = multiple
    .Read<(int Id1, string Foo1, string Bar1)>()
    .Single();

multiple.Next();

var result2 = multiple
    .Read<(int Id2, string Foo2, string Bar2)>()
    .Single();
using System.Linq;
using Norm;

const string Queires = @"
select 1 as id1, 'foo1' as foo1, 'bar1' as bar1;    // query 1
select 2 as id2, 'foo2' as foo2, 'bar2' as bar2;    // query 2";

//
// declare two record types - Record1 and Record2
//
public record Record1(int Id1, string Foo1, string Bar1);
public record Record2(int Id2, string Foo2, string Bar2);


//
// Use Multiple method to create a disposable mutiple read object.
// Move to next result set with Next method.
//
using var multiple = connection.Multiple(Queires);

var result1 = multiple
    .Read<Record1>()
    .Single();

multiple.Next();

var result2 = multiple
    .Read<Record2>()
    .Single();

//
// Same mapping rules apply as in regular Read methods.
//
using var multiple = connection.Multiple(Queires);

var result1 = multiple
    .Read<(int Id1, string Foo1, string Bar1)>()
    .Single();

multiple.Next();

var result2 = multiple
    .Read<(int Id2, string Foo2, string Bar2)>()
    .Single();
documentation
Use nested mapping to build complex object map
With Norm, building nested object maps is easy.

You can use the combination of tuple destructions and LINQ expressions to build complex object maps with nested objects from your database. See example bellow:
using System.Linq;
using Norm;

const string query = @"
    select shop.*, account.*
    from shop 
    join account on shop.id = account.shop_id";
/*
"id"    "name"  "id"    "name"      "address"   "shop_id"
---------------------------------------------------------
1       "shop1" 3       "account3"  "addr3"     1
1       "shop1" 2       "account2"  "addr2"     1
1       "shop1" 1       "account1"  "addr1"     1
2       "shop2" 5       "account5"  "addr5"     2
2       "shop2" 4       "account4"  "addr4"     2
*/

//
// Each Shop instance will have a list of related Accounts.
//
public class Shop
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IList<Account> Accounts { get; set; }
}

//
// Each Account instance will have a reference to its parent Shop.
//
public class Account
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public int ShopId { get; set; }
    public Shop Shop { get; set; }
}

//
// Build a list of Shops with list of related Accounts where 
// each Account has a reference to its parent Shop
// using GroupBy and Select LINQ methods for nested mappings.
//
var shops = connection.Read<Shop, Account>(query)
    .GroupBy(item => item.Item1.Id)
    .Select(group =>
    {
        var shop = group.First().Item1;
        shop.Accounts = group.Select(item =>
        {
            var account = item.Item2;
            account.Shop = shop;
            return account;
        }).ToList();
        return shop;
    })
    .ToList();
using System.Linq;
using Norm;

const string query = @"
    select shop.*, account.*
    from shop 
    join account on shop.id = account.shop_id";
/*
"id"    "name"  "id"    "name"      "address"   "shop_id"
---------------------------------------------------------
1       "shop1" 3       "account3"  "addr3"     1
1       "shop1" 2       "account2"  "addr2"     1
1       "shop1" 1       "account1"  "addr1"     1
2       "shop2" 5       "account5"  "addr5"     2
2       "shop2" 4       "account4"  "addr4"     2
*/

//
// Each Shop instance will have a list of related Accounts.
//
public class Shop
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IList<Account> Accounts { get; set; }
}

//
// Each Account instance will have a reference to its parent Shop.
//
public class Account
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public int ShopId { get; set; }
    public Shop Shop { get; set; }
}

//
// Build a list of Shops with list of related Accounts where 
// each Account has a reference to its parent Shop
// using GroupBy and Select LINQ methods for nested mappings.
//
var shops = connection.Read<Shop, Account>(query)
    .GroupBy(item => item.Item1.Id)
    .Select(group =>
    {
        var shop = group.First().Item1;
        shop.Accounts = group.Select(item =>
        {
            var account = item.Item2;
            account.Shop = shop;
            return account;
        }).ToList();
        return shop;
    })
    .ToList();
Nullable instances behavior
You can configure Norm to return NULL values for instances that have all mapped properties NULL.

This is useful when mapping the result of left outer joins, where you want to get NULL values when joins doesn't match.
using System.Linq;
using Norm;

//
// Set mapping behavior to nullable instances in your startup.
//
// If instance has all properties or fields null, 
// that instance will be null by default.
// 
NormOptions.Configure(options =>
{
    options.NullableInstances = true;
});

//
// Get the shop and account instance by shop id = 1
//
var (shop, account) = connection
    .Read<Shop, Account>(@"
        select 
            shop.*, account.*
        from shop 
        left outer join account 
            on shop.id = account.shop_id
        where 
            shop.id = @id", 1) // id = 1
    .Single();
    
// For @id = 1, this is the result:

/*
"id"    "name"  "id"    "name"  "address"   "shop_id"
-----------------------------------------------------
1       "shop1" NULL    NULL    NULL        NULL
*/

Assert.NotNull(shop); // shop is not null
Assert.Null(account); // account is null
using System.Linq;
using Norm;

//
// Set mapping behavior to nullable instances in your startup.
//
// If instance has all properties or fields null, 
// that instance will be null by default.
// 
NormOptions.Configure(options =>
{
    options.NullableInstances = true;
});

//
// Get the shop and account instance by shop id = 1
//
var (shop, account) = connection
    .Read<Shop, Account>(@"
        select 
            shop.*, account.*
        from shop 
        left outer join account 
            on shop.id = account.shop_id
        where 
            shop.id = @id", 1) // id = 1
    .Single();
    
// For @id = 1, this is the result:

/*
"id"    "name"  "id"    "name"  "address"   "shop_id"
-----------------------------------------------------
1       "shop1" NULL    NULL    NULL        NULL
*/

Assert.NotNull(shop); // shop is not null
Assert.Null(account); // account is null
documentation
Map to complex types like enums or arrays
You can map to complicated types like enums or arrays. Nullable or non-nullable enums are supported.

Enums can be mapped from either string or integer values.
using System.Linq;
using Norm;

public enum TestEnum { Value1, Value2 }

public class TestEnumClass 
{
    public TestEnum FromText { get; set; }
    public TestEnum FromInt{ get; set; }
    // PostgerSQL only
    public TestEnum[] ArrayFromText { get; set; }
    // PostgerSQL only
    public TestEnum[] ArrayFromInt { get; set; }
}

//
// Map Enum types from text or from integer columns in the database.
// Null values are also supported.
// You can also map arrays of enums (PostgreSQL only).
//
var instances = connection.Read<TestEnumClass>(@"
    select 
        from_text, 
        from_int,
        ARRAY['Value1', 'Value2'] as array_from_text,
        ARRAY[0, 1] as array_from_int
    from (
    values 
        ('Value1', 0),
        ('Value2', 1)
    ) t(from_text, from_int,)")
    .ToArray();

// first row
Assert.Equal(TestEnum.Value1, instances[0].FromText);
Assert.Equal(TestEnum.Value1, instances[0].FromInt);
// second row
Assert.Equal(TestEnum.Value2, instances[1].FromText);
Assert.Equal(TestEnum.Value2, instances[1].FromInt);
// arrays (PostgreSQL only)
Assert.Equal(
    new[] { TestEnum.Value1, TestEnum.Value2 }, 
    instances[0].ArrayFromText);
Assert.Equal(
    new[] { TestEnum.Value1, TestEnum.Value2 }, 
    instances[0].ArrayFromInt);
using System.Linq;
using Norm;

public enum TestEnum { Value1, Value2 }

public class TestEnumClass 
{
    public TestEnum FromText { get; set; }
    public TestEnum FromInt{ get; set; }
    // PostgerSQL only
    public TestEnum[] ArrayFromText { get; set; }
    // PostgerSQL only
    public TestEnum[] ArrayFromInt { get; set; }
}

//
// Map Enum types from text or from integer columns in the database.
// Null values are also supported.
// You can also map arrays of enums (PostgreSQL only).
//
var instances = connection.Read<TestEnumClass>(@"
    select 
        from_text, 
        from_int,
        ARRAY['Value1', 'Value2'] as array_from_text,
        ARRAY[0, 1] as array_from_int
    from (
    values 
        ('Value1', 0),
        ('Value2', 1)
    ) t(from_text, from_int,)")
    .ToArray();

// first row
Assert.Equal(TestEnum.Value1, instances[0].FromText);
Assert.Equal(TestEnum.Value1, instances[0].FromInt);
// second row
Assert.Equal(TestEnum.Value2, instances[1].FromText);
Assert.Equal(TestEnum.Value2, instances[1].FromInt);
// arrays (PostgreSQL only)
Assert.Equal(
    new[] { TestEnum.Value1, TestEnum.Value2 }, 
    instances[0].ArrayFromText);
Assert.Equal(
    new[] { TestEnum.Value1, TestEnum.Value2 }, 
    instances[0].ArrayFromInt);
documentation
Run your statement in a prepared mode
If you need to run some or all commands in a prepared mode, you can do that by using the Prepared method.

Prepared mode is useful for PostgreSQL databases because explicit command preparation is recommended for better performance.
using System.Linq;
using Norm;

//
// Following query will be executed in prepared mode.
// Any Read, ReadAsync, Execute or ExecuteAsync method 
// after Prepared() will be executed in prepared mode.
//
var (p1, p2, p3) = connection
    .Prepared()
    .WithParameters(1, 2, 3) // parameters are positional
    .Read<int, int, int>("select @p1, @p2, @p3")
    .Single();


//
// Set global options in your startup 
// to always execute in prepared mode (default is false).
//
NormOptions.Configure(options =>
{
    options.Prepared = true;
});

// No need to call Prepared() anymore, every call is prepared.
var (p1, p2, p3) = connection
    .WithParameters(1, 2, 3) // parameters are positional
    .Read<int, int, int>("select @p1, @p2, @p3")
    .Single();
using System.Linq;
using Norm;

//
// Following query will be executed in prepared mode.
// Any Read, ReadAsync, Execute or ExecuteAsync method 
// after Prepared() will be executed in prepared mode.
//
var (p1, p2, p3) = connection
    .Prepared()
    .WithParameters(1, 2, 3) // parameters are positional
    .Read<int, int, int>("select @p1, @p2, @p3")
    .Single();


//
// Set global options in your startup 
// to always execute in prepared mode (default is false).
//
NormOptions.Configure(options =>
{
    options.Prepared = true;
});

// No need to call Prepared() anymore, every call is prepared.
var (p1, p2, p3) = connection
    .WithParameters(1, 2, 3) // parameters are positional
    .Read<int, int, int>("select @p1, @p2, @p3")
    .Single();
documentation
Take advantage of specific PostgreSQL optimizations
Norm has several built-in support for PostgreSQL-specific optimizations. You can even say it was made for PostgreSQL (although it works with other databases as well).

For example, you can use specific PostgreSQL paremeters format and disable Npgsql query rewriting. See this article.

You can also skip Npgsql parsing type parsing and use direct PostgreSQL text format instead.
using System.Linq;
using Norm;

//
// When using PostgreSQL, you can use PostgreSQL paremeter format.
// This will allow Npgsql to skip query rewriting.
//
var (s, i, b, d) = connection
    // parameters are positional
    .WithParameters("str", 999, true, new DateTime(1977, 5, 19))
    .Read<string, int, bool, DateTime, string>("select $1, $2, $3, $4")
    .Single();

//
// You can globally disable query rewriting by setting 
// NpgsqlEnableSqlRewriting to false in your startup.
// This will ensure that all queries are intact by underlying Npgsql driver 
// (skips query rewriting). 
// In that case, PostgreSQL paremeter format is required always.
//
NormOptions.Configure(options =>
{
    options.NpgsqlEnableSqlRewriting = false;
});

//
// Marks all of the query result columns as unknown.
// Unknown result columns are requested from PostgreSQL in text format, 
// and Npgsql makes no attempt to parse them (accessible as strings).
//
// This is useful when you want to avoid the overhead of parsing 
// or you want to fetch some exotic type from PostgreSQL.
//
// Note: you can write your own parser with `WithReaderCallback` method.
// 
var (s, i, b, d) = connection
    .WithUnknownResultType()
    .WithParameters("str", 999, true, new DateTime(1977, 5, 19))
    .Read<string, string, string, string, string>("select $1, $2, $3, $4")
    .Single();

//
// Marks all of the query result columns as unknown, except first one.
//
var (s, i, b, d) = connection
    .WithUnknownResultType(false, true, true, true)
    .WithParameters("str", 999, true, new DateTime(1977, 5, 19))
    .Read<string, string, string, string, string>("select $1, $2, $3, $4")
    .Single();
using System.Linq;
using Norm;

//
// When using PostgreSQL, you can use PostgreSQL paremeter format.
// This will allow Npgsql to skip query rewriting.
//
var (s, i, b, d) = connection
    // parameters are positional
    .WithParameters("str", 999, true, new DateTime(1977, 5, 19))
    .Read<string, int, bool, DateTime, string>("select $1, $2, $3, $4")
    .Single();

//
// You can globally disable query rewriting by setting 
// NpgsqlEnableSqlRewriting to false in your startup.
// This will ensure that all queries are intact by underlying Npgsql driver 
// (skips query rewriting). 
// In that case, PostgreSQL paremeter format is required always.
//
NormOptions.Configure(options =>
{
    options.NpgsqlEnableSqlRewriting = false;
});

//
// Marks all of the query result columns as unknown.
// Unknown result columns are requested from PostgreSQL in text format, 
// and Npgsql makes no attempt to parse them (accessible as strings).
//
// This is useful when you want to avoid the overhead of parsing 
// or you want to fetch some exotic type from PostgreSQL.
//
// Note: you can write your own parser with `WithReaderCallback` method.
// 
var (s, i, b, d) = connection
    .WithUnknownResultType()
    .WithParameters("str", 999, true, new DateTime(1977, 5, 19))
    .Read<string, string, string, string, string>("select $1, $2, $3, $4")
    .Single();

//
// Marks all of the query result columns as unknown, except first one.
//
var (s, i, b, d) = connection
    .WithUnknownResultType(false, true, true, true)
    .WithParameters("str", 999, true, new DateTime(1977, 5, 19))
    .Read<string, string, string, string, string>("select $1, $2, $3, $4")
    .Single();
Access to low-level data reader
You can gain access to the low-level data reader object (type DbDataReader) - created for every query command. Reader callback accepts returning values and passes them down to the default mapper.
This provides the ability to do some interesting stuff, like:
- Do some custom mapping.
- Return alternative values.
- Create complex objects with different reader values, etc.

The following example shows how to return a value incremented by 1 a) for all columns at the first position and b) for all columns with the name "i".
using System.Linq;
using Norm;

const string query = @"
        select * from (values 
            (1, 1),
            (2, 2),
            (3, 3)
        ) t(i, j)";

//
// You can gain access to the underlying DbDataReader by 
// using the WithReaderCallback method.
//
// This method allows you to perform any custom processing on the DbDataReader.
// Return null to indicate that the default value should be used.
// If you need to return a literal null value, return `DBNull.Value`.
//
// This example will i and j tuple, where i value is incremented by 1.
// 
var result = connection
    .WithReaderCallback(r => r.Ordinal switch
    {
        0 => r.Reader.GetInt32(r.Ordinal) + 1,
        _ => null // default value
    })
    .Read<(int i, int j)>(query)
    .ToArray();

//
// You can also use the column name instead of the ordinal position.
//
var result = connection
    .WithReaderCallback(arg => arg.Name switch
    {
        "i" => arg.Reader.GetInt32(arg.Ordinal) + 1,
        _ => null // default value
    })
    .Read<(int i, int j)>(query)
    .ToArray();

//
// You can also declare a method that implements the reader callback
//
private object? ReaderCallback((string Name, int Ordinal, DbDataReader Reader) arg)
{
    return arg => arg.Name switch
    {
        "i" => arg.Reader.GetInt32(arg.Ordinal) + 1,
        _ => null // default value
    }
}

// map reader callback to method
var result = connection
    .WithReaderCallback(ReaderCallback)
    // map to anonymous type by name
    .Read(new { i = default(int), j = default(int) }, query)
    .ToArray();
using System.Linq;
using Norm;

const string query = @"
        select * from (values 
            (1, 1),
            (2, 2),
            (3, 3)
        ) t(i, j)";

//
// You can gain access to the underlying DbDataReader by 
// using the WithReaderCallback method.
//
// This method allows you to perform any custom processing on the DbDataReader.
// Return null to indicate that the default value should be used.
// If you need to return a literal null value, return `DBNull.Value`.
//
// This example will i and j tuple, where i value is incremented by 1.
// 
var result = connection
    .WithReaderCallback(r => r.Ordinal switch
    {
        0 => r.Reader.GetInt32(r.Ordinal) + 1,
        _ => null // default value
    })
    .Read<(int i, int j)>(query)
    .ToArray();

//
// You can also use the column name instead of the ordinal position.
//
var result = connection
    .WithReaderCallback(arg => arg.Name switch
    {
        "i" => arg.Reader.GetInt32(arg.Ordinal) + 1,
        _ => null // default value
    })
    .Read<(int i, int j)>(query)
    .ToArray();

//
// You can also declare a method that implements the reader callback
//
private object? ReaderCallback((string Name, int Ordinal, DbDataReader Reader) arg)
{
    return arg => arg.Name switch
    {
        "i" => arg.Reader.GetInt32(arg.Ordinal) + 1,
        _ => null // default value
    }
}

// map reader callback to method
var result = connection
    .WithReaderCallback(ReaderCallback)
    // map to anonymous type by name
    .Read(new { i = default(int), j = default(int) }, query)
    .ToArray();
documentation
Implement your own custom mapping for entire application
You can configure a global reader callback that will be used for all queries in your application.

This is useful when you want to implement your custom mapping for the entire application.
For example, I want all database type json to be mapped to JsonObject type always.

Here is how to do that:
using System.Linq;
using System.Text.Json.Nodes;
using Norm;

//
// You can also set a global reader callback
// that will be executed for every query in the application.
//
// This is a great way to implement custom type mapping.
// Here is an example of how to map all database `json` types to `JsonObject`
//

// Reader callback can be in a expression method instead of a lambda function
private object? ReaderCallback((string Name, int Ordinal, DbDataReader Reader) arg) => 
    // switch over the column type
    arg.Reader.GetDataTypeName(arg.Ordinal) switch
{
    // if the column type is json, then convert it to JsonObject
    "json" => JsonNode.Parse(arg.Reader.GetString(arg.Ordinal))?.AsObject(),
    _ => null // default value
};

// Set the option in the your startup code...
NormOptions.Configure(options => options.DbReaderCallback = ReaderCallback);

// example class (I is string and J is JsonObject)
private class JsonTest
{
    public string I { get; set; }
    public JsonObject J { get; set; }
}

// example query
var result = connection
    .Read<JsonTest>(
        // column i is a text and column j is a json
        "select '{\"a\": 1}'::text as i, '{\"a\": 1}'::json as j")
    .Single();

// assertion
Assert.IsType<JsonObject>(result.J); // property J is JsonObject
Assert.Equal(1, (int)result.J["a"]); // property J has a property "a" with value 1
using System.Linq;
using System.Text.Json.Nodes;
using Norm;

//
// You can also set a global reader callback
// that will be executed for every query in the application.
//
// This is a great way to implement custom type mapping.
// Here is an example of how to map all database `json` types to `JsonObject`
//

// Reader callback can be in a expression method instead of a lambda function
private object? ReaderCallback((string Name, int Ordinal, DbDataReader Reader) arg) => 
    // switch over the column type
    arg.Reader.GetDataTypeName(arg.Ordinal) switch
{
    // if the column type is json, then convert it to JsonObject
    "json" => JsonNode.Parse(arg.Reader.GetString(arg.Ordinal))?.AsObject(),
    _ => null // default value
};

// Set the option in the your startup code...
NormOptions.Configure(options => options.DbReaderCallback = ReaderCallback);

// example class (I is string and J is JsonObject)
private class JsonTest
{
    public string I { get; set; }
    public JsonObject J { get; set; }
}

// example query
var result = connection
    .Read<JsonTest>(
        // column i is a text and column j is a json
        "select '{\"a\": 1}'::text as i, '{\"a\": 1}'::json as j")
    .Single();

// assertion
Assert.IsType<JsonObject>(result.J); // property J is JsonObject
Assert.Equal(1, (int)result.J["a"]); // property J has a property "a" with value 1
documentation
Logging and analytics
Besides access to the data reader, you can gain access to the DbCommand object also, for all commands created by Norm.

By using global command callback, you can implement logging and analytics for all queries in your application, see the example below.

To improve application analytics, you can configure Norm to automatically add a comment header to every command text that can contain the following information:
- Command type and timeout.
- Exact source file name and line number (from compiler metadata).
- Parameter names and values.

This information will not only be visible when you log commands, but it will also be visible in your database administration tools such as Activity Monitor for SQL Server or pg_stat_activity on PostgreSQL which can be very useful for database administrators.
using System.Linq;
using Norm;

//
// Set global handler in your startup 
// for each DbCommand.created by Norm.
//
// You can use this to log every query executed with Norm in your application.
//
NormOptions.Configure(options => 
    options.DbCommandCallback = command => 
        logger.LogInformation(command.CommandText));

// ... later in your application

var result = connection
    .Read<FooBar>(@"select foo, bar from foo_bar where id = @id", id)
    .ToArray();

//
// Console output:
//
// info: Application.Logger[0]
//       select foo, bar from foo_bar where id = @id
//

//
// Configure Norm to include comment header in each query 
// that will include one or more entries like:
// - Comand type (Text, StoredProcedure, TableDirect) and timeout.
// - Method name and source file path from compiler metadata.
// - Parameter values.
// - Custom comments.
//
// Example:
//
NormOptions.Configure(options => 
{
    options.CommandCommentHeader.Enabled = true;
    options.DbCommandCallback = command =>  
        logger.LogInformation(command.CommandText);
});

// ... later in your application

var result = connection
    .Read<FooBar>(@"select foo, bar from foo_bar where id = @id", id)
    .ToArray();

//
// Console output:
//
// info: Application.Logger[0]
//       /*
// Sql Text Command. Timeout: 30 seconds.
// at GetData in /home/user/project/MyModule.cs#10
// @id = 1
// */
// select foo, bar from foo_bar where id = @id
//
using System.Linq;
using Norm;

//
// Set global handler in your startup 
// for each DbCommand.created by Norm.
//
// You can use this to log every query executed with Norm in your application.
//
NormOptions.Configure(options => 
    options.DbCommandCallback = command => 
        logger.LogInformation(command.CommandText));

// ... later in your application

var result = connection
    .Read<FooBar>(@"select foo, bar from foo_bar where id = @id", id)
    .ToArray();

//
// Console output:
//
// info: Application.Logger[0]
//       select foo, bar from foo_bar where id = @id
//

//
// Configure Norm to include comment header in each query 
// that will include one or more entries like:
// - Comand type (Text, StoredProcedure, TableDirect) and timeout.
// - Method name and source file path from compiler metadata.
// - Parameter values.
// - Custom comments.
//
// Example:
//
NormOptions.Configure(options => 
{
    options.CommandCommentHeader.Enabled = true;
    options.DbCommandCallback = command =>  
        logger.LogInformation(command.CommandText);
});

// ... later in your application

var result = connection
    .Read<FooBar>(@"select foo, bar from foo_bar where id = @id", id)
    .ToArray();

//
// Console output:
//
// info: Application.Logger[0]
//       /*
// Sql Text Command. Timeout: 30 seconds.
// at GetData in /home/user/project/MyModule.cs#10
// @id = 1
// */
// select foo, bar from foo_bar where id = @id
//
documentation
Comments