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

Exception when using inner entity with owned properties in the in-memory database #23934

Closed
AntonPervushin opened this issue Jan 21, 2021 · 7 comments · Fixed by #24363
Closed
Assignees
Labels
area-in-memory area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported regression type-bug
Milestone

Comments

@AntonPervushin
Copy link

AntonPervushin commented Jan 21, 2021

Getting an exception when using a queries with an owned property in an internal property.

System.InvalidCastException: 'Unable to cast object of type 'System.Guid' to type 'System.Nullable`1[System.DateTime]'.'

Looks like related to #23285
But that bug is realy fixed.
I took test model from that issue and get an exception in 5.0.0. And have no exception in 5.0.2 with that test model.

But I have an exception with my test model.

Test model

        public class Outer
        {
            public Guid Id { get; set; }
            public OwnedClass OwnedProp { get; set; }
            public Guid InnerId { get; set; }
            public Inner Inner { get; set; }
        }

        public class Inner
        {
            public Guid Id { get; set; }
            public OwnedClass OwnedProp { get; set; }
        }

        [Owned]
        public class OwnedClass
        {
            public DateTime At { get; set; }
        }

        public class TestContext : DbContext
        {
            public DbSet<Outer> Outers { get; set; }

            public DbSet<Inner> Inners { get; set; }

            public TestContext(DbContextOptions options)
                : base(options)
            {
            }
        }

Test case

            var options = new DbContextOptionsBuilder<TestContext>()
                .UseInMemoryDatabase("Test")
                .Options;
            var context = new TestContext(options);

            var inner = new Inner
            {
                Id = Guid.NewGuid(),
                OwnedProp = new OwnedClass{ At = DateTime.Now }
            };

            var outer = new Outer
            {
                Id = Guid.NewGuid(),
                OwnedProp = new OwnedClass { At = DateTime.Now },
                InnerId = inner.Id
            };

            context.Inners.Add(inner);
            context.Outers.Add(outer);
            context.SaveChanges();
            context.ChangeTracker.Clear();

            var criteria = DateTime.Now;

            var data = context.Outers
                .Where(x => x.OwnedProp.At >= criteria 
                            || x.Inner.OwnedProp.At >= criteria // Have no exception if comment this line
                            )
                .ToList();
        }

Stack trace:
at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext() at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable1.Enumerator.MoveNextHelper()
at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable1.Enumerator.MoveNext() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)
at
...MyTest.cs:line 43

EF Core version: 5.0.1
Microsoft.EntityFrameworkCore.InMemory version 5.0.2
Database provider: Microsoft.EntityFrameworkCore.SqlServer/Microsoft.EntityFrameworkCore.InMemory
Target framework: (NET 3.1)
Operating system:
IDE: (Microsoft Visual Studio Professional 2019 16.7.6)

@ajcvickers
Copy link
Contributor

Note for triage: regression from 3.1.

Logs:

dbug: Microsoft.EntityFrameworkCore.Model.Validation[10600]
      The property 'InnerId.Inner.OwnedProp#OwnedClass' was created in shadow state because there are no eligible CLR members with a matching name.
dbug: Microsoft.EntityFrameworkCore.Model.Validation[10600]
      The property 'OuterId.Outer.OwnedProp#OwnedClass' was created in shadow state because there are no eligible CLR members with a matching name.
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 5.0.2 initialized 'TestContext' using provider 'Microsoft.EntityFrameworkCore.InMemory' with options: SensitiveDataLoggingEnabled StoreName=Test
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10806]
      Context 'TestContext' started tracking 'Inner' entity with key '{Id: 10b76e92-e6a3-48c5-9cba-fc786801269f}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10806]
      Context 'TestContext' started tracking 'OwnedClass' entity with key '{InnerId: 10b76e92-e6a3-48c5-9cba-fc786801269f}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10806]
      Context 'TestContext' started tracking 'Outer' entity with key '{Id: 0e5777de-c558-4025-83da-985b3c2b1c83}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10806]
      Context 'TestContext' started tracking 'OwnedClass' entity with key '{OuterId: 0e5777de-c558-4025-83da-985b3c2b1c83}'.
dbug: Microsoft.EntityFrameworkCore.Update[10004]
      SaveChanges starting for 'TestContext'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10800]
      DetectChanges starting for 'TestContext'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10801]
      DetectChanges completed for 'TestContext'.
info: Microsoft.EntityFrameworkCore.Update[30100]
      Saved 4 entities to in-memory store.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10807]
      The 'Inner' entity with key '{Id: 10b76e92-e6a3-48c5-9cba-fc786801269f}' tracked by 'TestContext' changed state from 'Added' to 'Unchanged'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10807]
      The 'Outer' entity with key '{Id: 0e5777de-c558-4025-83da-985b3c2b1c83}' tracked by 'TestContext' changed state from 'Added' to 'Unchanged'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10807]
      The 'OwnedClass' entity with key '{InnerId: 10b76e92-e6a3-48c5-9cba-fc786801269f}' tracked by 'TestContext' changed state from 'Added' to 'Unchanged'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10807]
      The 'OwnedClass' entity with key '{OuterId: 0e5777de-c558-4025-83da-985b3c2b1c83}' tracked by 'TestContext' changed state from 'Added' to 'Unchanged'.
dbug: Microsoft.EntityFrameworkCore.Update[10005]
      SaveChanges completed for 'TestContext' with 4 entities written to the database.
dbug: Microsoft.EntityFrameworkCore.Query[10111]
      Compiling query expression:
      'DbSet<Outer>()
          .Where(x => x.OwnedProp.At >= __criteria_0 || x.Inner.OwnedProp.At >= __criteria_0)'
dbug: Microsoft.EntityFrameworkCore.Query[10112]
      Including navigation: 'Outer.OwnedProp'.
dbug: Microsoft.EntityFrameworkCore.Query[10107]
      Generated query execution expression:
      'queryContext => new QueryingEnumerable<Outer>(
          queryContext,
          InMemoryShapedQueryCompilingExpressionVisitor.Table(
              queryContext: queryContext,
              entityType: EntityType: Outer)
              .Join(
                  inner: InMemoryShapedQueryCompilingExpressionVisitor.Table(
                      queryContext: queryContext,
                      entityType: EntityType: Inner),
                  outerKeySelector: valueBuffer => (Nullable<Guid>)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                      valueBuffer: valueBuffer,
                      index: 1,
                      property: Property: Outer.InnerId (Guid) Required FK Index),
                  innerKeySelector: valueBuffer => (Nullable<Guid>)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                      valueBuffer: valueBuffer,
                      index: 0,
                      property: Property: Inner.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd),
                  resultSelector: (outer, inner) => new ValueBuffer(new object[]
                  {
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                          valueBuffer: outer,
                          index: 0,
                          property: Property: Outer.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                          valueBuffer: outer,
                          index: 1,
                          property: Property: Outer.InnerId (Guid) Required FK Index),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                          valueBuffer: inner,
                          index: 0,
                          property: Property: Inner.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd)
                  }))
              .GroupJoin(
                  inner: InMemoryShapedQueryCompilingExpressionVisitor.Table(
                      queryContext: queryContext,
                      entityType: EntityType: Outer.OwnedProp#OwnedClass),
                  outerKeySelector: valueBuffer => ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                      valueBuffer: valueBuffer,
                      index: 0,
                      property: Property: Outer.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd),
                  innerKeySelector: valueBuffer => ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                      valueBuffer: valueBuffer,
                      index: 0,
                      property: Property: Outer.OwnedProp#OwnedClass.OuterId (no field, Guid) Shadow Required PK FK AfterSave:Throw),
                  resultSelector: (outer, inner) => new TransparentIdentifier<ValueBuffer, IEnumerable<ValueBuffer>>(
                      Outer = outer,
                      Inner = inner
                  ))
              .SelectMany(
                  collectionSelector: collection => collection.Inner
                      .DefaultIfEmpty(new ValueBuffer(new object[]
                      {
                          null,
                          null
                      })),
                  resultSelector: (outer, inner) => new ValueBuffer(new object[]
                  {
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                          valueBuffer: outer.Outer,
                          index: 0,
                          property: Property: Outer.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                          valueBuffer: outer.Outer,
                          index: 1,
                          property: Property: Outer.InnerId (Guid) Required FK Index),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                          valueBuffer: outer.Outer,
                          index: 2,
                          property: Property: Inner.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Nullable<Guid>>(
                          valueBuffer: inner,
                          index: 0,
                          property: Property: Outer.OwnedProp#OwnedClass.OuterId (no field, Guid) Shadow Required PK FK AfterSave:Throw),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Nullable<DateTime>>(
                          valueBuffer: inner,
                          index: 1,
                          property: Property: Outer.OwnedProp#OwnedClass.At (DateTime) Required)
                  }))
              .GroupJoin(
                  inner: InMemoryShapedQueryCompilingExpressionVisitor.Table(
                      queryContext: queryContext,
                      entityType: EntityType: Inner.OwnedProp#OwnedClass),
                  outerKeySelector: valueBuffer => ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                      valueBuffer: valueBuffer,
                      index: 2,
                      property: Property: Inner.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd),
                  innerKeySelector: valueBuffer => ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                      valueBuffer: valueBuffer,
                      index: 0,
                      property: Property: Inner.OwnedProp#OwnedClass.InnerId (no field, Guid) Shadow Required PK FK AfterSave:Throw),
                  resultSelector: (outer, inner) => new TransparentIdentifier<ValueBuffer, IEnumerable<ValueBuffer>>(
                      Outer = outer,
                      Inner = inner
                  ))
              .SelectMany(
                  collectionSelector: collection => collection.Inner
                      .DefaultIfEmpty(new ValueBuffer(new object[]
                      {
                          null,
                          null
                      })),
                  resultSelector: (outer, inner) => new ValueBuffer(new object[]
                  {
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                          valueBuffer: outer.Outer,
                          index: 0,
                          property: Property: Outer.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                          valueBuffer: outer.Outer,
                          index: 1,
                          property: Property: Outer.InnerId (Guid) Required FK Index),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Nullable<Guid>>(
                          valueBuffer: outer.Outer,
                          index: 3,
                          property: Property: Outer.OwnedProp#OwnedClass.OuterId (no field, Guid) Shadow Required PK FK AfterSave:Throw),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Nullable<DateTime>>(
                          valueBuffer: outer.Outer,
                          index: 4,
                          property: Property: Outer.OwnedProp#OwnedClass.At (DateTime) Required),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                          valueBuffer: outer.Outer,
                          index: 2,
                          property: Property: Inner.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Nullable<Guid>>(
                          valueBuffer: inner,
                          index: 0,
                          property: Property: Inner.OwnedProp#OwnedClass.InnerId (no field, Guid) Shadow Required PK FK AfterSave:Throw),
                      (object)ExpressionExtensions.ValueBufferTryReadValue<Nullable<DateTime>>(
                          valueBuffer: inner,
                          index: 1,
                          property: Property: Inner.OwnedProp#OwnedClass.At (DateTime) Required)
                  }))
              .Where(valueBuffer => ExpressionExtensions.ValueBufferTryReadValue<Nullable<DateTime>>(
                  valueBuffer: valueBuffer,
                  index: 4,
                  property: Property: Outer.OwnedProp#OwnedClass.At (DateTime) Required) >= (Nullable<DateTime>)InMemoryExpressionTranslatingExpressionVisitor.GetParameterValue<DateTime>(
                  queryContext: queryContext,
                  parameterName: "__criteria_0") || ExpressionExtensions.ValueBufferTryReadValue<Nullable<DateTime>>(
                  valueBuffer: valueBuffer,
                  index: 6,
                  property: Property: Inner.OwnedProp#OwnedClass.At (DateTime) Required) >= (Nullable<DateTime>)InMemoryExpressionTranslatingExpressionVisitor.GetParameterValue<DateTime>(
                  queryContext: queryContext,
                  parameterName: "__criteria_0"))
              .Select(valueBuffer => new ValueBuffer(new object[]
              {
                  (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                      valueBuffer: valueBuffer,
                      index: 0,
                      property: Property: Outer.Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd),
                  (object)ExpressionExtensions.ValueBufferTryReadValue<Guid>(
                      valueBuffer: valueBuffer,
                      index: 1,
                      property: Property: Outer.InnerId (Guid) Required FK Index),
                  (object)ExpressionExtensions.ValueBufferTryReadValue<Nullable<Guid>>(
                      valueBuffer: valueBuffer,
                      index: 2,
                      property: Property: Outer.OwnedProp#OwnedClass.OuterId (no field, Guid) Shadow Required PK FK AfterSave:Throw),
                  (object)ExpressionExtensions.ValueBufferTryReadValue<Nullable<DateTime>>(
                      valueBuffer: valueBuffer,
                      index: 3,
                      property: Property: Outer.OwnedProp#OwnedClass.At (DateTime) Required)
              })),
          Func<QueryContext, ValueBuffer, Outer>,
          TestContext,
          False
      )'
Unhandled exception. failSystem.InvalidCastException: Unable to cast object of type 'System.Guid' to type 'System.Nullable`1[System.DateTime]'.
   at lambda_method101(Closure , ValueBuffer )
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNextHelper()
   at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Program.Main() in C:\Stuff\AllTogetherNow\FiveOh\Program.cs:line 89
   at Program.<Main>()
