QueryLens 1.0.0-preview.1
See the version list below for details.
dotnet add package QueryLens --version 1.0.0-preview.1
NuGet\Install-Package QueryLens -Version 1.0.0-preview.1
<PackageReference Include="QueryLens" Version="1.0.0-preview.1" />
<PackageVersion Include="QueryLens" Version="1.0.0-preview.1" />
<PackageReference Include="QueryLens" />
paket add QueryLens --version 1.0.0-preview.1
#r "nuget: QueryLens, 1.0.0-preview.1"
#:package QueryLens@1.0.0-preview.1
#addin nuget:?package=QueryLens&version=1.0.0-preview.1&prerelease
#tool nuget:?package=QueryLens&version=1.0.0-preview.1&prerelease
QueryLens
Validate your raw SQL at test time — not runtime.
QueryLens catches column typos, missing parameters, invalid functions, and dialect mistakes in your SQL before they hit production. Built for Dapper, ADO.NET, and any .NET code with hand-written SQL.
Why?
SQL queries in .NET are opaque strings. The compiler can't see them. A typo in a column name, a function that doesn't exist in your SQL Server version, a table that was renamed last week — you won't know until runtime. QueryLens fixes that.
Quick Start
dotnet add package QueryLens
1. Check for syntax errors
var query = new SqlQuery("SELECT * FROM customers WHERE created_date >= GETDATE()");
var result = SqlValidator.UseSqlServer().Validate(query);
Assert.True(result.IsValid); // passes — valid T-SQL
That's it. One query, one validator, one assert.
2. Check for version-specific function errors
STRING_AGG was introduced in SQL Server 2017. If your production server runs 2014:
var query = new SqlQuery("SELECT STRING_AGG(name, ',') FROM departments");
var result = SqlValidator.UseSqlServer()
.ServerVersion("2014")
.AllowCustomFunctions(false)
.Validate(query);
Assert.False(result.IsValid);
// QL0071: Function 'STRING_AGG' is not available in SQL Server version 2014.
3. Check for tables and columns that don't exist
var query = new SqlQuery("SELECT id, full_name, email FROM customers");
var result = SqlValidator.UseSqlServer()
.WithSchema(s => s
.Table("customers")
.Column("id", SqlType.Int)
.Column("name", SqlType.NVarChar)
.Column("email", SqlType.NVarChar))
.Validate(query);
Assert.False(result.IsValid);
// QL0011: Column 'full_name' does not exist in table 'customers'.
4. Check for missing parameters
var query = new SqlQuery("SELECT * FROM orders WHERE status = @Status AND id = @Id");
var result = SqlValidator.UseSqlServer()
.Build()
.Validate(query, new { Status = "active" }); // @Id is missing
Assert.False(result.IsValid);
// QL0030: SQL parameter '@Id' has no matching value in the provided parameters.
5. Catch identity column violations
var insert = new SqlInsert("INSERT INTO customers (id, name) VALUES (@Id, @Name)");
var result = SqlValidator.UseSqlServer()
.WithSchema(s => s
.Table("customers")
.Column("id", SqlType.Int, isIdentity: true)
.Column("name", SqlType.NVarChar))
.Validate(insert);
Assert.False(result.IsValid);
// QL0060: Column 'id' is an identity column and cannot be explicitly set.
6. Warn on dangerous deletes
var delete = new SqlDelete("DELETE FROM customers");
var result = SqlValidator.UseSqlServer().Validate(delete);
Assert.Contains(result.Warnings, w => w.Code == "QL0040");
// QL0040: DELETE statement without WHERE clause.
Configure Once, Validate Everywhere
Set up your validator once at startup. Then every query validates itself:
// Call once — in an xUnit fixture, [ModuleInitializer], or test base class
QueryLensConfig.Configure(b =>
{
b.UseSqlServer();
b.WithSchema(s => s
.Table("customers")
.Column("id", SqlType.Int, isIdentity: true)
.Column("name", SqlType.NVarChar)
.Column("email", SqlType.NVarChar));
});
// Now every query validates itself
var query = new SqlQuery("SELECT id, name FROM customers");
Assert.True(query.IsValid);
var bad = new SqlQuery("SELECT id, phone FROM customers");
Assert.False(bad.IsValid);
// bad.ErrorSummary → "Column 'phone' does not exist in table 'customers'."
Works With Every Major SQL Dialect
SqlValidator.UseSqlServer() // T-SQL: TOP, ISNULL, [brackets]
SqlValidator.UsePostgreSql() // PG: LIMIT/OFFSET, COALESCE, "double quotes"
SqlValidator.UseMySql() // MySQL: LIMIT, IFNULL, `backticks`
SqlValidator.UseSqlite() // SQLite: LIMIT, lightweight functions
SqlValidator.UseOracle() // Oracle: ROWNUM, NVL, DUAL
Each dialect ships with a complete JSON catalog of built-in functions (with version tags), commands, reserved words, and deprecated keywords.
Type System
QueryLens provides typed SQL wrappers. They implicitly convert to string, so they drop in wherever Dapper expects SQL:
// These just work with Dapper — no .Text or .ToString()
await connection.QueryAsync<Customer>(myQuery, new { Status = "active" });
await connection.ExecuteAsync(myInsert, new { Name = "Acme" });
| Type | For |
|---|---|
SqlQuery |
SELECT |
SqlInsert |
INSERT |
SqlUpdate |
UPDATE |
SqlDelete |
DELETE |
SqlMerge |
MERGE / UPSERT |
SqlStoredProc |
Stored procedures |
Validation Rules
Every rule is independently toggleable. Start simple, enable more as your team is ready:
| Rule | Default | What It Catches |
|---|---|---|
ValidateSyntax |
ON | Malformed SQL |
ValidateTableExistence |
ON | Table doesn't exist |
ValidateColumnExistence |
ON | Column doesn't exist |
ValidateColumnMapping |
ON | SELECT column doesn't map to C# model |
ValidateParameterConsistency |
ON | Missing or extra @Parameters |
ValidateInsertColumnValueMatch |
ON | INSERT column/value count mismatch |
ValidateWhereClause |
ON | UPDATE/DELETE without WHERE |
ValidateIdentityColumns |
ON | Writing to identity/computed columns |
ValidateBuiltInFunctions |
ON | Invalid or version-incompatible functions |
ValidateRequiredColumns |
OFF | Non-nullable columns missing from INSERT |
ValidateJoinStructure |
OFF | Invalid JOIN structure |
ValidateAliasConsistency |
OFF | Inconsistent alias usage |
ValidateReservedWords |
OFF | Reserved words as identifiers |
ValidateDeprecatedSyntax |
OFF | Deprecated syntax |
var validator = SqlValidator.UseSqlServer()
.Enable(Rules.ValidateRequiredColumns)
.Disable(Rules.ValidateBuiltInFunctions)
.Build();
Issue Codes
| Code | What It Means |
|---|---|
| QL0001 | SQL syntax error |
| QL0010 | Table doesn't exist |
| QL0011 | Column doesn't exist |
| QL0020 | SELECT column doesn't map to model property |
| QL0030 | Missing parameter value |
| QL0031 | Unused parameter |
| QL0040 | UPDATE/DELETE without WHERE |
| QL0050 | INSERT column/value count mismatch |
| QL0060 | Writing to identity column |
| QL0061 | Writing to computed column |
| QL0070 | Unrecognized function |
| QL0071 | Function not available in target version |
| QL0072 | Too few function arguments |
| QL0073 | Too many function arguments |
| QL0080 | Required column missing from INSERT |
| QL0090 | Table name is a reserved word |
| QL0091 | Column name is a reserved word |
Performance
Fast enough to validate your entire query catalog on every build:
| Operation | Time |
|---|---|
| Simple SELECT | ~3 μs |
| Complex query (JOINs, GROUP BY) | ~16 μs |
| 100 mixed statements | ~260 μs |
What QueryLens Is Not
- Not an ORM — it doesn't generate SQL or map results
- Not a query optimizer — it doesn't suggest indexes
- Not a migration tool — it doesn't apply schema changes
- Not a runtime validator — all validation is static, at test time
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 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. |
-
net9.0
- No dependencies.
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 |
|---|---|---|
| 1.1.3 | 1,033 | 4/12/2026 |
| 1.1.3-preview.40 | 72 | 5/3/2026 |
| 1.1.2 | 114 | 4/12/2026 |
| 1.1.1 | 120 | 4/12/2026 |
| 1.1.0 | 126 | 4/12/2026 |
| 1.0.0 | 145 | 4/11/2026 |
| 1.0.0-preview.10 | 84 | 4/7/2026 |
| 1.0.0-preview.9 | 71 | 4/5/2026 |
| 1.0.0-preview.8 | 85 | 4/5/2026 |
| 1.0.0-preview.7 | 66 | 4/5/2026 |
| 1.0.0-preview.6 | 89 | 4/5/2026 |
| 1.0.0-preview.4 | 77 | 4/4/2026 |
| 1.0.0-preview.3 | 94 | 4/4/2026 |
| 1.0.0-preview.1 | 82 | 4/2/2026 |