Chapter 03 · T-SQL Foundations, Data Types, NULL, Conversion, and Expressions

Design a Robust Domain Mapping Between Application Types and SQL Server Types

Design a durable application-to-SQL Server type contract covering parameter metadata, precision/scale/length, Unicode, timestamps, GUIDs, binary/JSON values, nullability, and driver behavior.

Intermediate105–135 minutesApplication mapping + parameter contract labMicrosoft.Data.SqlClient concepts + SQL Server 2025No hard-coded secrets · stable mandatory typesLast reviewed: August 2026

Learning outcomes

ServiceHub’s database schema is now strongly typed, but the application can still erase those guarantees at the boundary. A .NET string inferred as nvarchar is compared to a varchar indexed key, decimal precision is left to inference, timestamps lose offset policy, and optional values are sent as language null instead of database NULL. Production correctness therefore requires a domain mapping between application types, parameter metadata, SQL Server types, and serialization rules.

01

Map common application booleans, integers/decimals, strings, timestamps, GUIDs, binary values, JSON, and optional values to intentional SQL Server types.

02

Explain why parameter data type, length, precision and scale are part of the query contract—not cosmetic driver settings.

03

Use parameterized commands without hard-coded secrets and avoid string concatenation for values.

04

Design UTC/time-zone and Unicode policies that survive round trips across drivers.

05

Validate the database-side parameter contract through stored-procedure metadata and representative application binding.

Driver scope

The application example uses current Microsoft.Data.SqlClient concepts because it is Microsoft’s actively documented .NET provider. Other drivers have equivalent concerns but different APIs. Re-check the driver documentation/version used by your application rather than copying provider-specific code blindly.

1. A domain type is more than a language primitive

A business amount is not merely a C# decimal or Python Decimal; it has an allowed range, precision, scale, currency policy, and rounding rule. A customer code is not merely a string; it can be ASCII-only varchar(16), Unicode nvarchar(16), case-sensitive or case-insensitive depending on collation, and possibly fixed-format. A timestamp needs a UTC/offset/time-zone contract.

Domain value Typical application representation SQL Server choice Contract to document
Boolean bool/Boolean bit NULL allowed or not
Count / key 32/64-bit integer int/bigint range, identity/sequence ownership
Money/quantity decimal decimal(p,s) precision, scale, rounding
Human text Unicode string nvarchar(n) max length, collation
Bounded ASCII/code string varchar(n) when justified encoding/collation/length
Instant in UTC DateTime/Instant datetime2 UTC policy, precision
Value with offset DateTimeOffset datetimeoffset offset preservation vs named zone
GUID Guid/UUID uniqueidentifier generation authority/index implications
Bytes byte[] varbinary(n/max) maximum size/streaming
Optional nullable/Option nullable column/parameter missing vs empty/default

2. Parameter metadata can change semantics and plans

Microsoft’s ADO.NET documentation explains that providers can infer parameter types from application values. For SQL Server, a .NET String is commonly inferred as nvarchar. That may be correct for a Unicode column, but Chapter 3 Lesson 3 showed why an inferred Unicode parameter against an intentional varchar key can introduce a conversion.

Do not make “never infer anything” another superstition; instead, explicitly specify metadata where the domain contract matters. Bounded strings should have a deliberate size. Decimal parameters should have deliberate precision/scale where the provider exposes them. Temporal parameters should use the corresponding SQL Server type and precision policy.

sql · server-side equivalent: explicit parameter metadata with sp_executesql
USE ServiceHubLab;GODECLARE @sql nvarchar(max) = N'SELECT work_order_id, statusFROM ops.WorkOrderWHERE customer_code = @customer_code  AND opened_at >= @from_utc;';EXEC sys.sp_executesql    @sql,    N'@customer_code varchar(16), @from_utc datetime2(0)',    @customer_code = 'CUST-001',    @from_utc = '2026-08-01T00:00:00';GO

This does not replace application-side binding; it demonstrates that parameter definitions are part of the compiled statement contract. The driver should send corresponding metadata rather than asking the server to repair a mismatch repeatedly.

3. Parameterize values; keep secrets out of source