: Microsoft.EntityFrameworkCore.Query[10100]
      An exception occurred while iterating over the results of a query for context type 'TestContext'.
      System.InvalidCastException: Unable to cast object of type 'System.Guid' to type 'System.Nullable`1[System.DateTime]'.
         at lambda_method101(Closure , ValueBuffer )
         at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
         at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNextHelper()
         at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNext()
      System.InvalidCastException: Unable to cast object of type 'System.Guid' to type 'System.Nullable`1[System.DateTime]'.
         at lambda_method101(Closure , ValueBuffer )
         at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
         at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNextHelper()
         at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNext()

Process finished with exit code -532,462,766.

@ajcvickers ajcvickers added this to the 5.0.4 milestone Jan 26, 2021
@smitpatel smitpatel removed this from the 5.0.4 milestone Jan 26, 2021
@smitpatel
Copy link
Contributor

Removing this from backlog for consider not patching this.
Work-around:
If we revert the fix for #23285 then it works correctly. Using AppContext switch should be fine way to go.
Technical challenge:

Even if we manage to come with set of conditions to play with indexes to fix this particular issue, we may cause another regression in some other case. Given there is work-around possible here and risk is high, we should avoid patching this and rather fix #21677 for 6.0 to fix these issues more robustly.

@AntonPervushin
Copy link
Author

AntonPervushin commented Jan 27, 2021

@smitpatel, thank you for your investigation of the problem.
If I understand correctly, the workaround you are talking about is ef-side. And time of it completion is not known.
Maybe you can suggest a client-side workaround?
We moved from ef 2.2 to ef 5, fixed all breaking changes and now we have no problems with SqlProvider.
But a lot of part of unit tests are failed and we can't test our business logic.

@smitpatel
Copy link
Contributor

Client side work-around is to call following line right before the query

AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue23285", true);

If the app is working for you with calling above code only once at the startup then that is also fine. If there are other queries which would break with it then you can set the switch to false again after your above query has executed.

@AntonPervushin
Copy link
Author

AntonPervushin commented Jan 28, 2021

If I set

AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue23285", true);

then instead of

System.InvalidCastException : Unable to cast object of type 'System.String' to type 'System.Nullable`1[System.DateTime]'.

I get

System.NullReferenceException : Object reference not set to an instance of an object.

in my unit tests

@ajcvickers
Copy link
Contributor

@AntonPervushin Given that setting the AppContext switch works with the repro code posted above, this means that there must be something different about the issue in your production code. Could you update the code you posted so that it fails in the same way as your production code when setting the AppContext switch?

Also, could you post the full stack trace of the failure you are getting with the AppContext switch set?

