Skip to content

Throw EF Core's non-composable SQL error for FromSql/SqlQuery over CALL and other non-query statements - #468

Merged
renemadsen merged 1 commit into
microting:masterfrom
selvasmallive:fix/fromsql-non-composable-sql
Oct 16, 2026
Merged

renemadsen merged 1 commit into
microting:masterfrom
selvasmallive:fix/fromsql-non-composable-sql

Conversation

@selvasmallive

Copy link
Copy Markdown

Problem

When a LINQ query composes over raw SQL that cannot be used as a derived table, the provider sends it to the server wrapped in a subquery anyway:

context.Set<Container>()
    .FromSqlRaw("CALL `GetContainers`()")
    .Where(c => c.Number > 10)
    .ToList();
SELECT `c`.`Id`, `c`.`Name`, `c`.`Number`
FROM (
    CALL `GetContainers`()
) AS `c`
WHERE `c`.`Number` > 10

The server answers with MySqlException: You have an error in your SQL syntax … near 'CALL …'. Every other relational provider throws InvalidOperationException with RelationalStrings.FromSqlNonComposable before a command is sent, which tells the user what is wrong and how to fix it (AsEnumerable()).

This is more visible with EF Core 10. An entity that has a JSON-mapped complex or owned type is never treated as a non-composed FromSql query (EF Core limitation: the JSON projection is not a plain column), so even a bare FromSqlRaw("CALL …").ToList() gets wrapped and fails with the syntax error.

Root cause

MySqlQuerySqlGenerator overrides CheckComposableSql with an empty body. The override was added in 2021 (PomeloFoundation PR 1539) because EF Core rejected WITH at the time. EF Core has accepted WITH since 7.0 (dotnet/efcore#26471), and PomeloFoundation issue 1543 has tracked removing the override since then.

Change

  • Replace the empty CheckComposableSql override with a CheckComposableSqlTrimmed override that defers to the base implementation (SELECT, WITH) and additionally accepts the forms MySQL and MariaDB can compose over but EF Core does not know about: a parenthesized query expression, TABLE … and VALUES …. This keeps raw SQL that works today working.
  • Remove the ten overrides in FromSqlSprocQueryMySqlTest that asserted MySqlException. The EF Core specification tests they replaced assert InvalidOperationException with the non-composable message, which is now what happens.
  • Add MySqlFromSqlComposabilityTest (8 tests).

Tests

New, in test/EFCore.MySql.Tests/Query/MySqlFromSqlComposabilityTest.cs:

Test Before After
Stored_procedure_call_without_composition_works pass pass
Composing_over_stored_procedure_call_throws_non_composable fail (MySqlException) pass
Composing_over_non_query_statement_throws_non_composable fail (MySqlException) pass
Composing_over_select_with_leading_comments_works pass pass
Composing_over_common_table_expression_works pass pass
Composing_over_parenthesized_query_expression_works pass pass
Composing_over_table_statement_works (MySQL 8.0.19+) pass pass
Composing_over_values_statement_works (MySQL 8.0.19+) pass pass

Results on MySQL 8.4.10, Windows, .NET SDK 10.0.101:

Suite Result
EFCore.MySql.Tests 92 passed, 0 failed
EFCore.MySql.FunctionalTests, filter FromSql|SqlQuery|SqlExecutor 370 passed, 0 failed
EFCore.MySql.IntegrationTests 37 passed, 0 failed
EFCore.MySql.FunctionalTests, full 29,563 passed, 0 failed, 467 skipped (identical totals to master)

I have no MariaDB instance locally, so MariaDB coverage relies on CI here.

Compatibility

  • Behaviour change: composing over non-composable SQL now throws InvalidOperationException instead of MySqlException. Both are failures; code that caught MySqlException for this case would need to catch the new type. No query that succeeded before fails now, as far as the test suite and the added guard tests show.
  • The TABLE and VALUES tests are gated on ServerVersionSupport.ValuesWithRows, so they skip on MariaDB and on MySQL before 8.0.19.

Related

Remaining limitations

  • A plain FromSql("CALL …") on an entity with a JSON-mapped member still cannot work; it now fails with the EF Core message instead of a syntax error. Fixing that needs a change in EF Core's IsNonComposedFromSql.
  • The check looks only at the first keyword, as EF Core's does. It does not parse SQL.

🤖 Generated with Claude Code

MySqlQuerySqlGenerator overrode CheckComposableSql with an empty method,
so composing over FromSql/SqlQuery with SQL such as CALL was sent to the
server as "SELECT ... FROM (CALL ...) AS t" and failed with a syntax
error. With EF Core 10 this also affects a plain FromSql("CALL ...") on
entities with a JSON-mapped member, which EF Core always wraps.

The override predates EF Core accepting WITH (7.0). Use the base check
again, and additionally accept the forms MySQL and MariaDB can compose
over: parenthesized query expressions, TABLE and VALUES.

Remove the FromSqlSprocQueryMySqlTest overrides that asserted
MySqlException; the EF Core specification tests assert the
non-composable InvalidOperationException.

See PomeloFoundation#1543 and PomeloFoundation#2035.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@renemadsen
renemadsen merged commit a5f59d7 into microting:master Oct 16, 2026
65 of 68 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants