-
-
Notifications
You must be signed in to change notification settings - Fork 316
Description
Issue Description
Batch operations (Insert
, Update
, DeleteFromQuery
) fail to translate into SQL. This issue was first observed in EF Core version 5 and persists in EF Core 8 as well.
When using Join()
in a query with a temporary table represented as IQueryable<int>
, the DeleteFromQuery()
operation fails with the following error:
ArgumentException: No mapping exists from object type System.Object[] to a known managed provider native type.
Context
I have implemented a custom solution to use temporary tables in EF Core queries. The temporary table is used to filter records by integer IDs, as shown below:
SortedSet<int> orderIds = [...];
IQueryable<int> ordersIdsQuery = await helper.GetTemporaryTableIdQuery(db, "#orders", orderIds, ct);
// It creates the temporary table as:
// CREATE TABLE [#orders](Id INT NOT NULL PRIMARY KEY CLUSTERED)
The temporary table #orders
is created and populated with integer IDs using bulk copy. I then join this ordersIdsQuery
with my entity query to filter records by their primary integer ID:
var count = await db.Set<ServiceOrder>()
.Join(ordersIdsQuery, x => x.Id, x => x, (x, _) => x)
.SelectMany(x => x.BillingEvents, (_, x) => x)
.DeleteFromQuery(ct);
This query should translate into SQL similar to:
DELETE FROM [b0]
FROM [Bill_DistributionDocument] AS [b]
INNER JOIN (
SELECT Id FROM [#orders]
) AS [u] ON [b].[Id] = [u].[Id]
INNER JOIN [Bill_Event] AS [b0] ON [b].[Id] = [b0].[DistributionDocId]
However, when the query is joined with the temporary table, the batch operation fails.
Investigation
I created a gist to demonstrate the issue with a working example in LinqPad. The gist includes a rudimentary implementation of temporary tables in queries.
Interestingly, if I use EF Core's native batch delete operation (ExecuteDelete
), the query works as expected.
Based on my investigation, the issue seems to be related to the fact that the temporary table query representation (IQueryable<int>
) is created using raw SQL and lacks related table mapping.
Exception stack trace
at Microsoft.Data.SqlClient.MetaType.GetMetaTypeFromValue(Type dataType, Object value, Boolean inferLen, Boolean streamAllowed) in /_/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs:line 405
at Microsoft.Data.SqlClient.SqlParameter.Validate(Int32 index, Boolean isCommandProc) in /_/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs:line 1922
at Microsoft.Data.SqlClient.SqlCommand.BuildParamList(TdsParser parser, SqlParameterCollection parameters, Boolean includeReturnValue) in /_/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs:line 6134
at Microsoft.Data.SqlClient.SqlCommand.BuildExecuteSql(CommandBehavior behavior, String commandText, SqlParameterCollection parameters, _SqlRPC& rpc) in /_/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs:line 6003
at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean isAsync, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest) in /_/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs:line 4922
at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String method) in /_/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs:line 4663
at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) in /_/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs:line 4544
at Microsoft.Data.SqlClient.SqlCommand.ExecuteScalar() in /_/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs:line 1127
at Z.EntityFramework.Extensions.BatchDelete.<>c.(DbCommand )
at Z.EntityFramework.Extensions.BatchDelete.Execute[T](IQueryable`1 query)
at BatchDeleteExtensions.DeleteFromQuery[T](IQueryable`1 query, Action`1 batchDeleteBuilder)
at BatchDeleteExtensions.`1.()
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)