@AntonPervushin
Copy link
Author

@ajcvickers I can not reproduce that with test model yet.
There might be some problems with my unit tests fixtures.
I will be look further.

@smitpatel smitpatel removed their assignment Feb 10, 2021
@ajcvickers ajcvickers added this to the 6.0.0 milestone Feb 12, 2021
smitpatel added a commit that referenced this issue Mar 10, 2021
Implement left join as client method to reduce complexity
To resolve the indexing issue stemming from #23934
Now all the projections are applied immediately to reshape the value buffer so that all our future bindings are always read value expressions which refers to a proper index.
In order to do this, we apply select for entity type with hierarchy to avoid entity check conditional expression.
For adding projection (through ReplaceProjectionMapping),
- For client projection, we apply a select and re-generate client projection as read values
- For projection mapping, we iterate the mappings, we apply a select and re-generate the mapping as read values
- If projection mapping is empty then we add a dummy 1 so that it becomes non-range-variable
When applying projection, we generate a selector lambda to form a value buffer and replaced all the expressions to read from new value buffer. Overall this solves the issue of having complex expressions to map or pull. This also removed PushDownIntoSubquery method.

In order to avoid the issue of indexes changing when generating join due to iterating projection mappings, we now also have projectionMappingExpressions which remembers the all expressions inside projectionMapping (which are all read value as we generated before). So now we don't need to iterate the mapping and we use the existing expressions directly. This keeps existing indexes.

Resolves #13561
Resolves #17539
Resolves #18194
Resolves #18435
Resolves #19344
Resolves #19469
Resolves #19667
Resolves #19742
Resolves #19967
Resolves #20359
Resolves #21677
Resolves #23360
Resolves #17537
Resolves #18394
Resolves #23934
@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 Mar 10, 2021
smitpatel added a commit that referenced this issue Mar 10, 2021
Implement left join as client method to reduce complexity
To resolve the indexing issue stemming from #23934
Now all the projections are applied immediately to reshape the value buffer so that all our future bindings are always read value expressions which refers to a proper index.
In order to do this, we apply select for entity type with hierarchy to avoid entity check conditional expression.
For adding projection (through ReplaceProjectionMapping),
- For client projection, we apply a select and re-generate client projection as read values
- For projection mapping, we iterate the mappings, we apply a select and re-generate the mapping as read values
- If projection mapping is empty then we add a dummy 1 so that it becomes non-range-variable
When applying projection, we generate a selector lambda to form a value buffer and replace all the expressions to read from new value buffer. Overall this solves the issue of having complex expressions to map or pull. This also removed PushDownIntoSubquery method.

In order to avoid the issue of indexes changing when generating join due to iterating projection mappings, we now also have projectionMappingExpressions which remembers the all expressions inside projectionMapping (which are all read value as we generated before). So now we don't need to iterate the mapping and we use the existing expressions directly. This keeps existing indexes.

Resolves #13561
Resolves #17539
Resolves #18194
Resolves #18435
Resolves #19344
Resolves #19469
Resolves #19667
Resolves #19742
Resolves #19967
Resolves #20359
Resolves #21677
Resolves #23360
Resolves #17537
Resolves #18394
Resolves #23934
smitpatel added a commit that referenced this issue Mar 10, 2021
Implement left join as client method to reduce complexity
To resolve the indexing issue stemming from #23934
Now all the projections are applied immediately to reshape the value buffer so that all our future bindings are always read value expressions which refers to a proper index.
In order to do this, we apply select for entity type with hierarchy to avoid entity check conditional expression.
For adding projection (through ReplaceProjectionMapping),
- For client projection, we apply a select and re-generate client projection as read values
- For projection mapping, we iterate the mappings, we apply a select and re-generate the mapping as read values
- If projection mapping is empty then we add a dummy 1 so that it becomes non-range-variable
When applying projection, we generate a selector lambda to form a value buffer and replace all the expressions to read from new value buffer. Overall this solves the issue of having complex expressions to map or pull. This also removed PushDownIntoSubquery method.

In order to avoid the issue of indexes changing when generating join due to iterating projection mappings, we now also have projectionMappingExpressions which remembers the all expressions inside projectionMapping (which are all read value as we generated before). So now we don't need to iterate the mapping and we use the existing expressions directly. This keeps existing indexes.

Resolves #13561
Resolves #17539
Resolves #18194
Resolves #18435
Resolves #19344
Resolves #19469
Resolves #19667
Resolves #19742
Resolves #19967
Resolves #20359
Resolves #21677
Resolves #23360
Resolves #17537
Resolves #18394
Resolves #23934
Resolves #17620
Resolves #18912
smitpatel added a commit that referenced this issue Mar 10, 2021
Implement left join as client method to reduce complexity
To resolve the indexing issue stemming from #23934
Now all the projections are applied immediately to reshape the value buffer so that all our future bindings are always read value expressions which refers to a proper index.
In order to do this, we apply select for entity type with hierarchy to avoid entity check conditional expression.
For adding projection (through ReplaceProjectionMapping),
- For client projection, we apply a select and re-generate client projection as read values
- For projection mapping, we iterate the mappings, we apply a select and re-generate the mapping as read values
- If projection mapping is empty then we add a dummy 1 so that it becomes non-range-variable
When applying projection, we generate a selector lambda to form a value buffer and replace all the expressions to read from new value buffer. Overall this solves the issue of having complex expressions to map or pull. This also removed PushDownIntoSubquery method.

