A C# source generator that turns the queries in your .sql files into string constants, and into methods where a query has replacement tokens.
Keep SQL in .sql files, where your editor highlights it and your tools can run it:
-- name: GetUser
-- summary: Loads one user by id.
SELECT id, name
FROM users
WHERE id = @id;
-- name: ListUsers
SELECT id, name FROM users ORDER BY name;
Mark a partial type in the same folder, and use the queries by name:
using SqlSource;
[SqlQueries]
public partial class UserRepository(IDbConnection connection)
{
public Task<User> Get(int id) => connection.QuerySingleAsync<User>(Sql.GetUser, new { id });
}
A query that is renamed or removed is a compile error where it is used, and IntelliSense shows each query's summary and its SQL.
SqlSource is in early development, and no version has been published to nuget.org. It will be published as the SqlSource package.
dotnet add package SqlSource
The package is a development dependency. It adds nothing to your application's output and nothing to the dependencies of a package you build.
[SqlQueries] goes on a partial class, struct, record or record struct. The type may be static, generic, or nested in other types, as long as it and every type that contains it is partial.
| Property | Default | Meaning |
|---|---|---|
Path | The folder of the source file that carries the attribute | A folder or one .sql file, relative to that folder |
Mode | SqlQueriesMode.Nested | Where the generated members go |
Path, the type gets every .sql file in the folder of the source file that carries the attribute. Subfolders are not searched.Path that ends in .sql names one file: [SqlQueries(Path = "Queries/Users.sql")].Path names a folder: [SqlQueries(Path = "../Queries")].Path is always relative to the folder of the source file, never to the project. Both / and \ separate folders, and paths are compared ignoring case, so a project builds the same on every operating system. Two .sql files of a type whose paths differ only by case are therefore an error, SQLSRC013..sql file that no type uses is ignored, so a folder of migration scripts elsewhere in the project does no harm.| Mode | Generated members | Used as |
|---|---|---|
SqlQueriesMode.Nested | Public members in a private static class Sql nested in the type | Sql.GetUser, inside the type only |
SqlQueriesMode.Direct | Public members on the type itself | UserQueries.GetUser, wherever the type is visible |
[SqlQueries(Mode = SqlQueriesMode.Direct)]
public static partial class UserQueries;
Each member is a const string, or a static method when the query has tokens (see Tokens, below). Both are documented with the query's summary and its SQL.
SqlSource adds the attribute and SqlQueriesMode to each project that uses it, as internal types. A project that sees the internals of another one, as a test project does through InternalsVisibleTo, sees both types twice when both projects use SqlSource. Each project uses its own copy. The compiler warns about such a conflict (CS0436), and SqlSource turns that warning off for these two types only, as suppression SQLSRC901: nothing has to be added to NoWarn, and a conflict between two types of your own is still reported.
A line comment that starts its line and has the form -- name: GetUser begins a query. The query runs to the next -- name: line or to the end of the file, and its name becomes the member's name, so it must be a C# identifier.
A file with no -- name: line is one query, named after the file: CountUsers.sql becomes CountUsers.
Before the first -- name: line a file may hold comments, such as a licence header, and -- SqlSource: directives that apply to every query in the file.
A -- summary: line inside a query becomes the documentation of its member. Several are joined with a space. A query without one is documented with its name and its file.
-- name:, -- summary: and -- SqlSource: lines are removed./*+ ... */ and /*! ... */, are kept. So are MariaDB's /*M! ... */ and Oracle's --+ ... when the dialect is theirs.\n, so the SQL does not depend on how the file was checked out.A -- SqlSource: line holds one or more directives, separated by spaces. Inside a query it applies to that query. Before the first -- name: line it applies to every query in the file.
| Directive | Effect |
|---|---|
keep-comments | Comments and blank lines stay in the SQL |
token-ignore=name | {{name}} is literal text, not a token |
token-validation, no-token-validation | The query's method checks its arguments, or does not, whatever the project says (see Tokens, below) |
dialect=name | The file is read by the rules of that database (see Dialects, below). Allowed only before the first -- name: line and before any SQL. |
-- name: Report
-- SqlSource: keep-comments
SELECT /* the database logs this comment */ id FROM users;
Databases disagree about where a comment or a string ends. 'it\'s' is one string in MySQL and an unclosed one in PostgreSQL; # starts a comment in MySQL and names a temporary table in SQL Server. SqlSource removes comments, so it has to know which rules your SQL follows. Tell it the dialect.
| Dialect | Also accepted | Use it for |
|---|---|---|
ansi | The default. Any database without a dialect of its own, such as Db2. | |
mssql | sqlserver, tsql | SQL Server, Azure SQL |
postgres | postgresql | PostgreSQL, DuckDB |
cockroachdb | cockroach | CockroachDB |
mysql | MySQL | |
mariadb | MariaDB | |
sqlite | SQLite | |
oracle | Oracle, Firebird |
Names are not case-sensitive.
For the project, with an MSBuild property:
<PropertyGroup>
<SqlSourceDialect>postgres</SqlSourceDialect>
</PropertyGroup>
For some of its files, with metadata on their items. Where two lines match a file, the later one wins:
<ItemGroup>
<AdditionalFiles Update="Reporting/**/*.sql" SqlSourceDialect="mssql" />
</ItemGroup>
For one file, with a directive in the file:
-- SqlSource: dialect=mysql
-- name: FindByNote
SELECT id FROM notes WHERE body = 'it\'s here'; # MySQL reads this as a comment
The directive wins over the metadata, and the metadata over the property. A file has one dialect:
-- name: line and before its first SQL. Anywhere else it is the error SQLSRC115.MySQL and MariaDB have SQL modes that change how a string is read. If your server runs with one, name it after the dialect, with a comma:
| Option | Also accepted | SQL mode | What it changes |
|---|---|---|---|
ansi-quotes | ansi_quotes | ANSI_QUOTES | "..." is a quoted identifier, and a backslash does not escape in it |
no-backslash-escapes | no_backslash_escapes | NO_BACKSLASH_ESCAPES | A backslash does not escape in '...' or in "..." |
<PropertyGroup>
<SqlSourceDialect>mysql,ansi-quotes</SqlSourceDialect>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Update="Legacy/**/*.sql" SqlSourceDialect="mariadb,ansi-quotes,no-backslash-escapes" />
</ItemGroup>
-- SqlSource: dialect=mysql,no-backslash-escapes
-- name: GetPath
SELECT 'C:\temp\' AS path;
mysql and mariadb have options. An option of another dialect, or one that does not exist, is the same error as a name that is not a dialect.dialect=mysql in a project that sets mysql,ansi-quotes is read as plain mysql.ansi | mssql | postgres | cockroachdb | mysql | mariadb | sqlite | oracle | |
|---|---|---|---|---|---|---|---|---|
A backslash escapes in '...' and "..." | No | No | No | No | Yes | Yes | No | No |
A backslash escapes in E'...' | Yes | No | Yes | Yes | Yes | Yes | No | No |
`...` is a quoted identifier | Yes | No | No | No | Yes | Yes | Yes | No |
[...] is a quoted identifier | No | Yes | No | No | No | No | Yes | No |
$tag$...$tag$ is a string | Yes | No | Yes | Yes | Yes | No | No | No |
q'[...]' is a string | No | No | No | No | No | No | No | Yes |
A /* inside a block comment needs its own */ | Yes | Yes | Yes | Yes | No | No | No | No |
-- is a comment with no whitespace after it | Yes | Yes | Yes | Yes | No | No | Yes | Yes |
# starts a comment | No | No | No | No | Yes | Yes | No | No |
Kept as hints, besides /*+ ... */ and /*! ... */ | /*M! ... */ | --+ ... |
Four details:
mssql a ]] inside brackets stands for one ]. In sqlite the first ] ends the identifier.mysql and mariadb an option changes the first row (see Options of a dialect, above).postgres and cockroachdb an E'...' string that is continued on the next line is one string, and each later part takes backslash escapes too. In postgres, -- comments may stand between the parts, as PostgreSQL allows, and they are removed like any other comment. In cockroachdb only whitespace may.cockroachdb a bytes literal, b'...', takes backslash escapes as an E'...' string does.Under mysql and mariadb a marker needs the space that those databases need: -- name: GetUser is a marker, and --name: GetUser is not a comment at all.
SqlSource reads these differently from the database, whatever the dialect:
| Construct | Database | How SqlSource reads it |
|---|---|---|
A versioned comment whose body holds a string that contains */, such as /*!50700 SELECT '*/' */ | MySQL, MariaDB | The comment ends at the first */. Where the server ends it depends on the server's version. |
| A block comment that is still open at the end of the file | SQLite | The error SQLSRC102 |
| A file whose lines end with a carriage return alone, with no line feed | MySQL, SQLite, CockroachDB | A line ends there, as in every dialect. These databases end a -- comment only at a line feed. |
A client command that is not SQL: DELIMITER, GO, SQL*Plus PROMPT and REM, a psql \ command | All | As SQL, so a quote in it can open a string |
And ansi reads the SQL of every database by one set of rules, so it misreads each construct in the table above that it says No to and your database says Yes to. The fix for those is to set the dialect.
A misread has one of two results:
keep-comments does not help.ansi, SELECT 'a\'b -- c', 2 becomes SELECT 'a\'b. Nothing is reported. keep-comments on the query prevents the removal.If your SQL uses one of these constructs, check the generated SQL: hover over the member, or read its documentation.
A query parameter such as @id cannot stand for a table name, a list of columns or a whole clause. A token can: {{name}} in a query is replaced with text that the caller supplies.
-- name: ListFrom
-- summary: Lists the rows of one table.
SELECT id, name FROM {{table}} ORDER BY {{orderBy}};
A query with a token is a static method, not a constant. The method has one string parameter for each token name, in the order the names first appear, and returns the SQL with every token replaced:
var sql = Sql.ListFrom("users", "name DESC");
// SELECT id, name FROM users ORDER BY name DESC;
{{Table}} and {{table}} are two parameters.{{table-name}}, {{1st}} or {{order by}}, are not a token. The text stays in the SQL as written, and nothing is reported.token-ignore=name directive lists its name.Tokens are for trusted text only. A token is replaced by string concatenation. Nothing is escaped, quoted or checked for safety, so a value that a user can influence is a SQL injection. Use a token for a fragment that your own code chooses, such as a table name from a fixed list, and a query parameter for every value.
By default the method checks each argument with ArgumentException.ThrowIfNullOrWhiteSpace: null throws ArgumentNullException, and an empty or blank string throws ArgumentException. The check is that a value is present, not that it is safe.
An empty fragment can be what you want, for an optional clause for example, so the check can be turned off. Three switches decide, and the first one that applies wins:
-- SqlSource: token-validation or -- SqlSource: no-token-validation directive inside the query.-- name: line, which covers every query in the file.SqlSourceTokenValidation, which covers the project. It accepts true and false; any other value is the error SQLSRC010.<PropertyGroup>
<SqlSourceTokenValidation>false</SqlSourceTokenValidation>
</PropertyGroup>
-- name: ListFiltered
-- SqlSource: no-token-validation
SELECT id, name FROM users {{whereClause}};
With validation off the method checks nothing. An empty argument leaves nothing where its token was, and a null argument throws NullReferenceException.
Every problem SqlSource finds is a build error with an id that starts SQLSRC. An error in a .sql file is reported at its line and column in that file, and that file produces no members until it is fixed. docs/diagnostics.md explains each one.
The package registers every .sql file under the project's folder with the compiler as an AdditionalFiles item, which is how a source generator sees a file that is not C#. The output and intermediate folders are left out. A .sql file outside the project's folder is not registered; list it yourself.
To leave some files out:
<ItemGroup>
<AdditionalFiles Remove="Migrations/**/*.sql" />
</ItemGroup>
To turn the default off and list the files yourself:
<PropertyGroup>
<SqlSourceIncludeFiles>false</SqlSourceIncludeFiles>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="Queries/**/*.sql" />
</ItemGroup>
SqlSourceTokenValidation turns the argument checks of the generated methods off for a project when it is false. See Tokens, above.
SqlSourceDialect names the database whose rules the .sql files are read by. It is a property for the project and metadata of an AdditionalFiles item for some of its files. See Dialects, above.
A project that lists its own files can give the metadata where it lists them:
<ItemGroup>
<AdditionalFiles Include="Queries/**/*.sql" SqlSourceDialect="postgres" />
</ItemGroup>
A project that uses SqlSource must target .NET 8 or later; the generated code relies on it, and an older target is reported as SQLSRC003.
The generated code is C# 12, the default language version of a project that targets .NET 8. A project that sets LangVersion below 12 gets SQLSRC012.
The generator is compiled against Roslyn 4.8.0, so it loads in the .NET 8 SDK and later and in Visual Studio 2022 17.8 and later. Older SDKs and IDEs are not supported.
This is what a project that uses the generator needs. Working on the generator itself needs more; see Contributing, below.
A C# source generator that turns the queries in your .sql files into string constants, and into methods where a query has replacement tokens.
Keep SQL in .sql files, where your editor highlights it and your tools can run it:
-- name: GetUser
-- summary: Loads one user by id.
SELECT id, name
FROM users
WHERE id = @id;
-- name: ListUsers
SELECT id, name FROM users ORDER BY name;
Mark a partial type in the same folder, and use the queries by name:
using SqlSource;
[SqlQueries]
public partial class UserRepository(IDbConnection connection)
{
public Task<User> Get(int id) => connection.QuerySingleAsync<User>(Sql.GetUser, new { id });
}
A query that is renamed or removed is a compile error where it is used, and IntelliSense shows each query's summary and its SQL.
SqlSource is in early development, and no version has been published to nuget.org. It will be published as the SqlSource package.
dotnet add package SqlSource
The package is a development dependency. It adds nothing to your application's output and nothing to the dependencies of a package you build.
[SqlQueries] goes on a partial class, struct, record or record struct. The type may be static, generic, or nested in other types, as long as it and every type that contains it is partial.
| Property | Default | Meaning |
|---|---|---|
Path | The folder of the source file that carries the attribute | A folder or one .sql file, relative to that folder |
Mode | SqlQueriesMode.Nested | Where the generated members go |
Path, the type gets every .sql file in the folder of the source file that carries the attribute. Subfolders are not searched.Path that ends in .sql names one file: [SqlQueries(Path = "Queries/Users.sql")].Path names a folder: [SqlQueries(Path = "../Queries")].Path is always relative to the folder of the source file, never to the project. Both / and \ separate folders, and paths are compared ignoring case, so a project builds the same on every operating system. Two .sql files of a type whose paths differ only by case are therefore an error, SQLSRC013..sql file that no type uses is ignored, so a folder of migration scripts elsewhere in the project does no harm.| Mode | Generated members | Used as |
|---|---|---|
SqlQueriesMode.Nested | Public members in a private static class Sql nested in the type | Sql.GetUser, inside the type only |
SqlQueriesMode.Direct | Public members on the type itself | UserQueries.GetUser, wherever the type is visible |
[SqlQueries(Mode = SqlQueriesMode.Direct)]
public static partial class UserQueries;
Each member is a const string, or a static method when the query has tokens (see Tokens, below). Both are documented with the query's summary and its SQL.
SqlSource adds the attribute and SqlQueriesMode to each project that uses it, as internal types. A project that sees the internals of another one, as a test project does through InternalsVisibleTo, sees both types twice when both projects use SqlSource. Each project uses its own copy. The compiler warns about such a conflict (CS0436), and SqlSource turns that warning off for these two types only, as suppression SQLSRC901: nothing has to be added to NoWarn, and a conflict between two types of your own is still reported.
A line comment that starts its line and has the form -- name: GetUser begins a query. The query runs to the next -- name: line or to the end of the file, and its name becomes the member's name, so it must be a C# identifier.
A file with no -- name: line is one query, named after the file: CountUsers.sql becomes CountUsers.
Before the first -- name: line a file may hold comments, such as a licence header, and -- SqlSource: directives that apply to every query in the file.
A -- summary: line inside a query becomes the documentation of its member. Several are joined with a space. A query without one is documented with its name and its file.
-- name:, -- summary: and -- SqlSource: lines are removed./*+ ... */ and /*! ... */, are kept. So are MariaDB's /*M! ... */ and Oracle's --+ ... when the dialect is theirs.\n, so the SQL does not depend on how the file was checked out.A -- SqlSource: line holds one or more directives, separated by spaces. Inside a query it applies to that query. Before the first -- name: line it applies to every query in the file.
| Directive | Effect |
|---|---|
keep-comments | Comments and blank lines stay in the SQL |
token-ignore=name | {{name}} is literal text, not a token |
token-validation, no-token-validation | The query's method checks its arguments, or does not, whatever the project says (see Tokens, below) |
dialect=name | The file is read by the rules of that database (see Dialects, below). Allowed only before the first -- name: line and before any SQL. |
-- name: Report
-- SqlSource: keep-comments
SELECT /* the database logs this comment */ id FROM users;
Databases disagree about where a comment or a string ends. 'it\'s' is one string in MySQL and an unclosed one in PostgreSQL; # starts a comment in MySQL and names a temporary table in SQL Server. SqlSource removes comments, so it has to know which rules your SQL follows. Tell it the dialect.
| Dialect | Also accepted | Use it for |
|---|---|---|
ansi | The default. Any database without a dialect of its own, such as Db2. | |
mssql | sqlserver, tsql | SQL Server, Azure SQL |
postgres | postgresql | PostgreSQL, DuckDB |
cockroachdb | cockroach | CockroachDB |
mysql | MySQL | |
mariadb | MariaDB | |
sqlite | SQLite | |
oracle | Oracle, Firebird |
Names are not case-sensitive.
For the project, with an MSBuild property:
<PropertyGroup>
<SqlSourceDialect>postgres</SqlSourceDialect>
</PropertyGroup>
For some of its files, with metadata on their items. Where two lines match a file, the later one wins:
<ItemGroup>
<AdditionalFiles Update="Reporting/**/*.sql" SqlSourceDialect="mssql" />
</ItemGroup>
For one file, with a directive in the file:
-- SqlSource: dialect=mysql
-- name: FindByNote
SELECT id FROM notes WHERE body = 'it\'s here'; # MySQL reads this as a comment
The directive wins over the metadata, and the metadata over the property. A file has one dialect:
-- name: line and before its first SQL. Anywhere else it is the error SQLSRC115.MySQL and MariaDB have SQL modes that change how a string is read. If your server runs with one, name it after the dialect, with a comma:
| Option | Also accepted | SQL mode | What it changes |
|---|---|---|---|
ansi-quotes | ansi_quotes | ANSI_QUOTES | "..." is a quoted identifier, and a backslash does not escape in it |
no-backslash-escapes | no_backslash_escapes | NO_BACKSLASH_ESCAPES | A backslash does not escape in '...' or in "..." |
<PropertyGroup>
<SqlSourceDialect>mysql,ansi-quotes</SqlSourceDialect>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Update="Legacy/**/*.sql" SqlSourceDialect="mariadb,ansi-quotes,no-backslash-escapes" />
</ItemGroup>
-- SqlSource: dialect=mysql,no-backslash-escapes
-- name: GetPath
SELECT 'C:\temp\' AS path;
mysql and mariadb have options. An option of another dialect, or one that does not exist, is the same error as a name that is not a dialect.dialect=mysql in a project that sets mysql,ansi-quotes is read as plain mysql.ansi | mssql | postgres | cockroachdb | mysql | mariadb | sqlite | oracle | |
|---|---|---|---|---|---|---|---|---|
A backslash escapes in '...' and "..." | No | No | No | No | Yes | Yes | No | No |
A backslash escapes in E'...' | Yes | No | Yes | Yes | Yes | Yes | No | No |
`...` is a quoted identifier | Yes | No | No | No | Yes | Yes | Yes | No |
[...] is a quoted identifier | No | Yes | No | No | No | No | Yes | No |
$tag$...$tag$ is a string | Yes | No | Yes | Yes | Yes | No | No | No |
q'[...]' is a string | No | No | No | No | No | No | No | Yes |
A /* inside a block comment needs its own */ | Yes | Yes | Yes | Yes | No | No | No | No |
-- is a comment with no whitespace after it | Yes | Yes | Yes | Yes | No | No | Yes | Yes |
# starts a comment | No | No | No | No | Yes | Yes | No | No |
Kept as hints, besides /*+ ... */ and /*! ... */ | /*M! ... */ | --+ ... |
Four details:
mssql a ]] inside brackets stands for one ]. In sqlite the first ] ends the identifier.mysql and mariadb an option changes the first row (see Options of a dialect, above).postgres and cockroachdb an E'...' string that is continued on the next line is one string, and each later part takes backslash escapes too. In postgres, -- comments may stand between the parts, as PostgreSQL allows, and they are removed like any other comment. In cockroachdb only whitespace may.cockroachdb a bytes literal, b'...', takes backslash escapes as an E'...' string does.Under mysql and mariadb a marker needs the space that those databases need: -- name: GetUser is a marker, and --name: GetUser is not a comment at all.
SqlSource reads these differently from the database, whatever the dialect:
| Construct | Database | How SqlSource reads it |
|---|---|---|
A versioned comment whose body holds a string that contains */, such as /*!50700 SELECT '*/' */ | MySQL, MariaDB | The comment ends at the first */. Where the server ends it depends on the server's version. |
| A block comment that is still open at the end of the file | SQLite | The error SQLSRC102 |
| A file whose lines end with a carriage return alone, with no line feed | MySQL, SQLite, CockroachDB | A line ends there, as in every dialect. These databases end a -- comment only at a line feed. |
A client command that is not SQL: DELIMITER, GO, SQL*Plus PROMPT and REM, a psql \ command | All | As SQL, so a quote in it can open a string |
And ansi reads the SQL of every database by one set of rules, so it misreads each construct in the table above that it says No to and your database says Yes to. The fix for those is to set the dialect.
A misread has one of two results:
keep-comments does not help.ansi, SELECT 'a\'b -- c', 2 becomes SELECT 'a\'b. Nothing is reported. keep-comments on the query prevents the removal.If your SQL uses one of these constructs, check the generated SQL: hover over the member, or read its documentation.
A query parameter such as @id cannot stand for a table name, a list of columns or a whole clause. A token can: {{name}} in a query is replaced with text that the caller supplies.
-- name: ListFrom
-- summary: Lists the rows of one table.
SELECT id, name FROM {{table}} ORDER BY {{orderBy}};
A query with a token is a static method, not a constant. The method has one string parameter for each token name, in the order the names first appear, and returns the SQL with every token replaced:
var sql = Sql.ListFrom("users", "name DESC");
// SELECT id, name FROM users ORDER BY name DESC;
{{Table}} and {{table}} are two parameters.{{table-name}}, {{1st}} or {{order by}}, are not a token. The text stays in the SQL as written, and nothing is reported.token-ignore=name directive lists its name.Tokens are for trusted text only. A token is replaced by string concatenation. Nothing is escaped, quoted or checked for safety, so a value that a user can influence is a SQL injection. Use a token for a fragment that your own code chooses, such as a table name from a fixed list, and a query parameter for every value.
By default the method checks each argument with ArgumentException.ThrowIfNullOrWhiteSpace: null throws ArgumentNullException, and an empty or blank string throws ArgumentException. The check is that a value is present, not that it is safe.
An empty fragment can be what you want, for an optional clause for example, so the check can be turned off. Three switches decide, and the first one that applies wins:
-- SqlSource: token-validation or -- SqlSource: no-token-validation directive inside the query.-- name: line, which covers every query in the file.SqlSourceTokenValidation, which covers the project. It accepts true and false; any other value is the error SQLSRC010.<PropertyGroup>
<SqlSourceTokenValidation>false</SqlSourceTokenValidation>
</PropertyGroup>
-- name: ListFiltered
-- SqlSource: no-token-validation
SELECT id, name FROM users {{whereClause}};
With validation off the method checks nothing. An empty argument leaves nothing where its token was, and a null argument throws NullReferenceException.
Every problem SqlSource finds is a build error with an id that starts SQLSRC. An error in a .sql file is reported at its line and column in that file, and that file produces no members until it is fixed. docs/diagnostics.md explains each one.
The package registers every .sql file under the project's folder with the compiler as an AdditionalFiles item, which is how a source generator sees a file that is not C#. The output and intermediate folders are left out. A .sql file outside the project's folder is not registered; list it yourself.
To leave some files out:
<ItemGroup>
<AdditionalFiles Remove="Migrations/**/*.sql" />
</ItemGroup>
To turn the default off and list the files yourself:
<PropertyGroup>
<SqlSourceIncludeFiles>false</SqlSourceIncludeFiles>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="Queries/**/*.sql" />
</ItemGroup>
SqlSourceTokenValidation turns the argument checks of the generated methods off for a project when it is false. See Tokens, above.
SqlSourceDialect names the database whose rules the .sql files are read by. It is a property for the project and metadata of an AdditionalFiles item for some of its files. See Dialects, above.
A project that lists its own files can give the metadata where it lists them:
<ItemGroup>
<AdditionalFiles Include="Queries/**/*.sql" SqlSourceDialect="postgres" />
</ItemGroup>
A project that uses SqlSource must target .NET 8 or later; the generated code relies on it, and an older target is reported as SQLSRC003.
The generated code is C# 12, the default language version of a project that targets .NET 8. A project that sets LangVersion below 12 gets SQLSRC012.
The generator is compiled against Roslyn 4.8.0, so it loads in the .NET 8 SDK and later and in Visual Studio 2022 17.8 and later. Older SDKs and IDEs are not supported.
This is what a project that uses the generator needs. Working on the generator itself needs more; see Contributing, below.