Two shared scaffolds for blocks Sonar flagged across the SqlServer,
Snowflake, and PostgreSQL dialects:
1. **`AppendToClause` on `SqlBreakdownBase`** — collapses the "if
clause is empty set it, else append `{operation} {sql}`; then merge
comment with same rule" pattern that was repeated three times in
each of SqlServer/Snowflake `QueryBreakdown`. The matching
`AddWhereExpression` / `AddHavingExpression` / `AddWhereClause(string)`
sites in both files now delegate to a single `protected static`
helper. Operates against `ISqlClause`, so it works for both the
`WhereClause` and `HavingClause` properties.
2. **`HandleDoubleQuoteAsIdentifier` on `SqlServer.StatementParser`** —
PostgreSQL and Snowflake both override SqlServer's
`HandleDoubleQuote` (which produces a string-literal token) to
instead produce a `ColumnIdentifier` token. The two overrides had
identical 14-line bodies. The shared logic now lives once, and
each dialect's override is a one-liner that calls the helper.
Deliberately *not* refactored in this commit:
- The CTE WITH-clause SQL generation in SqlServer/Snowflake QueryBreakdown
(lines ~537-560 / ~579-601 Sonar flagged) — the surrounding logic
differs enough between the two that an extraction would obscure
rather than clarify.
- The PG/Snowflake QueryBreakdown constructor pair (lines 40-58 /
43-61) — only ~10 lines × 2; extracting requires either a new
shared helper for ~20 lines of savings or moving up the inheritance
chain, neither pays for itself.
All 1180 tests stay green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces `null` returns from `SqlBreakdownBase.GetQuery<T>()` and its four
overrides (LinqQueryBreakdown, SqlServer/PostgreSql/Snowflake QueryBreakdown)
with `Enumerable.Empty<T>().AsQueryable()`, and tightens the signature from
`IQueryable<T>?` to `IQueryable<T>`. The "we can't reconstruct" semantic
now lives in "the query yields zero rows" rather than a nullable return,
which is what callers in LINQ pipelines actually want.
Three LinqQueryBreakdownTests tests asserting `Is.Null` are renamed and
updated to assert `Is.Empty`.
BREAKING CHANGE: SqlBreakdownBase.GetQuery<T> and the SqlServer / PostgreSql /
Snowflake / LinqToSql QueryBreakdown.GetQuery<T> overrides no longer return
`IQueryable<T>?`; they now return a non-nullable `IQueryable<T>` that is
empty when reconstruction isn't possible. External NuGet consumers null-
checking the result must switch to `.Any()` / `Is.Empty` checks instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Manual sweep of all 42 IDE0028 sites flagged by SonarQube — `dotnet format
analyzers --diagnostics IDE0028` declined to fix these (no .editorconfig
opt-in for `dotnet_style_prefer_collection_expression`), so applied by
hand. The repo already targets `<LangVersion>latest</LangVersion>` on
net8.0, so C# 12 collection expressions are available.
Pattern: `new List<T>()` / `new Dictionary<K,V>()` / `new ArrayList()` /
`new()` -> `[]` for empty; `new List<T> { ... }` -> `[...]` for literal.
24 files touched in src/{EFCore, LinqToSql, Query, Snowflake, SqlBreakdown,
SqlServer}; tests untouched (no IDE0028 sites in test code).
Build clean (35 warnings unchanged from baseline, 0 errors). All tests
remain green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applied via `dotnet format analyzers --diagnostics CA1510 --severity info`.
Replaces `if (x == null) throw new ArgumentNullException(nameof(x));`
blocks with the one-line `ArgumentNullException.ThrowIfNull(x);` —
same behavior, same parameter name, much less noise.
6 files touched across SqlBreakdown, SqlServer, LinqToSql.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- WithClause.RecursiveQuery and WithClause.ColumnList had get/set bodies that
only forwarded to private backing fields. Convert both to auto-properties
and remove the now-orphaned _recursiveQuery / _columnList fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `[Serializable]` attribute and corresponding `[OnDeserialized]` methods have been removed from various breakdown classes. This eliminates reliance on `BinaryFormatter`, which is a deprecated and insecure serialization mechanism in modern .NET.
This change also resolves SonarQube rule S5766 warnings by removing the context in which they apply, leading to cleaner and more secure code.
Introduce a default regex match timeout across the library to prevent potential ReDoS attacks (SonarQube rule S6444).
Implement `[OnDeserialized]` methods to re-establish object invariants and validate state after deserialization, addressing SonarQube rule S5766.