Skip to content

Batch Operations (InsertFromQuery/UpdateFromQuery/DeleteFromQuery) fail SQL translation #830

@deimastep

Description

@deimastep

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)

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions