Query sources
Query sources at a glance
A query begins with a source, and From has one overload per kind of source. Every overload returns an EntityBuilder<TEntity>, so the full operator set (Where/Join/GroupBy/Having/OrderBy/Limit/Select) applies no matter where the rows come from:
| Source | Entry point |
|---|---|
| Mapped entity (its table from metadata) | From<TEntity>() |
Raw table or CTE name (via TableAlias accessors) |
From(string) |
| Subquery / already-composed query | From(QueryCommand<TResult>), From(EntityBuilder<TResult>) |
| Raw SQL fragment | FromSql — Raw SQL |
| Typed CTE | From(Cte<TResult>), From(CteReference<TResult>), declared with With / WithRecursive — CTEs |
| Lazy temporary table | From(TempTableSource<TResult>) — AsTempTable |
| Table-valued function (runtime factory) | From(() => ...) — Table-valued functions |
Every source also accepts a FromOptions callback (FromOptions) for per-source modifiers such as TABLESAMPLE/SAMPLE and table/index hints (see Table sampling below). The additive CreateQueryBuilder* forwarders mirror these overloads one-to-one (see the API reference). Overrides that change the rendered source of an already-chosen entity are described in Per-query source overrides.
Subquery as source
From(query) also wraps a filtered query, which is the SQL FROM equivalent of a subquery:
var inner = dataContext.From<SimpleEntity>()
.Where(it => it.Id > 8)
.Select(it => new { it.Id });
var rows = await dataContext.From(inner)
.Select(it => new { it.Id })
.ToListAsync();
-- SQLite
select id
from (select id
from simple_entity where (id > 8))
Output:
| Id |
|---|
| 9 |
| 10 |
Constructing a derived source and materializing it are two different steps. From(inner) composes the
derived table; reading its rows requires an outer Select, because the enclosing query can only
address the output columns the derived SELECT declares. A bare From(inner).ToList() is accepted only
when the inner projection is already a set of resolved column references (a mapped column, a renamed
projected member, a computed scalar read through its output name). A projection whose members are
row-member expressions — an anonymous new { it.Id } or a DTO new Dto { Id = it.Id } — cannot be
reused that way and is refused during preparation with a QueryPreparationException; the rule is the
expression shape of the inner projection, not the CLR type of its result. Add the outer selection
(From(inner).Select(it => it.Id), as above) to read such a source.
This rewrite works with member-init DTO and positional-record projections; the tested
constructor-only class projection has a known pre-existing failure
(Cannot find inner column Id), so use one of those supported forms instead. See
Limitations. See
Subqueries for the construction/materialization split.
A derived source may also project a computed scalar instead of a named member. The enclosing query addresses that value through the output column the derived select list exposes, so it can be ordered, filtered and projected like any other column:
var inner = dataContext.From<SimpleEntity>()
.Where(it => it.Id <= 2)
.Select(it => it.Id * 2);
var rows = dataContext.From(inner)
.OrderBy(x => x)
.Select(x => x * 3)
.ToList();
-- SQLite
select (c0 * 3)
from (select (id * 2) as 'c0'
from simple_entity
where (id <= 2)) as 't1'
order by t1.c0
Output: 6, 12.
The generated output name in the sample (
c0here) is how the rendered SQL exposes the computed column; it is not a public naming contract. Read the value through the builder (as above) instead of hard-coding the rendered spelling, which is provider-specific and may change.
Table function as source
dataContext.From(() => ...) uses a runtime table-valued-function factory as the FROM source: the
lambda carries the factory call and its arguments (see
Table-valued functions). The built-in helpers cover the common
set-returning functions; SqlFunctions.Postgres.unnest flattens a PostgreSQL array into one row per
element:
var elements = dataContext
.From(() => SqlFunctions.Postgres.unnest(SqlFunctions.Parameter<long[]>(0)))
.Select(r => r.Value)
.ToList(new long[] { 1, 2, 3 });
select unnest as "Value"
from unnest(@norm_p0) as "t1"
If the fully qualified SqlFunctions.* names feel too verbose, import the surface with
using static NextORM.Core.SqlFunctions; and drop the class prefix:
using static NextORM.Core.SqlFunctions;
var elements = dataContext
.From(() => Postgres.unnest(Parameter<long[]>(0)))
.Select(r => r.Value)
.ToList(new long[] { 1, 2, 3 });
SqlFunctions.Postgres.generate_series(start, stop) generates a numeric series the same way. The
built-ins are gated by the provider (SupportsTableFunction),
and a custom function is declared as a TvfEntityBuilder<T> factory; see
Table-valued functions.
Table sampling (TABLESAMPLE)
FromOptions.TableSample adds a TABLESAMPLE modifier to the
primary table, so the database reads only a percentage of its rows instead of scanning the whole table.
Sampling is a per-query source option, so it is configured in the From call. The percentage must be
in (0, 100]; the sampling method defaults to
TableSampleMethod.System and an optional seed makes the
sample repeatable:
var rows = await dataContext.From<SimpleEntity>(o => o.TableSample(10, TableSampleMethod.System, seed: 42))
.Select(x => x.Id)
.ToListAsync();
-- PostgreSQL
select id
from simple_entity tablesample system (10) repeatable (42)
-- SQL Server
select id
from simple_entity tablesample (10 percent) repeatable (42)
PostgreSQL supports both System and Bernoulli;
SQL Server supports only System. Every other provider throws NotSupportedException when the SQL is
built (TableSample and
ITableSampleMethods.Render). The modifier applies to the query's
primary table only.
Per-query source overrides
By default a query reads the table mapped with EntityMetadataBuilder<T>.Table(...) (or the name passed
to From("table")). The With* methods on EntityBuilder<TEntity>
change the rendered source for this query only; the metadata and every other query are untouched:
public EntityBuilder<TEntity> WithTableName(string name);
public EntityBuilder<TEntity> WithSchema(string schema);
public EntityBuilder<TEntity> WithDatabase(string database);
public EntityBuilder<TEntity> WithServer(string server);
public EntityBuilder<TEntity> WithTableExpression(string sql);
var rows = await dataContext.From<IOrder>()
.WithSchema("sales")
.Select(x => x.Id)
.ToListAsync();
-- SQL Server / PostgreSQL
select id
from sales.orders
The schema/database/server qualifiers are provider-specific; the levels a provider cannot express are
rejected with NotSupportedException when the SQL is built:
| Provider | WithSchema |
WithDatabase |
WithServer |
|---|---|---|---|
| PostgreSQL | schema.table |
rejected | rejected |
| SQL Server | schema.table |
database.schema.table |
server.database.schema.table |
| MySQL / MariaDB | db.table (schema = database) |
db.table |
rejected |
| SQLite | db.table (attached database) |
db.table |
rejected |
| ClickHouse | db.table |
db.table |
rejected |
| In-memory | rejected | rejected | rejected |
On MySQL, MariaDB, ClickHouse and SQLite the schema and the database name the same single qualifier, so
only one of the two may be set. The parts are quoted with the provider's delimiter when identifier
quoting is enabled (see WithQuotedIdentifiers),
each part separately: [srv].[db].[sales].[orders], `db`.`orders`, "sales"."orders".
WithTableExpression replaces the table access with a raw SQL
expression rendered as a derived source ((sql) AS alias) and cannot be combined with the name
qualifiers. The fragment is emitted verbatim, so pass only trusted SQL (the caller owns its validity and
injection safety, as with WithSql):
var rows = await dataContext.From<IOrder>()
.WithTableExpression("select id from orders_2025 union all select id from orders_2026")
.Select(x => x.Id)
.ToListAsync();
-- PostgreSQL
select id
from (select id from orders_2025 union all select id from orders_2026) as "t1"
The override is part of the plan-cache key, so two queries that differ only in the override never share a cached plan; without an override the generated SQL is exactly as before. The in-memory provider has no SQL source to rewrite and rejects every override.