✈️ FlightPlan Developer Guide

← Back to Guide

Raw String Literals

This is a pattern I'm adopting across the codebase because it measurably improves the SQL-authoring workflow. You'll start to see it in my code, and you'll see it in a future release of SqlStudio. This page explains what it is, why it's better than what we've been doing, and how it closes the loop between your C# file, SSMS, and the SqlStudio raw query window.

The pattern we're leaving behind

Here's the well-worn pattern most of our data access has used to assemble a query — a StringBuilder with one Append per line:

StringBuilder s = new StringBuilder();
s.Append("SELECT *");
s.Append("FROM   dictionary.Column_Details");
s.Append("WHERE  db_name = @db");
s.Append("AND  schema_name = @schema");
s.Append("AND  Table_name = @table");
s.Append("AND  (@showDeleted = 1 OR is_deleted = 0)");
s.Append("ORDER BY Position");

It works, but it has two real problems. Getting the query in takes a lot of typing, or a generator such as SqlStudio or an Excel template. And once it's in there, there's no clean way to get it back out into text you can paste into SSMS and run or edit. The SQL is trapped inside string plumbing.

There's a subtler third problem hiding in the sample above: each fragment has to carry its own leading and trailing spaces. Miss one and the concatenation silently fuses tokens — "@db" + "AND" becomes "@dbAND" — a runtime SQL error with no compile-time warning.

Enter raw string literals

C# 11 raw string literals let you write the query as itself. A block opens and closes with three (or more) double-quotes, and everything in between is taken verbatim — no escaping, no concatenation, no per-line ceremony. Read the language reference here.

The same query, rewritten as a raw string literal:

string sql = """
    SELECT *
    FROM   dictionary.Column_Details
    WHERE  db_name = @db
      AND  schema_name = @schema
      AND  Table_name = @table
      AND  (@showDeleted = 1 OR is_deleted = 0)
    ORDER BY Position
    """;

The value of sql is exactly the text between the delimiters — pure SQL, ready to hand to a DbQuery.

How the indentation works

This is the part worth understanding, because it's what makes the SQL align cleanly inside your C# file. For a multi-line raw string literal, the compiler looks at the indentation of the closing delimiter and strips exactly that much leading whitespace from every line of content. The opening and closing delimiters sit on their own lines and are not part of the value, and the final newline before the closing delimiter is dropped.

So the sql variable above actually contains this — no leading indentation, no trailing newline:

SELECT *
FROM   dictionary.Column_Details
WHERE  db_name = @db
  AND  schema_name = @schema
  AND  Table_name = @table
  AND  (@showDeleted = 1 OR is_deleted = 0)
ORDER BY Position

The relative indentation you add past the closing delimiter is preserved — that's why the AND lines stay tucked under WHERE. There's a built-in guardrail too: if any content line is indented less than the closing delimiter, it's a compile-time error, so you can't accidentally shift the SQL out of alignment. (Edge case: if your SQL itself contains three consecutive double-quotes, open and close the literal with four or more instead — the delimiter just has to be longer than any run inside.)

Benefits

Round-trip with SqlStudio and SSMS

SqlStudio is our home-grown visual query writer — drag and drop tables and it generates the raw SQL, plus a snippet you can copy straight into code. Today that snippet is the StringBuilder / Append pattern. A future release will also emit the raw-string-literal block, so you can drop a finished query directly into your C# file with correct alignment already applied.

And the loop runs both directions. Copy a raw-string-literal query out of your code into the SqlStudio raw query window (or into SSMS), run it, refine it, then copy it back into the literal. Because the indentation rules are symmetric, the SQL lands correctly on both sides — no cleanup on the way in or out.

Interpolating identifiers

Prefix the literal with a dollar sign — $"""... {db} ...""" — to interpolate. That's how you inject identifiers such as database and schema names that can't be passed as parameters. For composing conditional WHERE clauses and IN lists from interpolated fragments, see Building Dynamic SQL. Deciding when to interpolate an identifier versus parameterize a value (and how to keep interpolation safe) is its own topic and will get a dedicated page in this section.

← Back to Guide

Please wait...