Parameterized commands separate query text from value data, improve validation/type handling, and prevent a user value from becoming executable SQL text. They are also the normal route to plan reuse. Identifiers such as table/column names cannot generally be parameterized the same way; dynamic identifier SQL requires a separate allowlist/quoting design.

csharp · C# · explicit SqlClient parameter types
using Microsoft.Data.SqlClient;using System.Data;string cs = Environment.GetEnvironmentVariable("SERVICEHUB_SQL")    ?? throw new InvalidOperationException("SERVICEHUB_SQL is not set");await using var cn = new SqlConnection(cs);await cn.OpenAsync();const string sql = @"SELECT work_order_id, statusFROM ops.WorkOrderWHERE customer_code = @customer_code  AND opened_at >= @from_utc;";await using var cmd = new SqlCommand(sql, cn);cmd.Parameters.Add("@customer_code", SqlDbType.VarChar, 16).Value = "CUST-001";cmd.Parameters.Add("@from_utc", SqlDbType.DateTime2).Value =    new DateTime(2026, 8, 1, 0, 0, 0, DateTimeKind.Utc);await using var reader = await cmd.ExecuteReaderAsync();

The example reads its connection string from an environment variable rather than embedding a password. A production deployment should use its platform’s secret/configuration mechanism and current TLS/certificate validation settings. The lesson does not require .NET to complete the SQL lab; it uses C# only to make the client metadata boundary concrete.

4. Null, empty, zero and default are four different states

Application languages have their own null/optional abstractions. SQL Server has NULL. In ADO.NET, sending a database null uses DBNull.Value; a C# null reference is not automatically interchangeable in every API. The schema decides whether the column allows NULL. The business model decides whether empty string, zero, false, or a generated default means something different.

sql · database contract makes optionality visible
USE ServiceHubLab;GOSELECT    s.name AS schema_name,    t.name AS table_name,    c.name AS column_name,    TYPE_NAME(c.user_type_id) AS type_name,    c.max_length, c.precision, c.scale, c.is_nullableFROM sys.columns AS cJOIN sys.tables AS t ON t.object_id = c.object_idJOIN sys.schemas AS s ON s.schema_id = t.schema_idWHERE s.name = N'ops' AND t.name IN (N'Technician', N'WorkOrder')ORDER BY t.name, c.column_id;GO

For example, ops.WorkOrder.technician_id is nullable because an unassigned work order is meaningful. priority is not nullable because “no priority value” is not part of the established model. That difference should appear in the application type model too.

5. Time-zone and string policies must survive a round trip

If the application represents an instant in UTC, decide whether it will send datetime2 with a documented UTC convention or datetimeoffset with +00:00. If the user’s original offset matters, do not throw it away accidentally. If future scheduling depends on named zones and daylight-saving rules, store the zone identifier separately because datetimeoffset stores an offset, not the whole rule set.

For text, choose Unicode based on domain needs. Do not “optimize” a human name from nvarchar to varchar without encoding/collation analysis. Conversely, an intentional ASCII-style machine code can stay varchar if the application binds it as such.

6. JSON mapping is a feature-status and driver contract

On the stable course path, JSON payloads are strings stored in validated nvarchar(max). SQL Server 2025’s native json type is currently preview for SQL Server 2025, and Microsoft.Data.SqlClient documentation already describes JSON-specific client support such as SqlJson/SqlDbType.Json in supported .NET versions. That is exactly why application mapping must record server feature status and driver capability.

Production rule

Do not migrate a production API to a preview server type because a new driver enum exists. Confirm server feature status, edition/build, driver version, TDS behavior, backup/migration tooling, ORM support and rollback strategy together. The mandatory lab stays on stable types.

7. Deliberately wrong approach: AddWithValue/inference defines the schema contract

A developer passes every string using a convenience inference API, every decimal without fixed precision/scale, and every time value as the language’s default date-time object. The code passes tests, but production sees parameter-length/type variation, implicit conversions, rounded decimals, or time-zone ambiguity. The problem is not the API name itself; it is allowing incidental runtime values to decide persistent domain metadata.

Repair

