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 inheritance with owned properties in the in-memory database #23285

Closed
hgGeorg opened this issue Nov 12, 2020 · 6 comments
Closed
Assignees
Labels
area-in-memory closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported regression Servicing-approved type-bug
Milestone

Comments

@hgGeorg
Copy link

hgGeorg commented Nov 12, 2020

Using owned properties within class inheritance hierarchies causes columns/properties to be mapped incorrctly, or causes various exception to be thrown when trying to read something from DbContext.

Setup

[Owned]
public class OwnedClass
{
    public string A { get; set; }
    public string B { get; set; }
}

public class Root
{
    public int Id { get; set; }
    public OwnedClass OwnedProp { get; set; }

    public Root()
    {
        OwnedProp = new OwnedClass();
    }
}

public class ChildA : Root
{
    public bool Prop { get; set; }
}

public class ChildB : Root
{
    public double Prop { get; set; }
}

public class TestContext : DbContext
{
    public DbSet<Root> Table { get; set; }

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

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ChildA>().HasBaseType<Root>();
        modelBuilder.Entity<ChildB>().HasBaseType<Root>();
        base.OnModelCreating(modelBuilder);
    }
}

Testcase

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

context.Table.Add(new ChildA());
context.SaveChanges();
context.ChangeTracker.Clear();
context.Table.ToList();

What exactly happens depends on the class setup. In this example a InvalidCastException is thrown.
If ChildB.Prop is a nullable property, a NullReferenceException is thrown.
If Prop of both child classes are nullable, then null is always returned regardless what has been saved.

Might be related to #23211 and/or #23276

EF Core version: 5.0.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer/Microsoft.EntityFrameworkCore.InMemory
Target framework: NET 5.0
Operating system: Windows 10, 2004
IDE: Visual Studio 2019 16.8

@ajcvickers
Copy link
Contributor

@smitpatel @maumar This is a regression in 5.0. Stack trace:

fail: 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.Double' to type 'System.Boolean'.
         at lambda_method(Closure , ValueBuffer )
         at System.Linq.Enumerable.SelectEnumerableIterator`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.Double' to type 'System.Boolean'.
         at lambda_method(Closure , ValueBuffer )
         at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
         at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNextHelper()
         at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNext()
Unhandled exception. System.InvalidCastException: Unable to cast object of type 'System.Double' to type 'System.Boolean'.
   at lambda_method(Closure , ValueBuffer )
   at System.Linq.Enumerable.SelectEnumerableIterator`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 /home/ajcvickers/AllTogetherNow/FiveOh/Program.cs:line 95

@smitpatel
Copy link
Contributor

Note: error only happens in InMemory. Works for SqlServer correctly.

@ajcvickers ajcvickers added this to the 5.0.1 milestone Nov 12, 2020
@smitpatel
Copy link
Contributor

Meanwhile, initializing reference navigations in the ctor causes errors. See https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/breaking-changes#nonnullreferences

@ajcvickers
Copy link
Contributor

@smitpatel I missed OwnedProp = new OwnedClass(); Is that the root cause?

@smitpatel
Copy link
Contributor

smitpatel commented Nov 12, 2020

No that is not the root cause. There is a bug in InMemory but once that is fixed, the reference navigation can have side-effects in the app as usual.

smitpatel added a commit that referenced this issue Nov 12, 2020
Resolves #23285

Now my long paragraph to future contributors to this file
This issue reverts the fix from #22202 and fixes it differently.
The main issue is that when we generate a LeftJoin through GJ-DIE-SM pattern we need to generate result selector for SM scenario.
In this result selector, either we copy VB from source as is and the preserve the inner projection mapping with remapped parameters or we apply projection through the result selector and regenerate bindings for outer.
What happened in #22202 that when generating result selector and new bindings, we changed indexes of VB which broke owned navigation mappings. So we fixed that by preserving indexes from entity projections in WeakNavigation expansion scenario.
This caused side-effect that now we are applying selector for non-entity projection but preserving VB slots for entity projections. Further we used the read expression to preserve value buffer as is meaning multiple different properties which are in same slot were not preserved correctly causing InvalidCastException while reading.
A better approach is to generate result selector accounting for the owned navigations too. Essentially add owned navigation projections to result selector too. This has to happen in both case, when adding LeftJoin in non-owned scenario and in owned scenario.
Overall it makes all indexes happy.
Thus, harmony in the universe was restored.
@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 12, 2020
@hgGeorg
Copy link
Author

hgGeorg commented Nov 13, 2020

Note: error only happens in InMemory. Works for SqlServer correctly.

Seems correct, I might've mixed that up with another minor issue: Inheritance hierarchies with the same property names have different column names implicitly generated compared to efcore3.1. So changing ef core from 3.1 to 5.0 causes columns and properties to mapped incorrectly in some cases. But there is an easy workaround of just assigning an explicit column name.

smitpatel added a commit that referenced this issue Nov 17, 2020
Resolves #23285

Now my long paragraph to future contributors to this file
This issue reverts the fix from #22202 and fixes it differently.
The main issue is that when we generate a LeftJoin through GJ-DIE-SM pattern we need to generate result selector for SM scenario.
In this result selector, either we copy VB from source as is and the preserve the inner projection mapping with remapped parameters or we apply projection through the result selector and regenerate bindings for outer.
What happened in #22202 that when generating result selector and new bindings, we changed indexes of VB which broke owned navigation mappings. So we fixed that by preserving indexes from entity projections in WeakNavigation expansion scenario.
This caused side-effect that now we are applying selector for non-entity projection but preserving VB slots for entity projections. Further we used the read expression to preserve value buffer as is meaning multiple different properties which are in same slot were not preserved correctly causing InvalidCastException while reading.
A better approach is to generate result selector accounting for the owned navigations too. Essentially add owned navigation projections to result selector too. This has to happen in both case, when adding LeftJoin in non-owned scenario and in owned scenario.
Overall it makes all indexes happy.
Thus, harmony in the universe was restored.
@ajcvickers ajcvickers changed the title Entity inheritance with owned property Exception when using inheritance with owned properties in the in-memory database Dec 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-in-memory closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported regression Servicing-approved type-bug
Projects
None yet
Development

No branches or pull requests

4 participants