AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql 2.0.0

dotnet add package AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql --version 2.0.0
                    
NuGet\Install-Package AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql -Version 2.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="AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="AdaskoTheBeAsT.Dapper.GraphQL.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 AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql --version 2.0.0
                    
#r "nuget: AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql, 2.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 AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql@2.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=AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql&version=2.0.0
                    
Install as a Cake Tool

πŸš€ Dapper.GraphQL

License: MIT NuGet Downloads .NET PRs welcome

⚑ Generate exactly the SQL your GraphQL client asked for β€” no more, no less.

Dapper.GraphQL bridges Dapper and GraphQL.NET so the SQL you execute mirrors the GraphQL selection set: only requested columns are projected, only required joins are emitted, and Dapper's QueryMultiple machinery materialises the nested object graph back into your POCOs. 🎯


πŸ“š Table of contents


✨ Why use it

  • 🎯 No over-fetching. Selected GraphQL fields drive SELECT projection and JOIN emission.
  • πŸ”— One round-trip for object graphs. Nested entities load through Dapper's multi-mapping with splitOn automatically configured.
  • πŸ—„οΈ Vendor-aware identity retrieval. PostgreSQL nextval/currval, SQL Server SCOPE_IDENTITY(), MySQL LAST_INSERT_ID(), SQLite last_insert_rowid(), Oracle sequences β€” all behind the same ExecuteWith*Identity extension.
  • 🎨 Strongly typed builders. SqlBuilder.Insert(person) / SqlBuilder.Update(person) / SqlBuilder.Delete<Person>() derive table and parameter names from your entity types.
  • 🧩 First-class DI. Vendor-specific AddDapperGraphQl<Vendor> extension wires query builders, schemas, and a vendor-aware SqlBuilderOptions (parameter prefix etc.) into your container.
  • ⚑ Performance first. Built on Dapper's lightning-fast micro-ORM β€” zero reflection at hot path, minimal allocations.
  • πŸ“¦ Multi-target. net8.0, net9.0, net10.0, net472, net48, net481.

πŸ“¦ Packages

Package What's inside
🧱 AdaskoTheBeAsT.Dapper.GraphQL Vendor-agnostic core: SqlBuilder, query / insert / update / delete contexts, entity mappers, IQueryBuilder<T>, DI options object.
🐘 AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql AddDapperGraphQlPostgreSql, PostgreSqlIdentity.NextIdentity[Async], ExecuteWithPostgreSqlIdentity[Async].
πŸ—„οΈ AdaskoTheBeAsT.Dapper.GraphQL.SqlServer AddDapperGraphQlSqlServer, ExecuteWithSqlServerIdentity[Async].
🐬 AdaskoTheBeAsT.Dapper.GraphQL.MySql AddDapperGraphQlMySql, ExecuteWithMySqlIdentity[Async].
πŸͺΆ AdaskoTheBeAsT.Dapper.GraphQL.Sqlite AddDapperGraphQlSqlite, ExecuteWithSqliteIdentity[Async].
πŸ¦… AdaskoTheBeAsT.Dapper.GraphQL.Oracle AddDapperGraphQlOracle, OracleIdentity.NextIdentity[Async], ExecuteWithOracleIdentity[Async], BindByNameOracleConnection.

πŸ’‘ Pick exactly one vendor package per database. The vendor package transitively brings the core in.


⬇️ Install

dotnet add package AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql
# or .SqlServer / .MySql / .Sqlite / .Oracle

That's it. The vendor package brings in the core. There is no separate ServiceCollection package β€” DI lives in each vendor package. βœ…


⚑ Five-minute quickstart

1️⃣ Define your entities

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public List<Email> Emails { get; set; } = [];
}

public class Email
{
    public int Id { get; set; }
    public int PersonId { get; set; }
    public string Address { get; set; } = "";
}

2️⃣ Write query builders that mirror GraphQL β†’ SQL

public class EmailQueryBuilder : IQueryBuilder<Email>
{
    public SqlQueryContext Build(SqlQueryContext query, IHasSelectionSetNode ctx, string alias)
    {
        query.Select($"{alias}.Id");
        query.SplitOn<Email>("Id");

        var fields = ctx.GetSelectedFields();
        if (fields.ContainsKey("address")) query.Select($"{alias}.Address");
        return query;
    }
}

public class PersonQueryBuilder(IQueryBuilder<Email> emails) : IQueryBuilder<Person>
{
    public SqlQueryContext Build(SqlQueryContext query, IHasSelectionSetNode ctx, string alias)
    {
        query.Select($"{alias}.Id");
        query.SplitOn<Person>("Id");

        var fields = ctx.GetSelectedFields();
        if (fields.ContainsKey("firstName")) query.Select($"{alias}.FirstName");
        if (fields.ContainsKey("lastName"))  query.Select($"{alias}.LastName");

        if (fields.TryGetValue("emails", out var emailField))
        {
            query.LeftJoin($"Email email ON {alias}.Id = email.PersonId");
            query = emails.Build(query, emailField, "email");
        }

        return query;
    }
}

3️⃣ Register everything (vendor-specific DI extension)

services.AddDapperGraphQlPostgreSql(o =>
{
    o.AddType<PersonType>();
    o.AddType<EmailType>();
    o.AddSchema<PersonSchema>();

    o.AddQueryBuilder<Person, PersonQueryBuilder>();
    o.AddQueryBuilder<Email,  EmailQueryBuilder>();
});

For SQL Server use AddDapperGraphQlSqlServer, for MySQL AddDapperGraphQlMySql, etc. Each one registers the matching *SqlBuilderOptions (which carries the correct parameter prefix: @ for PostgreSQL/SQL Server/MySQL/SQLite, : for Oracle).

4️⃣ Resolve from a connection wrapped with vendor options

var people = Field<ListGraphType<PersonType>>("people")
    .Resolve(ctx =>
    {
        var query = SqlBuilder
            .From("Person person")
            .Where("person.IsActive = @isActive", new { isActive = true });

        query = personQueryBuilder.Build(query, ctx.FieldAst, "person");

        using var raw    = new NpgsqlConnection(_connStr);
        using var conn   = raw.WithDapperGraphQlOptions(_options); // vendor-aware prefix
        var mapper       = new PersonEntityMapper();

        return query.Execute(conn, mapper, ctx.FieldAst);
    });

WithDapperGraphQlOptions makes the generated SQL match the connection's parameter prefix β€” important on Oracle where parameters use : instead of @. πŸͺ„

5️⃣ Insert with vendor-aware identity retrieval

using AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql.Extensions;

var newId = SqlBuilder
    .Insert(person)
    .ExecuteWithPostgreSqlIdentity(conn, p => p.Id);
Vendor Identity call
🐘 PostgreSQL ExecuteWithPostgreSqlIdentity(conn, p => p.Id)
πŸ—„οΈ SQL Server ExecuteWithSqlServerIdentity<int>(conn)
🐬 MySQL ExecuteWithMySqlIdentity<int>(conn)
πŸͺΆ SQLite ExecuteWithSqliteIdentity<int>(conn)
πŸ¦… Oracle ExecuteWithOracleIdentity(conn, p => p.Id)

🧠 How it works

GraphQL request:

{
  people {
    firstName
    emails { address }
  }
}

What Dapper.GraphQL runs:

SELECT person.Id, person.FirstName,
       email.Id, email.Address
FROM Person person
LEFT JOIN Email email ON person.Id = email.PersonId
WHERE person.IsActive = @isActive;

lastName is never selected because the client never asked for it. βœ‚οΈ Add a phones { number } selection to the GraphQL query and a second join + columns appear automatically β€” driven by the matching query builder in DI. 🎩✨

The SqlQueryContext.Execute(...) overload uses Dapper's multi-mapping with the splitOn columns each builder reported via SplitOn<T>(...), then hands the row tuples to your EntityMapper<T> to assemble the object graph.


🧩 Core concepts

SqlBuilder πŸ—οΈ

Static fluent factory for the four context types:

SqlBuilder.From("Person p");
SqlBuilder.Insert(person);
SqlBuilder.Update(person);
SqlBuilder.Delete<Person>();

IQueryBuilder<T> 🧱

One per entity. Reads the GraphQL selection set, appends columns / joins to a SqlQueryContext, and returns it. Builders compose: a PersonQueryBuilder asks an injected IQueryBuilder<Email> to extend the same context.

EntityMapper<T> and DeduplicatingEntityMapper<T> πŸͺ‘

Multi-mapping turns one logical row into many physical rows once a LEFT JOIN expands a collection. Entity mappers stitch those rows back into a single object graph and (with DeduplicatingEntityMapper<T>) collapse duplicate parent rows.

SqlBuilderOptions and IDapperGraphQlConnection πŸ”Œ

Vendor packages register a *SqlBuilderOptions (singleton) that knows the correct parameter prefix. Wrap your raw DbConnection with .WithDapperGraphQlOptions(options) so the generated SQL uses the right prefix for that vendor.


πŸ”§ Advanced usage

Chained inserts ⛓️

SqlBuilder
    .Insert(person)
    .Insert("Email", new { Address = "a@b.com", PersonId = personId })
    .Execute(conn);

Async everywhere ⏱️

await SqlBuilder.Update(person).ExecuteAsync(conn);
await SqlBuilder.Delete<Person>(new { Id = 1 }).ExecuteAsync(conn);
await PostgreSqlIdentity.NextIdentityAsync<Person, int>(conn, p => p.Id);

Self-references and many-to-many πŸ•ΈοΈ

The integration test suite in test/integ/AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql.IntegrationTest demonstrates self-referencing entities (Person.Supervisor, Person.CareerCounselor) and many-to-many (Person ↔ Company) with composite join tables β€” copy those query builders as templates.

Connection pagination (Relay) πŸ“œ

The PostgreSQL test fixture wires up GraphQL.NET v8 ConnectionType<> and ConnectionType<,> and uses cursor encoding helpers β€” see GraphQLTests.cs.


πŸ“… DateOnly / TimeOnly per vendor

The library targets net472/net48/net481 (where DateOnly does not exist) and net8.0/net9.0/net10.0 (where it does). Use conditional compilation in your entities:

public class Person
{
#if NET6_0_OR_GREATER
    public DateOnly CreateDate { get; set; }
#else
    public DateTime CreateDate { get; set; }
#endif
}
Provider DateOnly support Action required
🐘 Npgsql (PostgreSQL) βœ… Native since 6.0 None.
πŸͺΆ Microsoft.Data.Sqlite βœ… Native since 6.0 None.
πŸ—„οΈ SqlClient (SQL Server) ⚠️ Dapper needs help Register TypeHandler<DateOnly> etc.
🐬 MySqlConnector ⚠️ Dapper needs help; can throw InvalidCastException Register custom TypeHandlers.
πŸ¦… Oracle ⚠️ Dapper needs help Register custom TypeHandler<DateOnly>.

Minimal SQL Server / MySQL / Oracle handler:

public sealed class DapperDateOnlyHandler : SqlMapper.TypeHandler<DateOnly>
{
    public override DateOnly Parse(object value) => DateOnly.FromDateTime((DateTime)value);
    public override void SetValue(IDbDataParameter parameter, DateOnly value)
    {
        parameter.DbType = DbType.Date;
        parameter.Value  = value.ToDateTime(TimeOnly.MinValue);
    }
}

SqlMapper.AddTypeHandler(new DapperDateOnlyHandler());

πŸ’‘ Tip. Always use strongly typed Dapper queries (Query<Person>(...)) β€” dynamic queries can return DateTime even where DateOnly is registered.


🚨 Upgrading from 1.x

2.0.0 splits the previously monolithic AdaskoTheBeAsT.Dapper.GraphQL package into a vendor-agnostic core plus five vendor-specific packages, and removes the standalone AdaskoTheBeAsT.Dapper.GraphQL.ServiceCollection package β€” DI moves into each vendor package.

πŸ“– See MIGRATION.md for the full upgrade guide, namespace mapping, and copy-pasteable before/after snippets.

TL;DR:

  1. πŸ”„ Replace AdaskoTheBeAsT.Dapper.GraphQL.ServiceCollection with the matching vendor package (*.PostgreSql, *.SqlServer, *.MySql, *.Sqlite, *.Oracle).
  2. πŸ”„ Replace services.AddDapperGraphQL(...) with services.AddDapperGraphQl<Vendor>(...).
  3. πŸ”„ Replace using AdaskoTheBeAsT.Dapper.GraphQL.Extensions; with the vendor namespace (e.g. AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql.Extensions).
  4. πŸ”„ Rename ExecuteWithSqlIdentity β†’ ExecuteWithSqlServerIdentity.

If you only used the vendor-agnostic surface (SqlBuilder, SqlQueryContext, EntityMapper) bumping the package version is enough. βœ…


πŸ› οΈ Building & testing

Prerequisites

  • 🟣 .NET 10 SDK
  • 🐳 Docker β€” required by the integration test suites; each fixture spins up its own container via Testcontainers for PostgreSQL / SQL Server / MySQL / Oracle. SQLite uses an in-memory file.

Run everything

dotnet restore
dotnet build  -c Release
dotnet test   -c Release

Run a single integration suite

dotnet test test/integ/AdaskoTheBeAsT.Dapper.GraphQL.PostgreSql.IntegrationTest

🀝 Contributing

PRs welcome! Please:

  1. πŸ’¬ Open an issue first if you're proposing a behaviour change or new public API.
  2. βœ… Add / extend integration tests for the affected vendor(s).
  3. 🧹 Keep dotnet build /warnaserror clean β€” the repo treats warnings as errors.

Standard fork β†’ branch β†’ PR flow:

git checkout -b feature/amazing-thing
git commit -m "feat: amazing thing"
git push origin feature/amazing-thing

πŸ‘ Credits & license

Originally created by the Landmark Home Warranty team:

  • Doug Day
  • Kevin Russon
  • Ben McCallum
  • Natalya Arbit
  • Per Liedman
  • John Stovin

Maintained by @AdaskoTheBeAsT. πŸ’œ

Released under the MIT License. πŸ“„


Made with ❀️ for developers who love clean code and fast queries.

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 is compatible.  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 is compatible.  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. 
.NET Framework net472 is compatible.  net48 is compatible.  net481 is compatible. 
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
2.0.0 90 5/18/2026