dotnet/efcore · error · InvalidOperationException
'FromSql' or 'SqlQuery' was called with non-composable SQL a
Error message
'FromSql' or 'SqlQuery' was called with non-composable SQL and with a query composing over it. Consider calling 'AsEnumerable' after the method to perform the composition on the client side.
What it means
EF Core throws this from CheckComposableSql when it must embed your raw SQL as a subquery (i.e. you compose LINQ over FromSqlRaw/FromSqlInterpolated/SqlQueryRaw/SqlQueryInterpolated) but the SQL cannot be verified as composable. This specific throw site fires when the trimmed SQL begins a '--' line comment that is never terminated by a newline, so EF cannot find the actual statement that follows. EF composes by wrapping your SQL in (...) and projecting columns, so it must be a single SELECT/WITH statement.
Source
Thrown at src/EFCore.Relational/Query/QuerySqlGenerator.cs:567
/// <summary>
/// Checks whether a given SQL string is composable, i.e. can be embedded as a subquery within a
/// larger SQL query.
/// </summary>
/// <param name="sql">An SQL string to be checked for composability.</param>
/// <exception cref="InvalidOperationException">The given SQL isn't composable.</exception>
protected virtual void CheckComposableSql(string sql)
{
var span = sql.AsSpan().TrimStart();
while (true)
{
// SQL -- comment
if (span.StartsWith("--"))
{
var i = span.IndexOf('\n');
span = i > 0
? span[(i + 1)..].TrimStart()
: throw new InvalidOperationException(RelationalStrings.FromSqlNonComposable);
continue;
}
// SQL /* */ comment
if (span.StartsWith("/*"))
{
var i = span.IndexOf("*/");
span = i > 0
? span[(i + 2)..].TrimStart()
: throw new InvalidOperationException(RelationalStrings.FromSqlNonComposable);
continue;
}
break;
}
CheckComposableSqlTrimmed(span);
}View on GitHub (pinned to 3a2006ef56)
Solutions
- Remove the unterminated trailing '--' comment, or add a newline after it so the real SELECT/WITH follows.
- If the SQL is genuinely non-composable (stored proc, multi-statement), stop composing: append .AsEnumerable() (or .ToList()) immediately after FromSql*/SqlQuery* and do the remaining LINQ in memory.
- Ensure the SQL, after comments are stripped, begins with SELECT or WITH followed by whitespace.
Example fix
// before
var q = db.Blogs
.FromSqlRaw("-- top comment" + sql) // no '\n' -> throws 580
.Where(b => b.Active);
// after
var q = db.Blogs
.FromSqlRaw("-- top comment\n" + sql)
.Where(b => b.Active); Defensive patterns
Strategy: validation
Validate before calling
// Reject SQL with an unterminated '--' comment before FromSql*.
static bool HasUnterminatedLineComment(string sql)
{
var span = sql.AsSpan().TrimStart();
while (span.Length > 0)
{
if (span.StartsWith("--"))
{
var nl = span.IndexOf('\n');
if (nl <= 0) return true; // unterminated
span = span[(nl + 1)..].TrimStart();
}
else if (span.StartsWith("/*"))
{
var end = span.IndexOf("*/");
if (end < 0) return true;
span = span[(end + 2)..].TrimStart();
}
else break;
}
return false;
}
if (HasUnterminatedLineComment(sql))
throw new ArgumentException("SQL has an unterminated '--' comment; EF will reject it as non-composable."); Prevention
- Build raw SQL from templates that always terminate comments with a newline.
- When composing LINQ over FromSql*/SqlQuery*, prefer a single SELECT/WITH statement and avoid inline comments.
- If the SQL is non-composable, call .AsEnumerable()/.ToListAsync() immediately after FromSql*.
When it happens
Trigger: Calling context.Set<T>().FromSqlRaw("-- comment with no trailing newline") followed by any LINQ operator (Where/Select/OrderBy/...). Also FromSqlInterpolated with the same shape, or SqlQueryRaw/SqlQueryInterpolated on DbContext. The composition forces VisitFromSql -> CheckComposableSql. Any '--' comment that runs to the end of the string with no '\n' hits this branch (IndexOf('\n') <= 0).
Common situations: Hand-written SQL strings built by concatenation where a trailing comment loses its newline; SQL copied from a profiler/SSMS that ends in a comment; templated SQL where a placeholder for the statement body resolves to empty. Encountered when migrating from raw ADO.NET to FromSql while keeping comments.
Related errors
- A FromSqlExpression has an invalid arguments expression type
- The LINQ expression '{expression}' could not be translated.
- Unable to translate set operation after client projection ha
- 'DefaultIfEmpty' cannot be applied after a client-evaluated
- Using 'Distinct' operation on a projection containing a subq
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/d85d408077c45c45.
Report an issue: GitHub.