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

Including collection navigation after optional navigation throws NRE with async in 2.0.0 #9038

Closed
caleblloyd opened this issue Jul 1, 2017 · 46 comments
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. punted-for-2.0 type-bug
Milestone

Comments

@caleblloyd
Copy link

caleblloyd commented Jul 1, 2017

We are trying to upgrade our MySQL provider to 2.0.0. Our Discriminator Test that tests TPH Inheritance is resulting in this NullReferenceException

Here is the Discriminator Test - read it for a good laugh, I'm put some time into making sure the Hierarchies were fun 😄

Here is the Query that is causing the Exception:

				var teachers = await db.People.OfType<PersonTeacher>()
					.Where(m => m.Id >= _fixture.LowestTeacherId && m.Id <= _fixture.HighestTeacherId)
					.OrderBy(m => m.Id)
					.Include(m => m.Family)
					.ThenInclude(m => m.Members)
					.Include(m => m.Students)
					.ThenInclude(m => m.Family)
					.ThenInclude(m => m.Members)
					.ToListAsync();

Here is the Exception:

Failed   Pomelo.EntityFrameworkCore.MySql.PerfTests.Tests.Models.DiscriminatorTest.TestDiscriminator
Error Message:
 System.NullReferenceException : Object reference not set to an instance of an object.
Stack Trace:
   at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler.IncludeLoadTreeNodeBase.<_AwaitMany>d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler.<_IncludeAsync>d__18`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.TaskLiftingExpressionVisitor.<_ExecuteAsync>d__8`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.AsyncSelectEnumerable`2.AsyncSelectEnumerator.<MoveNext>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Linq.AsyncEnumerable.SelectEnumerableAsyncIterator`2.<MoveNextCore>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Linq.AsyncEnumerable.AsyncIterator`1.<MoveNext>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.<MoveNext>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Linq.AsyncEnumerable.<Aggregate_>d__6`3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Pomelo.EntityFrameworkCore.MySql.PerfTests.Tests.Models.DiscriminatorTest.<TestDiscriminator>d__2.MoveNext() in /home/caleb/Projects/caleb/Pomelo.EntityFrameworkCore.Mysql/test/EFCore.MySql.FunctionalTests/Tests/Models/DiscriminatorTest.cs:line 96
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
@caleblloyd
Copy link
Author

The NullReferenceException appears to be triggered by some EF internal logic in the ThenInclude statements

@maumar
Copy link
Contributor

maumar commented Jul 5, 2017

Still repros in the current bits, and only in async. Full repro:

        [Fact]
        public virtual void Repro8693()
        {
            using (var ctx = new MyContext9038())
            {
                ctx.Database.EnsureDeleted();
                ctx.Database.EnsureCreated();

                var famalies = new List<PersonFamily>
                {
                    new PersonFamily
                    {
                        LastName = "Garrison",
                    },
                    new PersonFamily
                    {
                        LastName = "Cartman",
                    },
                    new PersonFamily
                    {
                        LastName = "McCormick",
                    },
                    new PersonFamily
                    {
                        LastName = "Broflovski",
                    },
                    new PersonFamily
                    {
                        LastName = "Marsh",
                    },
                };
                var teachers = new List<PersonTeacher>
                {
                    new PersonTeacher {Name = "Ms. Frizzle"},
                    new PersonTeacher {Name = "Mr. Garrison", Family = famalies[0]},
                    new PersonTeacher {Name = "Mr. Hat", Family = famalies[0]},
                };
                var students = new List<PersonKid>
                {
                    new PersonKid {Name = "Arnold", Grade = 2, Teacher = teachers[0]},
                    new PersonKid {Name = "Phoebe", Grade = 2, Teacher = teachers[0]},
                    new PersonKid {Name = "Wanda", Grade = 2, Teacher = teachers[0]},

                    new PersonKid {Name = "Eric", Grade = 4, Teacher = teachers[1], Family = famalies[1]},
                    new PersonKid {Name = "Kenny", Grade = 4, Teacher = teachers[1], Family = famalies[2]},
                    new PersonKid {Name = "Kyle", Grade = 4, Teacher = teachers[1], Family = famalies[3]},
                    new PersonKid {Name = "Stan", Grade = 4, Teacher = teachers[1], Family = famalies[4]},
                };

                ctx.People.AddRange(teachers);
                ctx.People.AddRange(students);
                ctx.SaveChanges();
            }

            using (var ctx = new MyContext9038())
            {
                var teachersTask = ctx.People.OfType<PersonTeacher>()
                    .Include(m => m.Students)
                    .ThenInclude(m => m.Family)
                    .ThenInclude(m => m.Members)
                    .ToListAsync();

                teachersTask.Wait();
            }
        }

        public abstract class Person
        {
            public int Id { get; set; }

            public string Name { get; set; }

            public int? TeacherId { get; set; }

            public PersonFamily Family { get; set; }
        }

        public class PersonKid : Person
        {
            public int Grade { get; set; }

            public PersonTeacher Teacher { get; set; }
        }

        public class PersonTeacher : Person
        {
            public ICollection<PersonKid> Students { get; set; }
        }

        public class PersonFamily
        {
            public int Id { get; set; }

            public string LastName { get; set; }

            public ICollection<Person> Members { get; set; }
        }

        public class MyContext9038 : DbContext
        {
            public DbSet<Person> People { get; set; }

            public DbSet<PersonFamily> Families { get; set; }


            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(@"Server=.;Database=Repro9038;Trusted_Connection=True;MultipleActiveResultSets=True");
            }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<PersonTeacher>().HasBaseType<Person>();
                modelBuilder.Entity<PersonKid>().HasBaseType<Person>();
                modelBuilder.Entity<PersonFamily>();

                modelBuilder.Entity<PersonKid>(entity =>
                    {
                        entity.Property("Discriminator")
                            .HasMaxLength(63);
                        entity.HasIndex("Discriminator");

                        entity.HasOne(m => m.Teacher)
                            .WithMany(m => m.Students)
                            .HasForeignKey(m => m.TeacherId)
                            .HasPrincipalKey(m => m.Id)
                            .OnDelete(DeleteBehavior.Restrict);
                    });
            }
        }

@caleblloyd
Copy link
Author

@ajcvickers this will create bugs for anyone using TPH Inheritance with ThenInclude and retrieving via Async methods when upgrading to 2.0.0. Should it be higher priority?

@ajcvickers
Copy link
Contributor

@caleblloyd We will discuss again, but at this point we have essentially no time left to get anything into 2.0 unless it is a ship-stopper type bug.
@divega Thoughts?

@divega
Copy link
Contributor

divega commented Jul 11, 2017

@ajcvickers although it doesn't seem a ship stopper, it seems pretty bad. Can we have someone from the query team try to understand the fix?

@ajcvickers
Copy link
Contributor

@maumar @smitpatel Since @anpete is out, can one of you spend some time today doing some more investigation? In particular:

  • What scenarios does it repro--is it whenever two collections are included, or is it narrower than that?
  • Any ideas on what the fix would look like?

@smitpatel
Copy link
Contributor

Assigning to @maumar as he has done some investigation on issue already.
Assign me back if you want me to fix this.

@ajcvickers
Copy link
Contributor

@maumar Also, adding to my list above, what workarounds could people use if they hit this?

@maumar
Copy link
Contributor

maumar commented Jul 11, 2017

This happens for two-level navigation include, when the first level is empty optional navigation (i.e. the included value is null). We then try to access the null value to include the collection and hence the exception. We are probably missing a null safeguard somewhere - investigating further.

Workarounds:

  • use sync:
ctx.People.OfType<PersonTeacher>()
	.Include(m => m.Students)
	.ThenInclude(m => m.Family)
	.ThenInclude(m => m.Members)
	.ToList()

break query into multiple includes:

var teachersTask = ctx.People.OfType<PersonTeacher>()
	.Include(m => m.Students)
	.ThenInclude(m => m.Family).ToListAsync();

var familiesTask = ctx.Families.Include(f => f.Members).ToArrayAsync();

teachersTask.Wait();
familiesTask.Wait();

var teachers = teachersTask.Result;

filter out elements that contain null navigation:

var kidsTask1 = ctx.People.OfType<PersonKid>()
	.Where(k => k.Family != null)
	.Include(m => m.Family)
	.ThenInclude(m => m.Members)
	.ToListAsync();

var kidsTask2 = ctx.People.OfType<PersonKid>()
	.Where(k => k.Family == null)
	.ToListAsync();

kidsTask1.Wait();
kidsTask2.Wait();

var kids = kidsTask1.Result.Concat(kidsTask2.Result);

@DSilence
Copy link
Contributor

This is something that we've immediately noticed after updating. A lot of our queries broke, since we're using async everywhere. Kinda frustrating that this hasn't been fixed before stable release.

@maumar maumar removed this from the Backlog milestone Aug 15, 2017
@ajcvickers ajcvickers added this to the 2.1.0 milestone Aug 16, 2017
@ajcvickers
Copy link
Contributor

@maumar Do you think this should be considered for patch?

@maumar
Copy link
Contributor

maumar commented Aug 16, 2017

I would say so, given that this used to work before 2.0 (which we didn't realize)

@danielpmo1371
Copy link

danielpmo1371 commented Oct 6, 2017

I have made managed to use the workaround suggested by @maumar.
**Thank you @maumar **

@smitpatel or @AndriySvyryd I have just found:
https://github.com/aspnet/EntityFrameworkCore/wiki/getting-and-building-the-code
Is that the way to do the patch?

@AndriySvyryd
Copy link
Member

@danielpmo1371 We'll ship 2.0.1 as a regular NuGet package, see https://docs.microsoft.com/en-us/nuget/consume-packages/reinstalling-and-updating-packages

@chmarti
Copy link

chmarti commented Oct 15, 2017

@AndriySvyryd is there any update on a timeline for the 2.0.1 NuGet package to be released? This is blocking my client from migrating to EF Core 2.0. I'm trying to decide if I need to go through and apply your workaround to all my queries, or if I can just wait for 2.0.1 to be released.

@ErikEJ
Copy link
Contributor

ErikEJ commented Oct 16, 2017

@Eilon
Copy link
Member

Eilon commented Oct 16, 2017

BTW that feed is not the correct feed, so please be careful using it because it's a fairly random set of packages on there (many of which are not meant for public consumption). We should have a publicly-consumable feed later this week. I'll update all "patch" bug issues when that feed is ready.

@ErikEJ
Copy link
Contributor

ErikEJ commented Oct 16, 2017

@Eilon Looking forward to that - finally!

@Eilon
Copy link
Member

Eilon commented Oct 23, 2017

Hi, we have a public test feed that you can use to try out the ASP.NET/EF Core 2.0.3 patch!

To try out the pre-release patch, please refer to the following guide:

We are looking for feedback on this patch. We'd like to know if you have any issues with this patch by updating your apps and libraries to the latest packages and seeing if it fixes the issues you've had, or if it introduces any new issues. If you have any issues or questions, please reply on this issue to let us know as soon as possible.

Thanks,
Eilon

ascott18 added a commit to IntelliTect/Coalesce that referenced this issue Oct 31, 2017
ascott18 added a commit to IntelliTect/Coalesce that referenced this issue Nov 3, 2017
Fix dependency detection for select2ajaxMultiple.
@dazinator
Copy link

Just hit this. Going to try updating to the patch from the patch preview feed.. fingers crossed.

@dazinator
Copy link

Yep, patch version fixed it.

@Eilon
Copy link
Member

Eilon commented Nov 8, 2017

@dazinator thanks for confirming!

@divega divega changed the title 2.0.0 including collection navigation after optional navigation throws NRE with async Including collection navigation after optional navigation throws NRE with async in 2.0.0 Nov 9, 2017
@deepak-khopade-isdr
Copy link

deepak-khopade-isdr commented Dec 20, 2017

Upgrading from .NET Core 1.x to 2.0.3 and getting this error on Ubuntu 16.04; however, as workarounds suggested, tried using Sync and its working fine, however, would still wait for async to work before we really count on .NET Core 2.x.

@GermanKuber-zz
Copy link

Hello, guys. Any news about a fix for this?

@YaredDejene
Copy link

This error is fixed in EF Core v2.0.1.

@p2atran
Copy link

p2atran commented May 22, 2018

I'm still seeing this issue with 2.0.3. Using sync or async still reproduces the exception each time. The only difference I have is that I have an annotation on the property that is null

      [ForeignKey("TeamId")]
      public Blah myProp { get; set; }

myProp is null even when I use .Include()

@ajcvickers Is there a known bug where using both annotations and fluent api in .NET Core 2 causes the same NullReferenceException?

@maumar
Copy link
Contributor

maumar commented May 30, 2018

@p2atran this issue was specific to async path, if you see errors for sync then it must be a different problem. Can you file a new bug with the full code listing that reproduces the issue? We will investigate accordingly.

@p2atran
Copy link

p2atran commented May 30, 2018

@maumar

It looks like this thread is the same issue:
#11341

but it looks like you guys are saying it wasn't supposed to work and accidentally worked in core 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. punted-for-2.0 type-bug
Projects
None yet
Development

No branches or pull requests