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

Misinterpreted expression with owned entities #20729

Closed
verdie-g opened this issue Apr 23, 2020 · 5 comments · Fixed by #22202
Closed

Misinterpreted expression with owned entities #20729

verdie-g opened this issue Apr 23, 2020 · 5 comments · Fixed by #22202
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 type-bug
Milestone

Comments

@verdie-g
Copy link

I have one entity (Owner) containing two owned entities (Owned1 and Owned2). When selecting a row and projecting it using AutoMapper I expected both owned entities to be different than null but one of them always gets null. If I remove one owned entity the other becomes different than null. Also, if I replace the navigation property of Owned2 and replace it with an int, both entities aren't null anymore.

I've reported the issue to AutoMapper but apparently the issue would be on EF's side.

Feel free to rename the title as I haven't understand the issue.

Steps to reproduce

class Program
{
    static async Task Main()
    {
        await using var db = new MyDbContext();

        db.Owners.Add(new Owner
        {
            Owned1 = new Owned1(),
            O = new Owned2(),
        });
        await db.SaveChangesAsync();

        var config = new MapperConfiguration(cfg =>
        {
            cfg.CreateMap<Owner, OwnerViewModel>();
            cfg.CreateMap<Other, OtherViewModel>();
            cfg.CreateMap<Owned1, Owned1ViewModel>();
            cfg.CreateMap<Owned2, Owned2ViewModel>();
        });

        var c = await db.Owners
            .ProjectTo<OwnerViewModel>(config)
            .ToListAsync();

        // c.O == null
    }
}


public class Owned1ViewModel { public int A { get; set; } }
public class Owned2ViewModel { public OtherViewModel Other { get; set; } }
public class OtherViewModel { public int Id { get; set; } }

public class OwnerViewModel
{
    public int Id { get; set; }
    public Owned1ViewModel Owned1 { get; set; }
    public Owned2ViewModel O { get; set; }
}

class MyDbContext : DbContext
{
    public DbSet<Owner> Owners { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder options) =>
        options.UseInMemoryDatabase("test");
}

public class Owner
{
    public int Id { get; set; }
    public Owned1 Owned1 { get; set; }
    public Owned2 O { get; set; }
}

[Owned] public class Owned1 { public int A { get; set; } }
[Owned] public class Owned2 { public Other Other { get; set; } }
public class Other { public int Id { get; set; } }

Further technical details

EF Core version: 3.1.3
Database provider: Microsoft.EntityFrameworkCore.InMemory
Target framework: .NET Core 3.1
Operating system: 5.6.3-arch1-1 x86_64 GNU/Linux
IDE: Rider 2020.1

@ajcvickers
Copy link
Contributor

@verdie-g This is working for me with EF Core 5.0 preview 3. Can you try again with preview 3 and check that it resolves your issue?

Note for team: this was not an in-memory issue; it also reproed on SQL Server.

@verdie-g
Copy link
Author

The issue is still present on my side using Microsoft.EntityFrameworkCore 5.0.0-preview.3.20181.2 and Microsoft.EntityFrameworkCore.InMemory 5.0.0-preview.3.20181.2. Making my project target netcoreapp5.0 didn't help.

@ajcvickers
Copy link
Contributor

@verdie-g Can you extract the IQueryable into a variable:

var queryable = db.Owners.ProjectTo<OwnerViewModel>(config);
        
var c = await queryable.ToListAsync();

And then drill down into it in the debugger, expand the DebugView, and post here both the Expression and Query.

image

On my machine I see this:

DbSet<Owner>()
    .Select(dtoOwner => new OwnerViewModel{ 
        Id = dtoOwner.Id, 
        O = dtoOwner.O == null ? null : new Owned2ViewModel{ Other = dtoOwner.O.Other == null ? null : new OtherViewModel{ Id = dtoOwner.O.Other.Id }
             }
        , 
        Owned1 = dtoOwner.Owned1 == null ? null : new Owned1ViewModel{ A = dtoOwner.Owned1.A }
         
    }
    )
SELECT [o].[Id], CAST(0 AS bit), CASE
    WHEN [o0].[Id] IS NULL THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END, [o0].[Id], [o].[Owned1_A]
FROM [Owners] AS [o]
LEFT JOIN [Other] AS [o0] ON [o].[O_OtherId] = [o0].[Id]

@verdie-g
Copy link
Author

Looks pretty similar

DbSet<Owner>()
    .Select(dtoOwner => new OwnerViewModel{ 
        Id = dtoOwner.Id, 
        O = dtoOwner.O == null ? null : new Owned2ViewModel{ Other = dtoOwner.O.Other == null ? null : new OtherViewModel{ Id = dtoOwner.O.Other.Id }
             }
        , 
        Owned1 = dtoOwner.Owned1 == null ? null : new Owned1ViewModel{ A = dtoOwner.Owned1.A }
         
    }
    )

And no query for InMemory provider.

@ajcvickers
Copy link
Contributor

@verdie-g Thanks--it was my error. I didn't check again with the in-memory provider. I can now repro this on the latest daily build.

@ajcvickers ajcvickers added this to the Backlog milestone May 1, 2020
smitpatel added a commit that referenced this issue Aug 24, 2020
… to weak type

Resolves #20729

The issue here was, when adding navigation to weak type, we generate GJ-DIE-SM pattern in which we push current projection to outer element. During the process we updated value buffer indexes for entity projections, some of them were being nested entity projection for weak types. This indexes differed from what it was earlier when the earlier navigation was expanded and put into the tree. Giving us incorrect expression to translate in projection.
Fix is to maintain original indexes and add a dummy one if needed.
For EntityProjections, each slot would represent reading 1 property value so we can just put the same value in same slot.
For non-entity projections, each slot can have complex value. We put this value in whatever next slot is. With projection binding it should bind correctly when translating.
@smitpatel smitpatel added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. and removed consider-for-current-release labels Aug 24, 2020
@smitpatel smitpatel modified the milestones: Backlog, 5.0.0-rc1 Aug 24, 2020
smitpatel added a commit that referenced this issue Aug 24, 2020
… to weak type

Resolves #20729

The issue here was, when adding navigation to weak type, we generate GJ-DIE-SM pattern in which we push current projection to outer element. During the process we updated value buffer indexes for entity projections, some of them were being nested entity projection for weak types. This indexes differed from what it was earlier when the earlier navigation was expanded and put into the tree. Giving us incorrect expression to translate in projection.
Fix is to maintain original indexes and add a dummy one if needed.
For EntityProjections, each slot would represent reading 1 property value so we can just put the same value in same slot.
For non-entity projections, each slot can have complex value. We put this value in whatever next slot is. With projection binding it should bind correctly when translating.
@ghost ghost closed this as completed in #22202 Aug 24, 2020
ghost pushed a commit that referenced this issue Aug 24, 2020
… to weak type (#22202)

Resolves #20729

The issue here was, when adding navigation to weak type, we generate GJ-DIE-SM pattern in which we push current projection to outer element. During the process we updated value buffer indexes for entity projections, some of them were being nested entity projection for weak types. This indexes differed from what it was earlier when the earlier navigation was expanded and put into the tree. Giving us incorrect expression to translate in projection.
Fix is to maintain original indexes and add a dummy one if needed.
For EntityProjections, each slot would represent reading 1 property value so we can just put the same value in same slot.
For non-entity projections, each slot can have complex value. We put this value in whatever next slot is. With projection binding it should bind correctly when translating.
@ajcvickers ajcvickers modified the milestones: 5.0.0-rc1, 5.0.0 Nov 7, 2020
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 closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants