QueryLens — SQL Validation for .NET
QueryLens is an open-source .NET library I built and published on NuGet. It validates raw SQL during unit tests so broken queries fail the build instead of failing in production — catching missing columns, typos, and dialect mismatches the C# compiler never sees. Works alongside Dapper and ADO.NET, with no refactor required. NuGet: dotnet add package QueryLens
Let's be honest about something.
If you've ever shipped a .NET application that uses hand-written SQL — through Dapper, ADO.NET, repositories, or a query registry — you know the moment. The deploy goes out. Everything looks green. And then, somewhere between the warehouse module and the reporting endpoint, a query explodes in production because someone renamed a column, mistyped STATSU instead of STATUS, or used a function that exists in PostgreSQL but not in SQL Server.
Your unit tests didn't catch it. Your build didn't catch it. Your code review didn't catch it. Your customer did.
This is the exact pain that pushed me to build my first NuGet package: QueryLens.
The Problem: SQL Lives in a Blind Spot
Modern .NET tooling is incredibly strict about C#. The compiler screams at you for an unused variable. Roslyn analyzers warn you about nullability. Your IDE underlines mistakes in real time.
But raw SQL? It's just a string. A magic, opaque, untyped string that the compiler completely ignores.
That means SQL bugs typically surface in the worst possible places:
- 🔴 During integration tests (slow feedback)
- 🔴 In staging (delayed releases)
- 🔴 In production (incidents, rollbacks, angry users)
- 🔴 In a customer's database (the worst kind of surprise)
Teams that depend on hand-written SQL detect mistakes too late. A missing column, invalid syntax, an unsupported database-specific construct, or a stale stored procedure name usually appears only when the application actually tries to execute it.
We have linters for everything except the most database-critical part of our codebase. That always felt wrong to me.
Especially painful in CQRS architectures
If your team uses CQRS (Command Query Responsibility Segregation), this problem is even more acute. It's extremely common — almost a default pattern — to use Dapper with hand-written SQL on the read side for performance and flexibility, while EF Core or another ORM handles the write side.
That means in a typical CQRS .NET project, you can easily have dozens or hundreds of raw SQL queries powering your queries/read models — all of them invisible to the compiler, all of them one renamed column away from breaking. Every team running this architecture can benefit from validating those queries before runtime.
The Solution: Validate SQL Where You Already Define It
QueryLens is a .NET library that validates raw SQL during tests, so broken queries fail before the code reaches production.
It's not an ORM. It's not a replacement for Dapper or ADO.NET. It doesn't execute your queries. It doesn't try to be smart about your data. It does one thing well: it tells you, in your test phase, whether your SQL is going to blow up.
Install
dotnet add package QueryLens
Validate a query in one line
var query = new SqlQuery(
"SELECT customer_id, status FROM dbo.orders WHERE order_id = @Id");
Assert.True(query.IsValid, query.ErrorSummary);
That's it. If the SQL is malformed or references unknown things, your test fails. Right there. In milliseconds. No database connection required.
How It Works: Three Levels of Validation
QueryLens is designed to be adopted incrementally. You don't have to commit to the deepest level on day one.
Level 1 — Generic structural validation
Catches syntax errors and malformed statements. Works out of the box, no configuration.
Level 2 — Dialect-aware validation
Tell QueryLens you're targeting SQL Server, PostgreSQL, MySQL, etc., and it validates against that dialect's function catalog and rules. No more "works on my Postgres, breaks on their SQL Server."
Level 3 — Schema-backed validation
Load a snapshot of your real database schema, and QueryLens validates that every table, column, and stored procedure your SQL references actually exists — and that you're not violating write restrictions.
This is the level that catches the dreaded "someone renamed a column three sprints ago and we just noticed" bug.
Two Ways to Declare SQL
You can validate inline:
var query = new SqlQuery("SELECT customer_id FROM dbo.orders WHERE id = @Id");
Assert.True(query.IsValid);
Or you can centralize your queries as [SqlQuery]-attributed static strings and validate the whole assembly at once:
public static class OrderQueries
{
[SqlQuery]
public static string GetById =>
"SELECT customer_id, status FROM dbo.orders WHERE order_id = @Id";
[SqlQuery]
public static string ListByCustomer =>
"SELECT * FROM dbo.orders WHERE customer_id = @CustomerId";
}
// In your test project:
[Fact]
public void All_queries_are_valid()
{
var result = QueryLens.ValidateAssembly(typeof(OrderQueries).Assembly);
Assert.True(result.IsValid, result.ErrorSummary);
}
One test. Every query in your codebase validated. Beautiful.
This pattern fits CQRS read-side query classes perfectly — point QueryLens at your Queries assembly and validate every Dapper query you have in a single test run.
Built to Live with Dapper
SqlQuery converts implicitly to string, so it drops directly into your existing Dapper calls — zero refactor required:
var sql = new SqlQuery(
"SELECT customer_id, status FROM dbo.orders WHERE order_id = @Id");
var order = await connection.QuerySingleOrDefaultAsync<OrderDto>(sql, new { Id = 42 });
Your production code stays the same. Your tests get a superpower.
The Benefits
- ⚡ Fast feedback — bugs caught in milliseconds, in your test suite
- 🛡️ Production safety — SQL errors fail the build, not the customer
- 🧩 Zero lock-in — works alongside Dapper, ADO.NET, or anything else
- 📈 Incremental adoption — start with one query, scale to the whole assembly
- 🗂️ Multi-dialect aware — SQL Server, PostgreSQL, MySQL, and more
- 🧪 No DB connection needed for the basic levels — your tests stay pure and fast
- 🏛️ Perfect for CQRS read sides — validate every Dapper query in your
Queriesproject at once
What QueryLens Is Not
I want to be honest about the boundaries:
- It does not execute your SQL.
- It does not replace your integration tests.
- It does not prove your runtime data is correct.
It's a safety net at the syntax, dialect, and schema layer — the cheapest, fastest layer of defense, sitting right where it should: in your test phase.
Why I Built It
I built QueryLens because I got tired of finding SQL bugs the hard way. I wanted something that would let me write hand-tuned SQL with the same confidence I have when I write C#. Something that respected the way real .NET teams actually work — with Dapper, with repositories, with raw queries, with CQRS — instead of forcing them into a heavy ORM.
This is my first NuGet package, and I'm shipping it because I think other developers feel the same pain I did.
📚 Docs and quick start: https://querylens.rafaelmelo.com/
📦 Install: dotnet add package QueryLens
If it saves you one production incident, it will have done its job. 🚀
Comments
Sign in to leave a comment.
No comments yet. Be the first to share your thoughts!