Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flatten CASE expressions to avoid error "Case expressions may only be nested to level 10" #12729

Closed
bradmarder opened this issue Jul 19, 2018 · 13 comments · Fixed by #18886
Closed
Assignees
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported Servicing-approved type-enhancement
Milestone

Comments

@bradmarder
Copy link

This fails on SQL server, but works for in-memory/SQLite.

System.Data.SqlClient.SqlException (0x80131904): Case expressions may only be nested to level 10.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.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& dataReSystem.Data.SqlClient.SqlException: Case expressions may only be nested to level 10.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.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.SqlDataReader.TryConsumeMetaData()
   at System.Data.SqlClient.SqlDataReader.get_MetaData()
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
   at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
   at System.Data.Common.DbCommand.ExecuteReader()
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteReader(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.BufferlessMoveNext(DbContext _, Boolean buffer)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1 results, QueryContext queryContext, IList`1 entityTrackingInfos, IList`1 entityAccessors)+MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)

Steps to reproduce

using System;
using System.Linq;
using System.Threading.Tasks;

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;

namespace EFCoreBugs
{
    class Program
    {
        static async Task Main(string[] args)
        {
            using (var db = new TestDb())
            {
                await db.Database.EnsureDeletedAsync();
                await db.Database.EnsureCreatedAsync();
            }
            using (var db = new TestDb())
            {
                var key = Guid.NewGuid();
                db.Todos
                    .Where(x => key ==
                        (x.Type == TodoType.foo9
                            ? key
                            : x.Type == TodoType.foo3
                                ? key
                                : x.Type == TodoType.foo1
                                    ? key
                                    : x.Type == TodoType.foo4
                                        ? key
                                        : x.Type == TodoType.foo6
                                            ? key
                                            : x.Type == TodoType.foo7
                                                ? key
                                                : x.Type == TodoType.foo8
                                                    ? key
                                                    : x.Type == TodoType.foo10
                                                        ? key
                                                        : x.Type == TodoType.foo11
                                                            ? key
                                                            : x.Type == TodoType.foo4
                                                                ? key
                                                                : x.Type == TodoType.foo2
                                                                    ? key
                                                                    : key))
                    .ToList();
            }

            Console.ReadLine();
        }
    }

    public class TestDb : DbContext
    {
        public DbSet<Todo> Todos { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var log = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => __ == LogLevel.Error, true) });
            optionsBuilder.UseLoggerFactory(log);
            optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=master;Database=bugtest;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False");
        }
    }

    public class Todo
    {
        public Guid Id { get; set; }
        public TodoType Type { get; set; }
    }

    public enum TodoType
    {
        foo0 = 0,
        foo1 = 1,
        foo2 = 2,
        foo3 = 3,
        foo4 = 4,
        foo6 = 6,
        foo7 = 7,
        foo8 = 8,
        foo9 = 9,
        foo10 = 10,
        foo11 = 11
    }
}

Further technical details

EF Core version: 2.1.2
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: win10 x64
IDE: Visual Studio 2017 15.8.0 Preview 4.0

@divega
Copy link
Contributor

divega commented Jul 20, 2018

@bradmarder do you actually always return key for all the TodoType values? In that case you could probably formulate the query using Enumerable.Contains as a workaround for this limitation.

@divega
Copy link
Contributor

divega commented Jul 20, 2018

EF Triage: We will use this to track flattening CASE statements.

@divega divega added this to the Backlog milestone Jul 20, 2018
@divega divega changed the title Invalid SQL generated - Case expressions may only be nested to level 10 Flatten CASE expressions to avoid error "Case expressions may only be nested to level 10" Jul 20, 2018
@divega divega added type-enhancement help wanted This issue involves technologies where we are not experts. Expert help would be appreciated. labels Jul 20, 2018
@bradmarder
Copy link
Author

@divega I just returned key for the purposes of demonstrating this issue. In the original code, different values are returned.

@divega divega added good first issue This issue should be relatively straightforward to fix. help wanted This issue involves technologies where we are not experts. Expert help would be appreciated. and removed help wanted This issue involves technologies where we are not experts. Expert help would be appreciated. good first issue This issue should be relatively straightforward to fix. labels May 31, 2019
@gdunit
Copy link

gdunit commented Jun 26, 2019

I am having an issue which I believe may be related to this..
In a query that has a lot of .Include() (11 to be exact) and nested .ThenInclude() statements on each include, the following error is thrown:

System.Data.SqlClient.SqlException (0x80131904): Case expressions may only be nested to level 10.
Case expressions may only be nested to level 10.
Case expressions may only be nested to level 10.
Incorrect syntax near 'x5'.
at System.Data.SqlClient.SqlCommand.<>c.b__122_0(Task1 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 where exception was thrown ---
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteAsync(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary2 parameterValues, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable1.AsyncEnumerator.BufferlessMoveNext(DbContext _, Boolean buffer, 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.AsyncQueryingEnumerable1.AsyncEnumerator.MoveNext(CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.IncludeCollectionAsync[TEntity,TRelated,TElement](Int32 includeId, INavigation navigation, INavigation inverseNavigation, IEntityType targetEntityType, IClrCollectionAccessor clrCollectionAccessor, IClrPropertySetter inverseClrPropertySetter, Boolean tracking, TEntity entity, Func1 relatedEntitiesFactory, Func3 joinPredicate, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler.IncludeLoadTreeNodeBase._AwaitMany(IReadOnlyList1 taskFactories)
at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._IncludeAsync[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Func5 fixup, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.TaskLiftingExpressionVisitor._ExecuteAsync[T](IReadOnlyList1 taskFactories, Func2 selector) at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.AsyncSelectEnumerable2.AsyncSelectEnumerator.MoveNext(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.IncludeCollectionAsync[TEntity,TRelated,TElement](Int32 includeId, INavigation navigation, INavigation inverseNavigation, IEntityType targetEntityType, IClrCollectionAccessor clrCollectionAccessor, IClrPropertySetter inverseClrPropertySetter, Boolean tracking, TEntity entity, Func1 relatedEntitiesFactory, Func3 joinPredicate, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._IncludeAsync[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Func5 fixup, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.TaskLiftingExpressionVisitor._ExecuteAsync[T](IReadOnlyList1 taskFactories, Func2 selector) at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.AsyncSelectEnumerable2.AsyncSelectEnumerator.MoveNext(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.IncludeCollectionAsync[TEntity,TRelated,TElement](Int32 includeId, INavigation navigation, INavigation inverseNavigation, IEntityType targetEntityType, IClrCollectionAccessor clrCollectionAccessor, IClrPropertySetter inverseClrPropertySetter, Boolean tracking, TEntity entity, Func1 relatedEntitiesFactory, Func3 joinPredicate, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._IncludeAsync[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Func5 fixup, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.TaskLiftingExpressionVisitor._ExecuteAsync[T](IReadOnlyList1 taskFactories, Func2 selector) at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.AsyncSelectEnumerable2.AsyncSelectEnumerator.MoveNext(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.IncludeCollectionAsync[TEntity,TRelated,TElement](Int32 includeId, INavigation navigation, INavigation inverseNavigation, IEntityType targetEntityType, IClrCollectionAccessor clrCollectionAccessor, IClrPropertySetter inverseClrPropertySetter, Boolean tracking, TEntity entity, Func1 relatedEntitiesFactory, Func3 joinPredicate, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler.IncludeLoadTreeNodeBase._AwaitMany(IReadOnlyList1 taskFactories) at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._IncludeAsync[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Func5 fixup, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.TaskLiftingExpressionVisitor.ExecuteAsync[T](IReadOnlyList1 taskFactories, Func2 selector)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.AsyncSelectEnumerable2.AsyncSelectEnumerator.MoveNext(CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken)
at System.Linq.AsyncEnumerable.Aggregate
[TSource,TAccumulate,TResult](IAsyncEnumerable1 source, TAccumulate seed, Func3 accumulator, Func`2 resultSelector, CancellationToken cancellationToken) in D:\a\1\s\Ix.NET\Source\System.Interactive.Async\Aggregate.cs:line 120
at BrandBuilder.Activities.ActivityAppService.ReIndexAll() in C:\Users\Davy\Dropbox\BrandBuilder\aspnet-core\src\BrandBuilder.Application\Activities\ActivityAppService.cs:line 603
at lambda_method(Closure , Object )
at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()

Is this the same issue, or should I raise a new one?
If this is the same issue, is there a workaround?

@ajcvickers ajcvickers added good first issue This issue should be relatively straightforward to fix. and removed help wanted This issue involves technologies where we are not experts. Expert help would be appreciated. labels Aug 5, 2019
@ajcvickers ajcvickers removed the good first issue This issue should be relatively straightforward to fix. label Aug 30, 2019
@maumar
Copy link
Contributor

maumar commented Nov 13, 2019

duplicate of #2881

@smitpatel smitpatel self-assigned this Nov 15, 2019
@smitpatel smitpatel added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Nov 15, 2019
@smitpatel smitpatel modified the milestones: Backlog, 3.1.0, 5.0.0 Nov 15, 2019
smitpatel pushed a commit that referenced this issue Nov 15, 2019
* Fixed #12729 Flatten Case expression
@Yen-David
Copy link

@smitpatel, it's not really a duplicate. It was fixed in .Net Core 2.2 but appeared again in .Net Core 3.0! You must re-close it (close it again).

@smitpatel
Copy link
Member

@Yen-David - Stop spamming all the issue linked with this. This issue is fixed in 5.0 milestone hence it will be released in next version of EF Core (which comes after EF Core 3.1). The fix is available in nightly builds. I am not sure from where you are getting idea that it was fixed in .NET Core 2.2. It was never fixed in any release of EF Core upto 3.1

@Yen-David
Copy link

@smitpatel, thank you for fixing it in a nightly build. I am currently staying with .Net Core 2.2 just for this to work. I will upgrade to .Net Core 3.1 by skipping .Net Core 3.0, thus avoiding this bug in 3.0. Trust me. I discovered this bug after my upgrade to 3.0, and rolled back to 2.2 for it to work again. The painful experience cannot be mistaken. I'll always remember it!

@maumar
Copy link
Contributor

maumar commented Mar 4, 2020

@ajcvickers we got a(nother) customer hitting this scenario - should we reconsider this for patch?

@smitpatel
Copy link
Member

I looked at the SQL generated in the case. It is just used to convert to specific string based on value. Doing that using a client function can easily give the same result. Certainly generates much shorter SQL and negligible perf difference.

@maumar
Copy link
Contributor

maumar commented Mar 4, 2020

problem is that odata generates that query, and the query string looks fairly innocuous

@ajcvickers
Copy link
Member

@maumar @smitpatel I'll remove from the milestone and we can discuss on Friday.

@ajcvickers ajcvickers reopened this Mar 4, 2020
@ajcvickers ajcvickers removed this from the 5.0.0 milestone Mar 4, 2020
@ajcvickers ajcvickers added this to the 3.1.x milestone Mar 16, 2020
@ajcvickers ajcvickers assigned maumar and unassigned smitpatel Mar 16, 2020
maumar added a commit that referenced this issue Mar 20, 2020
Summary of the changes
- Added CaseWhenFlatteningExpressionVisitor postprocessor
- Recursively collapse relevant CaseExpression
@ajcvickers ajcvickers modified the milestones: 3.1.x, 3.1.4 Mar 27, 2020
@smitpatel
Copy link
Member

This has not been merged to release/3.1

@ajcvickers ajcvickers reopened this Mar 31, 2020
ajcvickers pushed a commit that referenced this issue Apr 4, 2020
Summary of the changes
- Added CaseWhenFlatteningExpressionVisitor postprocessor
- Recursively collapse relevant CaseExpression
ajcvickers pushed a commit that referenced this issue Apr 4, 2020
Summary of the changes
- Added CaseWhenFlatteningExpressionVisitor postprocessor
- Recursively collapse relevant CaseExpression
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported Servicing-approved type-enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants