为何Entity Framework Core对ContainsTable()排序需额外添加TOP 200?
- 在SSMS中执行以下查询可正常返回预期的1行数据:
SELECT * FROM CONTAINSTABLE(AppUsers, *, 'hick', 200) AS t INNER JOIN AppUsers u ON u.Id = t.[KEY] ORDER BY t.[RANK] DESC
- 但在Entity Framework中调用相同查询时出现异常:
listUsers = await dbContext.AppUsers.FromSqlInterpolated( $@"SELECT * FROM CONTAINSTABLE(AppUsers, *, {Query}, 200) as t INNER JOIN AppUsers u on u.Id = t.[KEY] ORDER BY t.[RANK] desc") .ToListAsync();
异常信息:
Microsoft.EntityFrameworkCore.Query: Error: An exception occurred while iterating over the results of a query for context type 'LouisHowe.core.Data.NoTrackingDbContext'.
Microsoft.Data.SqlClient.SqlException (0x80131904): The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
at Microsoft.Data.SqlClient.SqlCommand.<>c.b__208_0(Task
1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask2.InnerInvoke()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func4 operation, Func4 verifySucceeded, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable1.AsyncEnumerator.MoveNextAsync()ClientConnectionId:34a279b8-9257-4414-b7d0-b0a3c135a4db
Error Number:1033,State:1,Class:15
- 添加
TOP 200后查询可正常运行:
listUsers = await dbContext.AppUsers.FromSqlInterpolated( $@"SELECT top 200 * FROM CONTAINSTABLE(AppUsers, *, {Query}, 200) as t INNER JOIN AppUsers u on u.Id = t.[KEY] ORDER BY t.[RANK]") .ToListAsync();
疑问:已向CONTAINSTABLE传入参数200,为何仍需额外添加TOP 200?
这是因为Entity Framework处理FromSqlInterpolated查询时,会将你编写的SQL包装成一个子查询,最终发送到SQL Server的查询结构会类似这样:
SELECT [u].* FROM ( SELECT * FROM CONTAINSTABLE(AppUsers, *, 'hick', 200) as t INNER JOIN AppUsers u on u.Id = t.[KEY] ORDER BY t.[RANK] desc ) AS [u]
SQL Server的规则明确:子查询、派生表、视图等对象中使用ORDER BY时,必须配合TOP、OFFSET或FOR XML,否则会触发Error Number:1033的语法错误。
你传给CONTAINSTABLE的200,作用是限制CONTAINSTABLE自身返回的匹配行数(前200条相关度最高的记录),但这只会影响连接前的结果集。当EF把你的查询包装成子查询后,外层没有TOP限制,而子查询里的ORDER BY又不符合SQL规则,所以必须在你编写的SQL中显式添加TOP 200,让子查询本身满足SQL Server的语法要求。
简单总结:CONTAINSTABLE的参数控制它自己返回的行数,而TOP是让你的整个查询语句在被EF包装后,依然符合SQL Server对ORDER BY在子查询中的使用规则。
内容的提问来源于stack exchange,提问作者David Thielen

