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

ThenInclude chain on One-To-Many Reflection doesn't map objects from sql query #24260

Closed
nehio opened this issue Feb 24, 2021 · 4 comments
Closed
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@nehio
Copy link

nehio commented Feb 24, 2021

Introduction

I have not seen anything in other bugs related to this behaviour in current open issues. I will describe as best i can the behaviour. Here is the link of a small repo reproducing the issue : Test repo

Models

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

            public string LastName { get; set; }


            public string FirstName { get; set; }

            public UserInformation Information { get; set; }
        }
        public class UserInformation
        {
            [Key]
            public int Id { get; set; }
            public List<UserInformationPhoneNumber> PhoneNumbers { get; set; }

            public ApplicationUser User { get; set; }
        }
        public class UserInformationPhoneNumber
        {

            public UserInformationPhoneNumber()
            {
                PhoneNumber = new PhoneNumber();
            }

            public UserInformationPhoneNumber(PhoneNumber p)
            {
                PhoneNumber = p;
            }

            public UserInformation UserInformation { get; set; }

            [ForeignKey("UserInformation")]
            public int UserInformationId { get; set; }

            public PhoneNumber PhoneNumber { get; set; }

            [ForeignKey("PhoneNumber")]
            public int PhoneNumberId { get; set; }
        }
        public class PhoneNumber
        {

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

            public string Value { get; set; }

        }

Relations definition

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<ApplicationUser>(entity =>
            {
                entity.HasOne(p => p.Information)
                .WithOne(i => i.User)
                .HasForeignKey<UserInformation>(b => b.Id);
            });

            builder.Entity<UserInformationPhoneNumber>()
            .HasKey(u => new { u.PhoneNumberId, u.UserInformationId });

            // These are not really required as the relations are defined in the classes themselves
            // The behaviour does not change by adding these
            //builder.Entity<UserInformationPhoneNumber>().HasOne(s => s.UserInformation).WithMany(s => s.PhoneNumbers).HasForeignKey(s => s.UserInformationId);
            //builder.Entity<UserInformationPhoneNumber>().HasOne(s => s.PhoneNumber).WithMany().HasForeignKey(s => s.PhoneNumberId);
        }

Adding mock data to query against

 private void AddUserToAppplication()
        {

            _context.ApplicationUsers.Add(new ApplicationUser
            {
                FirstName = "Name",
                LastName = "LastName",
                Information = new UserInformation
                {
                    PhoneNumbers = new List<UserInformationPhoneNumber>()
                    {
                        new UserInformationPhoneNumber
                        {
                            PhoneNumber = new PhoneNumber
                            {
                                Value = "+33 06 12 34 56 78"
                            }
                        }
                    }
                }
            });

            _context.SaveChanges();
        }

Bug part

 public IActionResult Index()
        {

            if (!_context.ApplicationUsers.Any())
            {
                AddUserToAppplication();
            }
            var query = _context.ApplicationUsers
                .Include(s => s.Information)
                .ThenInclude(s => s.PhoneNumbers)
                .ThenInclude(s => s.PhoneNumber) // Include generates empty objects with no Id or Values set
                .FirstOrDefault(s=> s.FirstName=="Name");
            
            return View();
        }

Expected behaviour

query returns a single "ApplicationUser" wich has within Information a single entry in the PhoneNumbers, wich contains a single PhoneNumber item containing the phone number.

Actual behaviour

PhoneNumber is an empty object with Id 0 and Value null (same is multiple PhoneNumber items), while the previous navigation property knows there's a ForeignKey for PhoneNumber with Id 1 that exists in the DB, the values are just not mapped when chained to the next .ThenInclude() .

** Sql generated **

SELECT [t].[Id], [t].[FirstName], [t].[LastName], [t].[Id0], [t0].[PhoneNumberId], [t0].[UserInformationId], [t0].[Id], [t0].[Value]
FROM (
    SELECT TOP(1) [a].[Id], [a].[FirstName], [a].[LastName], [u].[Id] AS [Id0]
    FROM [ApplicationUsers] AS [a]
    LEFT JOIN [UserInformation] AS [u] ON [a].[Id] = [u].[Id]
    WHERE [a].[FirstName] = N'Name'
) AS [t]
LEFT JOIN (
    SELECT [u0].[PhoneNumberId], [u0].[UserInformationId], [p].[Id], [p].[Value]
    FROM [UserInformationPhoneNumber] AS [u0]
    INNER JOIN [PhoneNumber] AS [p] ON [u0].[PhoneNumberId] = [p].[Id]
) AS [t0] ON [t].[Id0] = [t0].[UserInformationId]
ORDER BY [t].[Id], [t].[Id0], [t0].[PhoneNumberId], [t0].[UserInformationId], [t0].[Id]

This query executed in SSMS returns :
image

Where we can see that the query itselfs returns the PhoneNumber.Value and PhoneNumber.Id

image

But while running we can see that PhoneNumber mapping was ignored once returned from SQL.

I hope that i haven't missed anything configuration wise, but this seemes like a pretty straightforward query that should work.

Provider and version information

EF Core version: 5.0.3
Visual Studio 16.8.6
Database provider: Microsoft.EntityFrameworkCore.SqlServer 2019
Target framework: NET 5.0 (core)
Operating system: Windows 10 20H2
IDE: (e.g. Visual Studio 2019 16.3)

@anranruye
Copy link

I think the issue is caused by:

            public UserInformationPhoneNumber()
            {
                PhoneNumber = new PhoneNumber(); //comment this line may help
            }

@nehio
Copy link
Author

nehio commented Feb 25, 2021

You are quite correct @anranruyye. Is this a design choice ? Should the mapping of the object returned from DB be ignored if there's a constructor for a new empty PhoneNumber ? This code worked on EF Core 2.1 and would map objects without a problem.

@smitpatel
Copy link
Contributor

Refer to #18007

@nehio
Copy link
Author

nehio commented Feb 25, 2021

Thank you for your answers on this matter

@nehio nehio closed this as completed Feb 25, 2021
@ajcvickers ajcvickers added the closed-no-further-action The issue is closed and no further action is planned. label Feb 25, 2021
@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests

4 participants