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

EF returning NULL from queries for entities that include nested owned types #21807

Closed
primarilysoftware opened this issue Jul 27, 2020 · 4 comments · Fixed by #22019
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 type-bug
Milestone

Comments

@primarilysoftware
Copy link

I have a EF model for an entity like:

    public class Entity
    {
        public string Id { get; set; }
        public Contact Contact { get; set; }
    }

    public class Contact
    {
        public string Name { get; set; }
        public Address Address { get; set; }
    }

    public class Address
    {
        public string Street { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Zip { get; set; }
    }

    public class EntityContext : DbContext
    {
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Entity>(builder =>
            {
                builder.HasKey(x => x.Id);

                builder.OwnsOne(x => x.Contact, contact =>
                {
                    contact.OwnsOne(c => c.Address);
                });
            });
        }
    }

When using EF to query for these entities, if Contact.Name is null, the Contact property of all queried entities is always null, even if Contact.Address is non null.

Steps to reproduce

using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;

namespace EfCoreNestedOwnedTypes
{
    [TestClass]
    public class OwnedTypeTests
    {
        private static readonly string ConnectionString = "Data Source = test.db";

        private static readonly DbContextOptions<EntityContext> Options = new DbContextOptionsBuilder<EntityContext>()
            .UseSqlite(ConnectionString)
            .Options;

        [TestInitialize]
        public async Task Initialize()
        {
            using (var context = new EntityContext(Options))
            {
                await context.Database.MigrateAsync();
            }
        }

        [TestMethod]
        public async Task Querying_entity_with_value_for_nested_owned_type_should_not_return_null()
        {
            var id = Guid.NewGuid().ToString();

            using (var context = new EntityContext(Options))
            {
                context.Add(new Entity
                {
                    Id = id,
                    Contact = new Contact
                    {
                        Address = new Address
                        {
                            Zip = "12345"
                        }
                    }
                });

                await context.SaveChangesAsync();
            }

            // Get the zip using a DbCommand
            string? dbCommandZip = null;

            using (var connection = new SqliteConnection(ConnectionString))
            {
                var command = connection.CreateCommand();
                command.CommandText = @"
                    SELECT Contact_Address_Zip FROM Entity WHERE Id=$id
                ";

                command.Parameters.AddWithValue("$id", id);

                await connection.OpenAsync();
                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        dbCommandZip = reader.GetString(0);
                    }
                }
            }

            // Get the zip using EF
            string? efZip = null;

            using (var context = new EntityContext(Options))
            {
                var result = await context.Set<Entity>().SingleOrDefaultAsync(x => x.Id == id);
                efZip = result?.Contact?.Address?.Zip;
            }

            Assert.AreEqual(dbCommandZip, efZip);
        }
    }

    public class Entity
    {
        public string Id { get; set; }
        public Contact Contact { get; set; }
    }

    public class Contact
    {
        public string Name { get; set; }
        public Address Address { get; set; }
    }

    public class Address
    {
        public string Street { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Zip { get; set; }
    }

    public class EntityContext : DbContext
    {
        public EntityContext(DbContextOptions<EntityContext> options) : base(options)
        {

        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Entity>(builder =>
            {
                builder.HasKey(x => x.Id);

                builder.OwnsOne(x => x.Contact, contact =>
                {
                    contact.OwnsOne(c => c.Address);
                });
            });

            base.OnModelCreating(modelBuilder);
        }
    }

    public class EntityContextFactory : IDesignTimeDbContextFactory<EntityContext>
    {
        public EntityContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<EntityContext>();
            optionsBuilder.UseSqlite("Data Source=test.db");

            return new EntityContext(optionsBuilder.Options);
        }
    }
}

Further technical details

EF Core version: 5.0.0-preview.7.20365.15
Database provider: I can reproduce this issue in SQLite and SqlServer
Target framework: .NET Core 3.1
Operating system: Windows
IDE: Visual Studio 2019 16.6.2

@primarilysoftware
Copy link
Author

Note that this is not an issue using EF Core 3.1.6. This seems to be a regression in the 5.0 bits.

@smitpatel
Copy link
Contributor

#21488

@ajcvickers
Copy link
Member

Note for triage: Confirmed this passes with EF Core 3.1, but fails with the latest daily build. We should investigate further given this is a regression.

@ajcvickers ajcvickers added this to the 5.0.0 milestone Aug 3, 2020
smitpatel added a commit that referenced this issue Aug 11, 2020
- Also remove additional conditions in SelectExpression

Resolves #21807
Resolves #12100
@smitpatel
Copy link
Contributor

@primarilysoftware - Fix for this is included in 5.0. You should be able to get back 3.1 behavior by marking Contact as required dependent navigation which tells EF Core to materialize its instance always even when all columns are null.

protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Entity>(builder =>
            {
                builder.HasKey(x => x.Id);

                builder.OwnsOne(x => x.Contact, contact =>
                {
                    contact.OwnsOne(c => c.Address);
                });
               builder.Navigation(x => x.Contact).IsRequired(); // Add this line
            });

            base.OnModelCreating(modelBuilder);
        }

@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 Aug 11, 2020
@smitpatel smitpatel modified the milestones: 5.0.0, 5.0.0-rc1 Aug 11, 2020
smitpatel added a commit that referenced this issue Aug 11, 2020
- Also remove additional conditions in SelectExpression

Resolves #21807
Resolves #12100
smitpatel added a commit that referenced this issue Aug 11, 2020
- Also remove additional conditions in SelectExpression

Resolves #21807
Resolves #12100
@ajcvickers ajcvickers modified the milestones: 5.0.0-rc1, 5.0.0 Nov 7, 2020
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 type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants