skip to content

In a data-access layer, what is a native statement, and what result shapes can it be mapped into?

level: juniorimportance: must knowfreq 72%

answer

  1. the layer sends your text, unchanged
  2. database names, not object-model names
  3. three targets: entity, transfer model, scalar
  4. entity mapping needs the identifier column
  5. no startup name checking, no portability

basics

~20 s

A native statement is SQL you write in the database's own language and hand to the layer unchanged, rather than an object-level query it translates. Its rows map back to entity objects, transfer models, or scalars.

solid answer

~40 s

Most data-access layers offer an object-level query language they translate into SQL. A **native statement** skips that translation: you write the SQL, the layer sends it as written and only maps the rows coming back. Because the text is opaque to the layer, it uses **database names** — tables and columns — not your object-model names. Three mapping targets are usual: an **entity type**, which normally needs the identifier column present and puts the row through the layer's normal load path; a **transfer model**, a read-only type filled by column label or constructor position; and **scalars or untyped tuples** for counts and aggregates. You reach for one when the object-level language cannot express what the engine can, accepting that you gave up the checking and portability the translated path provided.

go deeper

for a junior

Remember the definition and the three targets: SQL you write yourself, mapped back to an entity, a transfer model, or a scalar. Say that the text uses table and column names, not class and property names.

for a middle

Explain what the layer stops doing for you — no name validation, no dialect translation, no automatically woven filters — and what an entity mapping needs from the result: the identifier and the full declared column set.

for a senior

Show the operational discipline: aliased labels as a contract, statements kept in one place, and a rename checklist that sweeps them, because nothing in the build will find a stale column name for you.

for a principal

Frame the choice as buying capability with coupling. Every native statement pins you to a schema and a dialect; decide deliberately how many the codebase may hold and where they live.

## What "native" means A data-access layer normally sits between you and SQL. You express a query against the object model — types, properties, associations — and the layer compiles that into the SQL its configured dialect wants. A **native statement** is the escape hatch through the same layer: the text you supply *is* the statement the database receives, expressed in the database's own SQL, including anything the engine supports that the object-level language never modelled. Two consequences follow immediately, and almost every surprise around native statements traces back to one of them: - **The layer does not understand the text.** Beyond finding parameter placeholders, it generally does not know which tables the statement touches, which columns it returns, or even whether it reads or writes. It cannot check the statement at startup the way it can check an object-level query. - **The names in the text are database names.** Table and column names, not mapped type and property names. Renaming a property in the mapping metadata silently leaves the native statement pointing at the old column — or at a column that no longer exists, which you learn at runtime. ## The three mapping targets Mapping is the half of the feature the layer still does for you: it turns a result row into something typed. | Target | What comes back | Inside the tracked set? | Typical use | |---|---|---|---| | **Entity type** | fully mapped instances of a mapped class | usually yes, when the identifier is selected | you intend to read then modify | | **Transfer model** | a purpose-built read type, one instance per row | no | reports, list screens, cross-aggregate reads | | **Scalar or tuple** | single values, or untyped arrays of column values | no | counts, existence probes, aggregates | ### Mapping to an entity type To rebuild a mapped object the layer needs enough of the row to populate it: the **identifier column** always, and in most layers every column the mapping declares as part of that type. Select a subset and you get either an error or a half-populated instance — which is worse, because a half-populated instance that the layer believes is complete can be written back with the missing columns cleared. When the statement joins several mapped types, the layer needs a mapping declaration that assigns result columns to types, which is also how you resolve the two-columns-named-`id` collision: alias explicitly in the SQL and declare the aliases. ### Mapping to a transfer model A transfer model is a plain type with no mapping metadata and no lifecycle. Layers fill it either by **column label matched to a property name** or by **select-list position matched to constructor parameters**. Either way the *labels or the order in the select list become part of the contract* — add a column in the middle of a positionally-bound select list and you have silently rewired the mapping. Alias every computed column so its label is stable. ### Mapping to scalars The cheapest shape: one value per row, or an untyped array of the selected values. Untyped tuples are convenient and fragile — the reader has to know that index 2 is the order date — so a named shape is worth the extra type as soon as more than one caller reads it. ## What the translated path gave you that this does not - **Name checking.** An object-level query can usually be validated against the mapping metadata at startup; a native statement is a string until it runs. - **Portability.** The dialect is now yours to own — paging syntax, string and date functions, locking clauses. - **Automatic conditions.** Filters the layer weaves into queries it generates — inheritance discriminator conditions, tenant or soft-delete filters where the layer supports them, association fetch plans — are not woven into text you wrote. If you need them, you write them. - **Refactoring safety.** A compiler or a startup check will not follow a column rename into a string. ## When a native statement earns its place 1. The object-level language genuinely cannot express the operation — a window function, a recursive query, an engine-specific set operation. 2. The generated SQL is measurably wrong for the plan you need and no fetch or hint knob reaches it. 3. The work is naturally set-based or scalar and building objects to do it would be waste. And the discipline that goes with it: bind every caller value as a parameter rather than pasting it into the text, alias every column you map by label, keep the statement in one place rather than scattered, and treat it as **schema-coupled code** — anything that renames a column has to visit it, because nothing else will.

  • Why is selecting only some of a mapped type's columns into an entity result risky?
    The layer may build an instance it believes is complete. Unselected columns sit at their default values, and if that instance is then tracked and flushed, dirty checking can write those defaults back over real data. Select the full column set for the type, or map to a transfer model instead.
  • A native statement joins two mapped types and both carry a column named `id`. What breaks and how do you fix it?
    Column labels collide, so the layer cannot tell which `id` belongs to which type — it maps one, duplicates it, or fails. Alias every column explicitly in the select list and declare the alias-to-type mapping the layer expects.
  • Why can a column rename break a native statement without breaking any object-level query?
    Object-level queries name properties, which the layer re-translates from the mapping metadata after the rename. A native statement names the column directly, in a string nothing validates until it executes. Renames must include a sweep of the statements.

saying these in an interview costs you the question

  • Thinks a native statement is still translated or rewritten by the layer
  • Uses object-model property names inside the SQL text
  • Selects a few columns into an entity type and expects a complete object
  • Assumes tenant or soft-delete conditions are added automatically
  • Pastes caller values into the statement text instead of binding them
  • Believes native results are always read-only regardless of mapping target