In order to avoid the issue of indexes changing when generating join due to iterating projection mappings, we now also have projectionMappingExpressions which remembers the all expressions inside projectionMapping (which are all read value as we generated before). So now we don't need to iterate the mapping and we use the existing expressions directly. This keeps existing indexes.

Resolves #13561
Resolves #17539
Resolves #18194
Resolves #18435
Resolves #19344
Resolves #19469
Resolves #19667
Resolves #19742
Resolves #19967
Resolves #20359
Resolves #21677
Resolves #23360
Resolves #17537
Resolves #18394
Resolves #23934
smitpatel added a commit that referenced this issue Mar 10, 2021
Implement left join as client method to reduce complexity
To resolve the indexing issue stemming from #23934
Now all the projections are applied immediately to reshape the value buffer so that all our future bindings are always read value expressions which refers to a proper index.
In order to do this, we apply select for entity type with hierarchy to avoid entity check conditional expression.
For adding projection (through ReplaceProjectionMapping),
- For client projection, we apply a select and re-generate client projection as read values
- For projection mapping, we iterate the mappings, we apply a select and re-generate the mapping as read values
- If projection mapping is empty then we add a dummy 1 so that it becomes non-range-variable
When applying projection, we generate a selector lambda to form a value buffer and replace all the expressions to read from new value buffer. Overall this solves the issue of having complex expressions to map or pull. This also removed PushDownIntoSubquery method.

In order to avoid the issue of indexes changing when generating join due to iterating projection mappings, we now also have projectionMappingExpressions which remembers the all expressions inside projectionMapping (which are all read value as we generated before). So now we don't need to iterate the mapping and we use the existing expressions directly. This keeps existing indexes.

Resolves #13561
Resolves #17539
Resolves #18194
Resolves #18435
Resolves #19344
Resolves #19469
Resolves #19667
Resolves #19742
Resolves #19967
Resolves #20359
Resolves #21677
Resolves #23360
Resolves #17537
Resolves #18394
Resolves #23934

# Conflicts:
#	test/EFCore.InMemory.FunctionalTests/Query/NorthwindGroupByQueryInMemoryTest.cs
@ghost ghost closed this as completed in #24363 Mar 10, 2021
ghost pushed a commit that referenced this issue Mar 10, 2021
Implement left join as client method to reduce complexity
To resolve the indexing issue stemming from #23934
Now all the projections are applied immediately to reshape the value buffer so that all our future bindings are always read value expressions which refers to a proper index.
In order to do this, we apply select for entity type with hierarchy to avoid entity check conditional expression.
For adding projection (through ReplaceProjectionMapping),
- For client projection, we apply a select and re-generate client projection as read values
- For projection mapping, we iterate the mappings, we apply a select and re-generate the mapping as read values
- If projection mapping is empty then we add a dummy 1 so that it becomes non-range-variable
When applying projection, we generate a selector lambda to form a value buffer and replace all the expressions to read from new value buffer. Overall this solves the issue of having complex expressions to map or pull. This also removed PushDownIntoSubquery method.

In order to avoid the issue of indexes changing when generating join due to iterating projection mappings, we now also have projectionMappingExpressions which remembers the all expressions inside projectionMapping (which are all read value as we generated before). So now we don't need to iterate the mapping and we use the existing expressions directly. This keeps existing indexes.

Resolves #13561
Resolves #17539
Resolves #18194
Resolves #18435
Resolves #19344
Resolves #19469
Resolves #19667
Resolves #19742
Resolves #19967
Resolves #20359
Resolves #21677
Resolves #23360
Resolves #17537
Resolves #18394
Resolves #23934

# Conflicts:
#	test/EFCore.InMemory.FunctionalTests/Query/NorthwindGroupByQueryInMemoryTest.cs
@ajcvickers ajcvickers removed this from the 6.0.0 milestone Mar 25, 2021
@ajcvickers ajcvickers added this to the 6.0.0-preview3 milestone Mar 25, 2021
@ajcvickers ajcvickers modified the milestones: 6.0.0-preview3, 6.0.0 Nov 8, 2021
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-in-memory area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported regression type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants