Table of Contents

How the engine works

A high-level tour of what sits between a fluent From<T>()...Select(...) call and the rows you get back: the query command, the pipeline that turns it into SQL, the plan cache, execution and row materialization. For step-by-step usage see the Guide; for the caching rules see Plan cache and reuse.

Prerequisites: Quickstart · Entities and metadata.

The pipeline at a glance

Every query follows the same shape:

  • A builder factory on the context creates the fluent builder: ctx.From<TEntity>() returns an EntityBuilder<TEntity>; the fluent operators (Where, Join, Select, ...) accumulate into a QueryCommand<TResult>. From starts a query — the other statement shapes have their own factories (see builder entry points).
  • A terminal (ToListAsync, FirstAsync, AnyAsync, ...) drives preparation: the command is turned into a structural QueryPlan key and looked up in the plan cache.
  • On a cache miss the query is prepared, translated to SQL text plus parameters, and paired with a compiled row mapper and a DbCommand; the plan is stored back in the cache.
  • The command is executed against the database, and the DbDataReader is turned into result rows by the row mapper or streamed straight through.
Piece Responsibility
IDataContext / provider DataContext Connection, metadata, dialect, command creation, and the builder factories — From* for queries, Create*Builder for DML, batch and CTAS.
EntityBuilder<TEntity> The query builder: fluent LINQ surface (Where, Join, Select, ...), created by a From* factory.
QueryCommand<TResult> The built query — source, projection, filters, joins, sorting, hints — and its terminals.
QueryPreparer Turns the expression tree into prepared clause pieces and plan hashes (via expression visitors).
QueryPlan / QueryPlanStore The structural plan key and the per-thread implicit plan cache.
SqlBuilder + ISqlDialect Renders the dialect-specific SQL text and the parameter list.
RowMapperFactory / RowMaterializer Compiles the row mapper (IDataRecord → entity / DTO).
DbCommand (via DataContext.CreateCommand) The provider command carrying the SQL text and the bound parameters.
QueryExecutor Runs the command and owns the ADO.NET reader.
DbDataReader The ADO.NET reader over the result set.
Terminals Every output shape: buffered collections, single rows, aggregates and scalars, streamed rows, JSON/CSV, and raw readers/streams.

Builder entry points

Every statement starts at a factory on IDataContext that returns the builder for that statement shape. From is the query factory, not the only one:

Entry point Returns Builds
ctx.From<T>(), ctx.From<T>(o => ...) EntityBuilder<T> SELECT over a mapped entity, with per-source options
ctx.From("table") EntityBuilder<TableAlias> SELECT over a raw table or CTE name
ctx.FromSql(sql, parameters) EntityBuilder<TableAlias> SELECT over a derived-table SQL
ctx.From(() => Db.MyTvf(...)) EntityBuilder<T> SELECT over a table-valued function
ctx.From(query) / ctx.From(cte) / ctx.From(tempTable) / ctx.From(builder) EntityBuilder<TResult> (EntityBuilder<TableAlias> for a temporary table) SELECT over a subquery, a CTE, a lazy temporary table, or another builder
ctx.CreateInsertBuilder<T>(), CreateBulkInsertBuilder<T>(), CreateUpdateBuilder<T>(), CreateDeleteBuilder<T>(), CreateMergeBuilder<T>(), CreateTruncateBuilder<T>() InsertBuilder<T>, BulkInsertBuilder<T>, UpdateBuilder<T>, DeleteBuilder<T>, MergeBuilder<T>, TruncateBuilder<T> DML statements
ctx.CreateBatchBuilder() BatchBuilder several statements in one round trip
builder.ToTable(name) / .ToTempTable(name) / .AsTempTable() CTAS command / TempTableSource<T> materialising a query into a table

CreateQueryBuilder<T>() is an explicit alias of From<T>() and returns the same builder. The DML, batch and CTAS surfaces are shown in the Guide: INSERT, UPDATE, DELETE, MERGE, bulk insert, SQL batch, CREATE TABLE AS. The rest of this section follows the query (From) path.

Command execution path

The diagram traces the full path — from the fluent builder through the plan cache and SQL generation to execution and the row mapper:

Command execution path: EntityBuilder → QueryCommand → QueryPlan → plan-cache lookup → QueryExecutor → DbDataReader → RowMapperFactory → buffered/streamed/scalar/serializing/raw terminals; on a cache miss QueryPreparer → SqlBuilder generate the SQL, and QueryPlanStore backs the lookup.

Explicit Prepare()

The second diagram shows the explicit reuse path, where the query is compiled once into an IPreparedQueryCommand<TResult> and executed any number of times — the plan cache is not involved:

Explicit Prepare: EntityBuilder → QueryCommand.Prepare() → GetPreparedQueryCommand → QueryPreparer → SqlBuilder, which forks into RowMapperFactory and CreateCommand, both merged into IPreparedQueryCommand; the compiled command is then executed N times through prepared.ToList(db)/First(db) → QueryExecutor → DbDataReader → result rows, while QueryPlanStore is bypassed.

In this section

See also