EntityFramework-Plus: "Invalid column name 'Id'." at UpdateFromQuery when using Where-condition with related entity

1. Description

I am trying to run the following query:

var deletedItems = db.OrderItems.Where(i => !i.IsDeleted && !i.Order.IsCompleted && i.Order.OrderDate < margin)
				.UpdateFromQuery(i => new OrderItem()
					{
						IsDeleted = true,
						DeletedReason = DeletedReason.Cleanup,
						DeletedDate = deletionDate,
						TransactionId = transactionId,
					});

On the following models (simplified):

	public class OrderItem
	{
		[Key]
		public int Id { get; set; }

		[ForeignKey(nameof(Order)), Column("Order_Id")]
		public int OrderId { get; set; }
		public virtual Order Order { get; set; }

		public bool IsDeleted { get; set; }
		public DeletedReason? DeletedReason { get; set; }
		public DateTime? DeletedDate { get; set; }
		public Guid? TransactionId { get; set; } = Guid.NewGuid();
	}

	public class Order
	{
		[Key]
		public int Id { get; set; }

		public DateTime OrderDate { get; set; }
		public bool IsCompleted { get; set; }

		public virtual List<OrderItem> Items { get; set; } = new ();
	}

2. Exception

This results in the following exception:

System.Data.SqlClient.SqlException (0x80131904): Invalid column name 'Id'.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)
   at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.NonQuery(DbCommand command, DbCommandInterceptionContext interceptionContext)
   at .[](IQueryable`1 , Expression`1 )
   at DbContextExtensions.[](IQueryable`1 , Expression`1 , Action`1 , Boolean )
   at DbContextExtensions.UpdateFromQuery[TEntity](IQueryable`1 query, Expression`1 updateExpression, Action`1 bulkOperationFactory)

If I log the generated SQL it really is invalid. Here the main parts of it (there is a lot of more SQL in the middle, as OrderItem has many inherited types):

UPDATE A 
SET A.[IsDeleted] = @zzz_BatchUpdate_0,
A.[DeletedReason] = @zzz_BatchUpdate_1,
A.[DeletedDate] = @zzz_BatchUpdate_2,
A.[TransactionId] = @zzz_BatchUpdate_3
FROM [dbo].[EventTicket@OrderItems] AS A
INNER JOIN ( SELECT 
    1 AS [C1], 
    [Project13].[C1] AS [C2], 
    -- ... 
    [Project13].[C30] AS [C30]
    FROM (
		-- ....
	) AS [Project13]
    OUTER APPLY  (SELECT [Project14].[C2] AS [C2], [Project14].[C3] AS [C3]
        FROM ( SELECT 
            [Extent7].[Id] AS [C1], 
            [Extent7].[OrderDate] AS [C2], 
            [Extent7].[IsCompleted] AS [C3]
            FROM [dbo].[EventTicket@Orders] AS [Extent7]
        )  AS [Project14]
        WHERE [Project13].[C29] = [Project14].[C1] ) AS [Filter1]
    WHERE ([Project13].[C4] <> 1) AND ([Filter1].[C3] <> 1) AND ([Filter1].[C2] < @p__linq__0)
           ) AS B ON A.[Id] = B.[Id]

The error is from the very last ON A.[Id] = B.[Id] - there is no [Id] on B and I also think it should be A.[Order_Id]?

3. Fiddle or Project

https://dotnetfiddle.net/6N3nfr - BUT this example works, I’m trying to find out why or what’s the difference to my real code…

4. Any further technical details

I’m using the latest version of EF 6 and EF-Plus/-Extensions as of today (2021-10-30).

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 16 (5 by maintainers)

Most upvoted comments

Please find attached a minimal reproduction example. Bug-related comments are annotated with FIXME. BugEFExtMinRepro.zip

Effectively, this bug seems triggered by a very large number of columns in the base class table, in a TPT setup. (the repro mimicks the large legacy application that triggered the bug).

Furthermore, depending on the number of child classes in the TPT hierarchy, it seems I can trigger either an “Invalid column name ‘ID’” or a “Sequence contains no elements” error, but I haven’t explored the latter.