SqlArtisan.ArrayBind
0.9.0-beta.1
Prefix Reserved
dotnet add package SqlArtisan.ArrayBind --version 0.9.0-beta.1
NuGet\Install-Package SqlArtisan.ArrayBind -Version 0.9.0-beta.1
<PackageReference Include="SqlArtisan.ArrayBind" Version="0.9.0-beta.1" />
<PackageVersion Include="SqlArtisan.ArrayBind" Version="0.9.0-beta.1" />
<PackageReference Include="SqlArtisan.ArrayBind" />
paket add SqlArtisan.ArrayBind --version 0.9.0-beta.1
#r "nuget: SqlArtisan.ArrayBind, 0.9.0-beta.1"
#:package SqlArtisan.ArrayBind@0.9.0-beta.1
#addin nuget:?package=SqlArtisan.ArrayBind&version=0.9.0-beta.1&prerelease
#tool nuget:?package=SqlArtisan.ArrayBind&version=0.9.0-beta.1&prerelease
SqlArtisan
Write SQL, in C#. A type-safe SQL query builder and deterministic guard rail for AI-assisted SQL — the SQL you write is the SQL that runs, allocation-light, fast, and automatically parameterized.
Why SqlArtisan?
Does this sound familiar?
- Your ORM can't express the query, so you fall back to raw SQL strings.
- Building a query dynamically means concatenating strings through your logic.
- Every value needs a hand-written
DbParameter— tedious and easy to get wrong.
SqlArtisan is SQL, written in C#: a type-safe, SQL-like API where literals become bind parameters automatically and the SQL you write is the SQL that runs.
When an AI coding assistant writes your SQL, a new failure mode appears: plausible queries that look right but mix dialects, misname columns, or silently change semantics. SqlArtisan is a deterministic guard rail — four verification layers catch these mistakes before the query reaches a database:
- Types — column names and statement structure are compile-checked; a wrong name fails the build.
- Analyzer — the opt-in Roslyn analyzer flags constructs your target dialect does not support, catching the most common AI failure mode (mixing dialects from training data).
- Exact-SQL tests —
Build()is deterministic, so a unit test pins the reviewed SQL as a regression contract. - Integration matrix — analyzer entries are verified against live engines.
bool onlyActive = true;
UsersTable u = new();
SqlStatement sql =
Select(u.Id, u.Name, u.CreatedAt)
.From(u)
.Where(u.Id > 0 & u.Name.Like("A%") & ConditionIf(onlyActive, u.StatusId == 1))
.Build();
// SELECT id, name, created_at FROM users WHERE (id > :0) AND (name LIKE :1) AND (status_id = :2)
All the convenience, minimal overhead: an allocation-light, fast builder benchmarked. Focus on the query, not the plumbing.
Contents
- Key Features
- Performance
- Packages
- Getting Started
- Configuration
- Design Philosophy
- Documentation
- Versioning & Support
- Contributing
- Changelog
- License
Key Features
- SQL-like API: queries read like the SQL they emit.
- Schema IntelliSense: table/column completion from generated table classes — no stringly-typed names.
- Allocation-light & fast: pooled buffers keep it nearly as lean as a hand-written
StringBuilder(benchmarks). - Automatic parameterization: literals become bind parameters, preventing SQL injection through values.
- Dynamic conditions: add or drop
WHEREparts at runtime with helpers likeConditionIf. - Dapper integration: optional
SqlArtisan.Dapperadds one-call execution. - Oracle array-bind execution: optional
SqlArtisan.ArrayBindruns SqlArtisan-built statements for thousands of rows in one round trip via ODP.NET array binding. - Dialect-aware analyzer: an opt-in Roslyn analyzer that deterministically flags constructs your target dialect does not support — the second layer of the guard-rail stack (docs).
Performance
SqlArtisan minimizes heap allocations — string buffers are recycled from a pooled ArrayPool<T> — so it adds little GC pressure on hot paths. On a fair, like-for-like BenchmarkDotNet workload, where every entrant builds the same query's SQL string and its bind parameters, it is the lowest-allocation and fastest builder; only a hand-written StringBuilder (no type safety, no dialect handling) is lighter.
| Method | Category | Mean | Allocated |
|---|---|---|---|
| StringBuilder_DapperDynamicParams | Baseline¹ | 630.9 ns | 1.92 KB |
| SqlArtisan_SpecificParams | Builders | 1,451.6 ns | 2.16 KB |
| Sqlify_SpecificParams | Builders | 1,871.0 ns | 3.13 KB |
| SqlArtisan_DapperDynamicParams | Builders | 1,892.5 ns | 2.84 KB |
| InterpolatedSql_SpecificParams | Builders | 2,749.7 ns | 5.11 KB |
| DapperSqlBuilder_DapperDynamicParams | Builders | 2,762.2 ns | 5.70 KB |
| Linq2db_TypedParams | Builders | 44,173.3 ns | 19.13 KB |
| SqlKata_SpecificParams³ | Builders | 50,577.6 ns | 40.54 KB |
| EfCore_Reference | ORM reference² | 49,728.5 ns | 12.86 KB |
The allocation lead is firm (lightweight builders allocate the same bytes every run); treat the timing order as directional, since run-to-run variance grows for the heavier entrants.
¹ Raw StringBuilder + Dapper DynamicParameters — the floor, with no type safety or dialect handling. ² EF Core is a full-ORM reference (different work, caches compiled queries), shown only for scale. ³ Understated: this row predates a fix to the SqlKata entrant, which had been building a lighter query than the others; it now allocates about half again as much. Every other row is unaffected, and SqlKata was already the heaviest builder.
Measured on .NET 8.0.28, i5-1135G7 / 16 GB / Windows 11, PostgreSQL dialect. Query shape, library versions, and re-run instructions are in the benchmark project's README.
Packages
Getting Started
Prerequisites
- .NET 8.0 or later.
- Choose the API for your target DBMS (e.g.
Systimestampfor Oracle vsCurrentTimestampfor PostgreSQL). Bind-parameter prefixes (:/@/?) are then handled for you — verified for MySQL, Oracle, PostgreSQL, SQLite, and SQL Server. - (Optional)
SqlArtisan.Dapperauto-detects the dialect from yourIDbConnectionand adds execution methods. - (Optional)
SqlArtisan.ArrayBindadds Oracle array-bind execution for SqlArtisan-built statements.
Installation
Packages are pre-release, so pass --prerelease:
dotnet add package SqlArtisan --prerelease # core query builder
dotnet add package SqlArtisan.Dapper --prerelease # optional: Dapper execution
dotnet add package SqlArtisan.ArrayBind --prerelease # optional: Oracle array-bind execution
Quick Start
Define your Table Class
Create a C# table class for each database table to enable IntelliSense and prevent typos in names. Write it manually (see below) or generate it from an existing database with the
SqlArtisan.TableClassGentool. (For a one-off query you can skip the class and name a table inline withDbTable.)using SqlArtisan; // ... internal sealed class UsersTable : DbTableBase { public UsersTable(string tableAlias = "") : base("users", tableAlias) { Id = new DbColumn(this, "id"); Name = new DbColumn(this, "name"); CreatedAt = new DbColumn(this, "created_at"); } public DbColumn Id { get; } public DbColumn Name { get; } public DbColumn CreatedAt { get; } }Define your DTO Class
A plain class to map query results onto:
internal sealed class UserDto(int id, string name, DateTime createdAt) { public int Id => id; public string Name => name; public DateTime CreatedAt => createdAt; }Build and Execute your Query
Add
using static SqlArtisan.Sql;for the entry-point methods (Select,InsertInto, …), build the query, and execute it — here with Dapper:using SqlArtisan; using SqlArtisan.Dapper; using static SqlArtisan.Sql; // ... UsersTable u = new(); ISqlBuilder sql = Select(u.Id, u.Name, u.CreatedAt) .From(u) .Where(u.Id > 0 & u.Name.Like("A%")) .OrderBy(u.Id); // Dapper: Set true to map snake_case columns to PascalCase/camelCase C# members. Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; // 'connection' is your IDbConnection. SqlArtisan auto-detects the DBMS // (MySQL, Oracle, PostgreSQL, SQLite, SQL Server) & applies // the correct bind-parameter prefix (e.g., ':' or '@'). IEnumerable<UserDto> users = await connection.QueryAsync<UserDto>(sql);Note:
sqlabove is single-use —Build()runs insideQueryAsync(and every other Dapper call) the moment you call it, so passing the samesqlto a second call, e.g. to fetch another page, throws. Build a fresh query per call instead. See Reusing a builder chain.Alternative: Manual Execution
Not using Dapper? Call
Build()to get the SQL string and its parameters for raw ADO.NET, another micro-ORM, or debugging.Build()takes an optionalDbms(defaultPostgreSql) that sets dialect details like the parameter prefix.SqlStatement sql = Select(u.Id, u.Name).From(u).Where(u.Id == 10).Build(); // sql.Text => "SELECT id, name FROM users WHERE id = :0" // sql.Parameters => ":0" is 10Note: the single-use rule above applies here too — calling
Build()twice on the same instance throws.
Configuration
Setting the Default DBMS
Build()'s Dbms argument defaults to PostgreSQL. Set a global default once at startup instead of passing it in every call.
// At application startup
SqlArtisanConfig.SetDefaultDbms(Dbms.SqlServer);
// Now, Build() without arguments will generate SQL Server-compatible SQL
SqlStatement sql = Select(u.Name).From(u).Build();
Note: SqlArtisanConfig is not thread-safe and should be configured only once at application startup.
Design Philosophy
SqlArtisan's principle is simple: the SQL you write is the SQL that runs. Your C# maps directly to SQL — no translation layer second-guesses your intent.
Full ORMs hide SQL behind object graphs; portability-focused builders rewrite your query per database. SqlArtisan does neither. It's for developers who want to write SQL — type-safely and composably in C#, not as fragile strings.
It normalizes only mechanical dialect details — bind-parameter markers (:0 vs @0), identifier quoting (" vs MySQL's backtick), and same-meaning token spellings such as the UPSERT excluded-row name — and never rewrites SQL grammar. Where dialects diverge, it exposes distinct, dialect-faithful APIs:
Sequence("users_id_seq").Nextval // Oracle: users_id_seq.NEXTVAL
Nextval("users_id_seq") // PostgreSQL: NEXTVAL('users_id_seq')
NextValueFor("users_id_seq") // SQL Server: NEXT VALUE FOR users_id_seq
So cross-database portability is a deliberate non-goal: target Oracle and you write Oracle SQL; target PostgreSQL and you write PostgreSQL SQL. You trade portability for fidelity — the full power of your database's SQL, never flattened to a lowest common denominator.
Faithful emission is the foundation of a broader mission: SqlArtisan is a deterministic guard rail for SQL written alongside AI, where each verification layer rests on the one below. See Why SqlArtisan? for the full stack.
Documentation
The same type-safe C# emits idiomatic SQL for MySQL, Oracle, PostgreSQL, SQLite, and SQL Server — each example shows the C# you write and the exact SQL it produces. The full reference lives in docs/; signatures and emitted SQL are also visible inline via IntelliSense.
Using an AI coding assistant? Point it at
llms.txtfor an LLM-friendly index of this documentation, orllms-full.txtfor the full-text dump when your tool ingests one file instead of following links. Tools integrated with Context7 can resolve "SqlArtisan" by name instead. See the AI coding assistants guide for the setup that makes generated queries verifiable.
Versioning & Support
- Versioning, breaking changes, deprecation, support window: see the versioning & support policy.
- Security vulnerabilities: report privately per SECURITY.md — never in a public issue.
Contributing
Feedback, bug reports, and ideas are welcome.
- Bugs or feature requests: open an issue.
- Questions or ideas: start a discussion.
Changelog
Please see the CHANGELOG.md file for all notable changes.
License
This project is licensed under the MIT License. See the LICENSE file for the full license text.
| Product | Versions 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. |
-
net8.0
- Oracle.ManagedDataAccess.Core (>= 23.8.0)
- SqlArtisan (>= 0.9.0-beta.1)
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 |
|---|---|---|
| 0.9.0-beta.1 | 77 | 8/18/2026 |
| 0.8.0-beta.1 | 70 | 8/2/2026 |
| 0.7.0-beta.1 | 68 | 7/24/2026 |