Create a small data-access mapping layer or stored-procedure contract where parameter type, size, precision/scale and nullability are explicit. Validate the mapping in integration tests against the actual SQL Server schema/driver version. Avoid hard-coding connection secrets while doing so.

8. Hands-on lab: expose a stored-procedure parameter contract

The mandatory lab stays entirely in SQL Server so every learner can complete it. It creates a disposable procedure whose parameters match the domain exactly, inspects that metadata, invokes it with typed values, and then removes it. Applications can use the same metadata as their binding specification.

sql · create and inspect a typed procedure contract
USE ServiceHubLab;GOIF SCHEMA_ID(N'lab03') IS NULL EXEC(N'CREATE SCHEMA lab03 AUTHORIZATION dbo;');GOCREATE OR ALTER PROCEDURE lab03.FindWorkOrders    @customer_code varchar(16),    @from_utc datetime2(0),    @minimum_priority tinyint = 1ASBEGIN    SET NOCOUNT ON;    SELECT work_order_id, customer_code, status, priority, opened_at    FROM ops.WorkOrder    WHERE customer_code = @customer_code      AND opened_at >= @from_utc      AND priority >= @minimum_priority    ORDER BY opened_at, work_order_id;END;GOSELECT    p.parameter_id,    p.name,    TYPE_NAME(p.user_type_id) AS type_name,    p.max_length,    p.precision,    p.scale,    p.is_output,    p.has_default_valueFROM sys.parameters AS pWHERE p.object_id = OBJECT_ID(N'lab03.FindWorkOrders')ORDER BY p.parameter_id;GOEXEC lab03.FindWorkOrders    @customer_code = 'CUST-001',    @from_utc = '2026-08-01T00:00:00',    @minimum_priority = 1;GO

Verification checklist

  • The server reports @customer_code as varchar(16), not an inferred Unicode/max string.
  • The temporal parameter has the intended datetime2 scale.
  • The application example uses parameterized values and an external connection-string source.
  • You can distinguish SQL NULL from empty string/zero/default.
  • You record that the native SQL Server 2025 JSON type is not a mandatory production mapping while it remains preview.
sql · cleanup the application contract probe
USE ServiceHubLab;GODROP PROCEDURE IF EXISTS lab03.FindWorkOrders;GO

9. Production judgment: version the boundary contract

Treat database/app type mappings as versioned interface definitions. A schema review should compare SQL type/length/precision/scale/nullability/collation to stored-procedure parameters, ORM model annotations, serialization formats, and driver bindings. Integration tests should include maximum lengths, decimal boundaries, Unicode, NULL, invalid values, UTC/offset round trips, and representative indexed predicates.

Chapter 03 closes with one principle: a SQL value is never “just a value.” It has type, nullability, collation/encoding where textual, precision/scale where numeric, temporal semantics where time-based, and client metadata when crossing a protocol. Chapter 04 builds on that foundation with schema ownership, keys, constraints, identity/sequence strategies, computed columns, and temporal tables.

Check your understanding

  1. Why can a .NET string parameter inferred as nvarchar matter when the SQL column is varchar?
  2. Which parameter metadata should be explicit for a decimal business value?
  3. What is the difference between datetime2 UTC convention and datetimeoffset?
  4. Why should a database NULL not automatically be replaced with empty string or zero in application code?
  5. What additional question must be answered before mapping an app JSON type to SQL Server 2025 native json?
Review the answers

The type mismatch can cause an implicit conversion on the varchar column and change plan/access behavior. If varchar is intentional, bind a matching varchar parameter/size.

The SQL type plus precision and scale, along with allowed range/rounding policy and nullability.

datetime2 stores date/time without an offset; a UTC convention is external metadata/policy. datetimeoffset stores a numeric offset with the value, but still not a named time-zone rule set.

Those are different domain states. Collapsing them can corrupt meaning and defeat database constraints/default semantics.

Confirm the server feature is GA/supported for your deployment and that the actual driver/ORM/toolchain supports its transport, parameters, migrations, backups and rollback. As of this review, Microsoft still labels the native type preview for SQL Server 2025.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.