-
Notifications
You must be signed in to change notification settings - Fork 0
Error Reference
Every exception QueryForge raises, what causes it, and what to do. Grouped by type.
"Query.Object must be specified before executing a query. Use ForObject(...) on the builder to set it."
Cause. A DapperQuery reached the provider with Object still null.
Fix. Set the target:
var dq = DapperQueryBuilder.FromBase(clientQuery)
.ForObject("Users", "dbo", DapperObjectType.Table) // ← this
.Build();Most often seen when a client body was bound directly as DapperQuery instead of Query. Bind
Query and upgrade with FromBase — see Security.
Cause. Object exists but Name is empty or whitespace.
Fix. Supply a real name. If the name comes from configuration, check it is actually being read.
Cause. A grouped compile method was called when no grouping column survives the whitelist.
Fix. In normal use you will not see this — the executor checks first and falls back to a flat
result. It appears when calling SqlQueryCompiler directly. Check that your ColumnWhitelist
contains the grouping column, and that its spelling matches the column the database reports.
"Stored procedures are executed directly rather than composed into a SELECT. This is handled by the execution path, not the compiler."
Cause. A compile method was called with DapperObjectType.SP.
Fix. Do not compile procedures. Execute them through IDapperQueryService or
QueryForgeAsync, which route them down a separate path. See
Dapper Provider.
"Cannot manage a connection because DapperQueryForgeOptions.ConnectionFactory was not configured in AddQueryForgeDapper(). Either configure it, or use the overload that takes an IDbConnection."
Cause. The connectionless QueryAsync(query) overload was called without a factory.
Fix. Either configure one:
builder.Services.AddQueryForgeDapper(options =>
{
options.ConnectionFactory = sp =>
new SqlConnection(sp.GetRequiredService<IConfiguration>().GetConnectionString("Default"));
});or pass a connection:
await svc.QueryAsync<User>(connection, query);Cause. AddQueryForgeDialect could not find a registry in the service collection.
Fix. Order the calls:
builder.Services.AddQueryForgeDapper();
builder.Services.AddQueryForgeDialect(new MyDialect());"QueryForge cannot infer a database engine from connection type 'X'. Supported connections are SqlConnection, NpgsqlConnection, MySqlConnection, OracleConnection and SqliteConnection."
Cause. The connection's type name is not one QueryForge recognises. Common triggers: a
profiling wrapper such as MiniProfiler's ProfiledDbConnection, a pooling decorator, a mock, or a
driver whose connection class is named differently.
Fix. Unwrap the connection before handing it over, or register a dialect and give the type a recognised name. See Extending QueryForge.
Note the match is on the simple type name only — namespace and assembly are ignored — so a type
you name SqlConnection resolves as SQL Server.
Cause. A connection resolved to a DapperDatabaseProvider with no dialect registered. Only
possible if you built a registry yourself or replaced the default.
Fix. Call AddQueryForgeDapper(), which registers all five.
Cause. DapperObjectType.TVF against an engine without the concept.
Fix. Use a view, or a stored procedure on MySQL. On SQLite, a view.
The same thing, raised by the compiler rather than the dialect, when
SupportsTableValuedFunctions is false.
Cause. DapperObjectType.SP against SQLite.
Fix. SQLite has no procedures at all. Use a view or query the table directly.
"X cannot return a result set from a stored procedure, so DapperObjectType.SP is not available for it."
Cause. DapperObjectType.SP against an engine whose SupportsStoredProcedures is false — Oracle
or SQLite.
Fix. See the Oracle-specific message below.
"Oracle procedures return result sets through REF CURSOR output parameters. Wrap the logic in a pipelined function and query it with DapperObjectType.TVF instead."
Cause. DapperObjectType.SP against Oracle.
Fix. Exactly what the message says:
CREATE OR REPLACE FUNCTION get_user_report(p_include_deleted NUMBER)
RETURN user_report_tab PIPELINED AS ....ForObject("GET_USER_REPORT", "", DapperObjectType.TVF,
new Dictionary<string, object?> { ["p_include_deleted"] = 0 })Cause. Validate(..., QueryValidationMode.ThrowException) found at least one violation. This is
working as designed, not a bug.
Handling. InvalidProperties lists every violation — all of them, not just the first:
catch (QueryValidationException ex)
{
return Results.ValidationProblem(
ex.InvalidProperties.Distinct().ToDictionary(p => p, p => new[] { "Denied by security policy" }));
}Entry formats:
| Violation | Entry |
|---|---|
| select / sort / group-by / condition column | the column name |
| page size too large | "Paging.Size > 100" |
| page size too small | "Paging.Size < 5" |
| missing object name | "Object.Name is required." |
| disallowed schema | "Object.Schema 'x' is not allowed." |
| disallowed object name | "Object.Name 'x' is not allowed." |
If the existence of a column is itself sensitive, do not echo
InvalidPropertiesto the caller.
Cause. QuoteIdentifier received a blank name. The compiler never passes one, so this indicates
either a custom dialect being called directly, or a ColumnWhitelist built with a blank entry.
Fix. Filter blanks out of any whitelist you construct yourself.
Standard null guards on public entry points. Pass the argument.
These are not QueryForge exceptions, but they show up in QueryForge call stacks.
Oracle folds unquoted identifiers to upper case and QueryForge quotes what it is given. A table
created as CREATE TABLE Users is stored as USERS.
.ForObject("USERS") // ✅ matches what Oracle stored
.ForObject("Users") // ❌ looks for "Users", which does not existAn Oracle bind variable named after a reserved word. QueryForge's own parameters are p0, p1, … so
this never comes from the library — check your own code for parameters named after columns such as
Number, Level, Size or Date.
A value was compared against a column of a different type without coercion. QueryForge coerces automatically using the type discovered from the result set, so this indicates the type was not discovered — for example a driver that could not describe the column, or a stale schema cache.
Fix. Restart the process to clear the cache if the table changed shape. If it persists, check whether the column is a computed or aliased expression whose type the driver cannot report.
The schema defaulted to dbo and the object lives elsewhere. Name the schema:
.ForObject("Users", "reporting")Parameters were not bound. Almost always a custom execution path that bypassed
SchemaCache.ToDapperParameters. Use IDapperQueryService or QueryForgeAsync.
The commonest support questions are not exceptions at all — QueryForge prefers dropping an unusable input to failing a request.
Checklist, in order:
- Does the column exist on the target? Names are matched against the target's real columns and dropped if absent. Check spelling and — on Oracle — case of the object, though column names themselves are matched case-insensitively.
-
Is the value missing? Every operator except
EqualsandNotEqualsis dropped whenValueis null.Betweenis also dropped withoutValueTo. -
Is the operator a defined enum value? A JSON
"operator": 47is dropped. -
Did validation strip it? A denied column in
SilentStripmode disappears silently. Run the same query inThrowExceptionmode to find out. - Is it a text operator on a non-string property? EF Core drops those.
Same first and fourth checks. Also: a sort naming a column that is not in SelectColumns still works
on Dapper and EF Core — the column is fetched for ordering even when not projected — so that is not
the cause.
Every grouping column was dropped, so the provider fell back to flat. Check the column names against
the target, and check rules.GroupBy(...) if you allow-list them.
Working as designed. Page size bounds the number of groups, and every row under those groups is returned so counts can be truthful. See Grouping and Hierarchies.
The schema cache is per-process and has no invalidation API. Restart the application.
The sort is not total, so the database is free to order ties differently between calls. Add a unique tie-breaker:
.Sort(new SortDescriptor("Score", SortOrder.Descending),
new SortDescriptor("UserId"))Almost always string case. In-memory text comparison is OrdinalIgnoreCase; the database follows the
column's collation. See Cross-Provider Parity.
Should not happen — a selection naming nothing real is ignored, and a model that cannot be shaped is returned whole. If you see it, the model has a parameterless constructor and settable properties but the names do not match the properties. Check spelling against the property names, not the database column names.
Include, at minimum:
- the
Queryas JSON, - the provider and engine,
- the SQL where relevant —
ToQueryString()for EF Core, orSqlQueryCompilerdirectly for Dapper (see Dapper: Generated SQL), - and what you expected versus what you got.
QueryForge · part of the PepperX Ecosystem · MIT licensed · packages 2.0.0, .NET 10
Foundations
- Getting Started
- Architecture
- Query Model
- Query Semantics
- Results and Metadata
- JSON Contract
- Fluent Builders
Behaviour
Providers
- Dapper Provider
- Dapper: Generated SQL
- Dapper: Dialects
- EF Core Provider
- EF Core: Joins & Includes
- In-Memory Provider
Practice
Reference